Автор оригинала: FreeCodeCamp Community Member.
Вы, возможно, слышали о Причина перед. Это синтаксис сверху Ocaml Это компилирует как для читаемого кода JavaScript, так и к родным, так и к байтекоду.
Это означает, что вы можете потенциально написать Одно приложение Используя синтаксис разума, и сможете запустить его в браузере, а также на телефонах Android и IOS.
Это одна из причин, почему причина (Ой, каламбур) становится все более популярным. Это особенно верно в сообществе JavaScript из-за сходства синтаксиса.
Если бы вы были разработчиком JavaScript до того, как причина вышла, и хотели изучить язык функционального программирования (FP), вам пришлось бы также изучить новый синтаксис и набор правил. Возможно, это было обескуражено многим людям.
По этой причине вам в основном нужно понимать принципы FP, на которых он основан – такой как неподумность, карри, композиция и функции высшего порядка.
Прежде чем я обнаружил причину, я пытался использовать принципы FP в JavaScript столько, сколько смогу. Однако JavaScript ограничен в этом смысле, поскольку он не должен быть языком FP. Чтобы эффективно использовать эти принципы, вам нужно использовать кучу библиотек, которые создают сложные абстракции, которые скрыты от вас.
Причина, с другой стороны, открывает весь FP Realm для всех заинтересованных завещающих JavaScript разработчиков. Он предоставляет нам возможность использовать все эти классные функции OCAML, используя синтаксис, мы очень знаем.
Последнее, но не менее важное, мы можем написать наше Реагировать или Реагировать родной Приложения используют разум.
Почему вы должны дать причину попробовать?
Я надеюсь, что вы обнаружите ответ для себя к тому времени, когда вы закончили читать этот пост.
Когда мы проходим через исходный код классической Tic Tac Tace Goy – написанный в разуме, используя реагирование – я объясню основные особенности языка. Вы увидите преимущества системы сильного типа, неподумность, сопоставление картины, функциональной композиции с использованием трубы и так далее. В отличие от JavaScript, эти функции являются неотъемлемой частью самой причины.
Разогрев
Прежде чем попасть в руки, вам нужно установить причину на вашей машине, следуя Это руководство Отказ
После этого вам нужно настроить ваше приложение. Для этого вы можете либо клонировать Мой репозиторий Содержащие код нашего приложения или вы можете настроить свой собственный проект, используя ОБРАЩАЮТСЯ ОБРАЗОВАНИЯ и код вместе.
Чтобы просмотреть ваше приложение в браузере, вам необходимо сначала скомпилировать свои причины для JavaScript. BescleScript Компилятор позаботится об этом.
Другими словами, когда вы запускаете NPM начать
(В проекте «Обрадочения») Ваша причина кода изъяты для JavaScript. Результат компиляции затем отображается в браузере. Вы можете видеть сами, насколько читательнее скомпилированный код, проверяя lib
папка внутри вашего приложения.
Наш первый компонент
Как мы уже упоминали, наше приложение Tic Tac Toe написано использование Разумность библиотека. Это делает причина доступной для разработчиков JavaScript, и много новичков исходит от этого сообщества.
Наше приложение имеет классическую структуру компонентов, как и любое другое приложение для реагирования. Мы пройдем сквозь компоненты, когда говорили о UI, и снизу вверх при описании их логики.
Давайте начнем, посмотрев на верхний уровень Приложение
составная часть.
let component = ReasonReact.statelessComponent("App"); let make = _children => { ...component, render: _self =>, };(ReasonReact.string("Tic Tac Toe"))
Компонент создается при звонке Разумность .Stactulecomponent
и пропустите название компонента к нему. Вам не нужно никаких ключевых слов классов, как в реакции, поскольку причиной не имеет ни одного.
Компонент не является ни классом, ни функцией – это так называемое запись Отказ запись
является одним из сосудистых структур данных, которые аналогичны объекту JavaScript. В отличие от последнего, однако, запись
неизменно.
Наш новый запись
Компонент содержит различные свойства по умолчанию, такие как исходное состояние, методы жизненного цикла и рендеринга. Чтобы настроить компонент нашим потребностям, нам нужно переопределить некоторые из этих свойств. Мы можем сделать это внутри сделать
Функция, которая возвращает наш компонент.
Так как запись
неизменно мы не можем переопределить свои свойства мутацией. Вместо этого нам нужно вернуть новый запись
Отказ Для этого нам нужно распределить наш компонент и переопределить свойства, которые мы хотим изменить. Это очень похоже на оператор распространения объекта JavaScript.
Так как Приложение
Это довольно простой компонент, мы хотим переопределить только по умолчанию оказывать
Метод, поэтому мы можем сделать наши элементы на экран. оказывать
Метод занимает один Я
Аргумент, который дает нам доступ к государству и редукторам, как мы увидим позже.
Поскольку разум для обзоров поддерживает Jsx наше оказывать
Функция может вернуть элементы JSX. Бекапитализированный элемент будет распознан как элемент DOM – Div
Отказ Капитализованный элемент будет признан компонентом – Игра
Отказ
Из-за сильного типа печати вы не можете просто пройти строку к элементу, чтобы отобразить его, как вы можете в классической реакции.
Вместо этого вам нужно пройти такую строку в Разумность .String
Функция помощника, которая преобразует это в Реактиция
которые могут быть оказаны.
Поскольку это немного многословна, и мы будем использовать этот помощник довольно часто, давайте храним его в TOSTRING
Переменная. Посуда вы можете использовать только Пусть
ключевое слово для этого.
let toString = ReasonReact.string;
Прежде чем двигаться дальше, давайте немного поговорим о сделать
Аргументы функции. Так как мы не передаем никаких реквизитов к Приложение
Компонент, требуется только по умолчанию дети
аргумент
Однако мы не используем его. Мы можем сделать эту явную, написав подчеркивание перед ним. Если мы этого не сделали, компилятор даст нам предупреждение о том, что аргумент не используется. Мы делаем то же самое с Я
Аргумент в оказывать
метод.
Понятые сообщения об ошибках и предупреждениях являются еще одной классной функцией, которая улучшит ваш опыт разработчика по сравнению с JavaScript.
Настройка вариантов типов
Перед погружением в самое приложение мы сначала определим нашими типы.
Причина – статически напечатанный язык. Это означает, что он оценивает типы наших ценностей во время компиляции. Другими словами, вам не нужно запускать свое приложение, чтобы проверить, правильно ли ваши типы. Это также означает, что ваш редактор может предоставить вам Полезная поддержка редактирования Отказ
Тем не менее, имея систему типа не означает, что вам нужно явно определить типы для всех значений. Если вы решите не, причина выяснится (выведите) типы для вас.
Мы воспользуемся системой типа, чтобы определить типы, которые мы будем использовать во всем нашем приложении. Это заставит нас подумать о структуре нашего приложения перед ними, и мы получим документацию по коду как бонус.
Если у вас был какой-либо опыт работы с Tymdercript или Поток , Типы причина будут выглядеть знакомы. Однако, в отличие от этих двух библиотек, вам вообще не нужна какая-либо предыдущая конфигурация (я смотрю на вас TypeScript). Типы доступны из коробки.
По причинам, мы можем различить Типы и Варианты типов (в коротких вариантах). Типы находятся например Bool
, строка
и int
Отказ С другой стороны, варианты более сложны. Подумайте о них как о перечислимых наборах ценностей или точнее, конструкторов. Варианты могут быть обработаны с помощью сопоставления с рисунком, поскольку посмотрим позже.
type player = | Cross | Circle; type field = | Empty | Marked(player);
Здесь мы определяем Игрок
и поле
Варианты Отказ При определении варианта необходимо использовать Тип
ключевое слово.
Поскольку мы строим Tic Tac Toe Goy, нам понадобится два игрока. Итак, Игрок
Тип будет иметь два возможных конструктора – Крест
и Круг
Отказ
Если мы думаем о игровой доске, мы знаем, что каждый поле
Тип может иметь два возможных конструктора – либо Пустой
или Отмечен
одним из игроков.
Если вы посмотрите на Отмечен
Конструктор, вы можете видеть, что мы используем его в качестве структуры данных. Мы используем вариант для проведения другого элемента данных. В нашем случае мы передаем это Игрок
вариант. Такое поведение довольно мощное, так как он позволяет нам объединять различные варианты и типы вместе для создания более сложных типов.
Итак, у нас есть поле
вариант. Однако нам нужно определить всю игровую доску, которая состоит из рядов полей.
type row = list(field); type board = list(row);
Каждый ряд
это список поле
S и игра доска
состоит из списка ряд
с.
Список
является одним из причин структур данных, аналогичных на массив JavaScript. Разница в том, что это неизменно. Причина также имеет массив
как сметный список фиксированных длин. Мы вернемся к этим структурам позже.
type gameState = | Playing(player) | Winner(player) | Draw;
Еще один вариант, который нам нужно определить, это Gamestate
Отказ Игра может иметь три возможных состояния. Один из Игрок
S может быть Играть
быть Победитель
или мы можем иметь Рисовать
Отказ
Теперь у нас есть все типы, которые нам нужно составить состояние нашей игры.
type state = { board, gameState, };
Государство нашего компонента – это запись
состоит из доска
и Gamestate
Отказ
Прежде чем двигаться дальше, я бы хотел поговорить о модулях. Посуточно, файлы являются модулями. Например, мы хранили все наши варианты внутри Sharedtypes.re
файл. Этот код автоматически завернут внутри модуля, как это:
module SharedTypes { /* variant types code */ }
Если мы хотели получить доступ к этому модулю в другом файле, нам не нужно Импорт
ключевое слово. Мы можем легко получить доступ к нашим модулям в любом месте нашего приложения, используя точечную обозначение – например Sharedtypes.gamestate
Отказ
Поскольку мы используем наши варианты довольно часто, мы можем сделать его более лаконичкой, написав Открыть sharedtypes
В верхней части файла, в котором мы хотим получить доступ к нашему модулю. Это позволяет нам отбрасывать точечную обозначение, поскольку мы можем использовать наш модуль в объеме нашего файла.
Создание штата
Поскольку мы знаем, как будет выглядеть состояние нашего приложения, мы можем начать строить саму игру.
Мы видели, что наши Приложение
Компонент оказывает Игра
составная часть. Это место, где начинается все веселье. Я пойду через код по шаговому коду.
Приложение
Был нестандартный компонент, аналогичный функциональному компоненту в реакции. С другой стороны, Игра
Является ли государственным, что означает, что он может содержать состояние и редукторы. Редукторы в разуме основаны на тех же принципах, что и те, которые вы знаете от Redux Отказ Вы называете действие, и редуктор поймает его и обновит состояние соответственно.
Чтобы увидеть, что происходит в Игра
Компонент, давайте проверим сделать
Функция (код сокращен).
let component = ReasonReact.reducerComponent("Game"); let make = _children => { ...component, initialState: () => initialState, reducer: (action: action, state: state) => ..., render: ({state, send}) => ..., };
В Приложение
Компонент, мы переопределили только оказывать
метод. Здесь мы перепрямили Редуктор
и инициация
свойства, а также. Мы поговорим о редукторах позже.
инициация
это функция, которая (удивительно) возвращает начальное состояние, которое мы сохранили в переменной.
let initialState = { board: [ [Empty, Empty, Empty], [Empty, Empty, Empty], [Empty, Empty, Empty], ], gameState: Playing(Cross), };
Если вы немного прокрутите и проверьте наши Государство
Введите, вы увидите, что инициация
имеет ту же структуру. Это состоит из доска
который состоит из ряд
с поле
с. В начале игры все поля Пустой
Отказ
Тем не менее, их статус может измениться, поскольку игра продолжается. Другая часть государства – это Gamestate
который изначально устанавливается на Крест
Игрок, который играет первым.
Рендеринг доска
Давайте посмотрим на оказывать
Метод нашего Игра
составная часть.
render: ({state, send}) =>,send(Restart)) onMark=(id => send(ClickSquare(id))) />
Мы уже знали, что он получает Я
аргумент Здесь мы используем разрушительность для доступа к Государство
и Отправить
функция. Это работает так же, как в JavaScript.
Метод Render возвращает Доска
компонент и передает его Государство
и два государственных обработчика как реквизит. Первый позаботится о перезапуске приложения и вторым огням, когда поле помечено игроком.
Вы могли бы заметить, что не пишете State = Государство
При прохождении Государство
пропры По причинам, если мы не изменяем имя PROP, мы можем пропустить опоры, используя этот упрощенный синтаксис.
Теперь мы можем взглянуть на Доска
составная часть. Я опустил большую часть оказывать
метод на данный момент.
let component = ReasonReact.statelessComponent("Board"); let make = (~state: state, ~onMark, ~onRestart, _children) => { ...component, render: _ =>/* ... */, };
Доска
это составляющая без гражданства. Как вы могли бы заметить, сделать
Функция теперь принимает несколько аргументов. Это реквизиты, которые мы прошли из Игра
родительский компонент.
~
Символ означает, что аргумент маркируется. При вызове функции с таким аргументом мы должны явно написать имя аргумента при вызове этой функции (компонента). И это то, что мы сделали, когда мы прошли реквизит к этому в Игра
составная часть.
Возможно, вы также заметили, что мы делаем еще одну вещь с одним из аргументов – ~ Государство: Государство
Отказ В предыдущем разделе мы определили наши Государство
тип. Здесь мы говорим компилятору, что структура этого аргумента должна быть такой же, как и из Государство
тип. Вы можете знать этот шаблон от потока.
Давайте вернемся к оказывать
Метод Доска
составная часть.
Поскольку мы имеем дело со списками там, мы поговорим о них немного больше сейчас, прежде чем проверять остальные оказывать
метод.
Экскурсия I: Список и массив
Посуда у нас есть две структуры данных, напоминающие массивы JavaScript – Список
и массив
Отказ Список
неизменно и измеряется, тогда как массив
смещается и имеет фиксированную длину. Мы используем Список
Из-за его гибкости и эффективности, которые действительно сияют, когда мы используем его рекурсивно.
Чтобы сопоставить Список
Вы можете использовать List.map
Способ, который получает два аргумента – функция и A Список
Отказ Функция принимает элемент из Список
и карты это. Это работает в значительной степени, как JavaScript Array.map
Отказ Вот простой пример:
let numbers = [1, 5, 8, 9, 15]; let increasedNumbers = List.map((num) => num + 2, numbers); Js.log(increasedNumbers); /* [3,[7,[10,[11,[17,0]]]]] */
Какие? Вы говорите, что напечатанный результат выглядит странно? Это потому, что списки в разуме есть связано Отказ
Списки печати в вашем коде могут быть запутаны. К счастью, вы можете преобразовать его в массив
используя Array.of_list
метод.
Js.log(Array.of_list(increasedNumbers)); /* [3,7,10,11,17] */
Давайте вернемся к нашему приложению и напомнить себе, как наш Государство
выглядит.
let initialState = { board: [ [Empty, Empty, Empty], [Empty, Empty, Empty], [Empty, Empty, Empty], ], gameState: Playing(Cross), };
Внутри доски оказывать
Метод мы первой карту на доска
который состоит из списка строк. Итак, сопоставив его, мы получим доступ к ряд
с. Тогда мы оказываем Собственность
составная часть.
let component = ReasonReact.statelessComponent("Board"); let make = (~state: state, ~onMark, ~onRestart, _children) => { ...component, render: _ =>( ReasonReact.array( Array.of_list( List.mapi( (index: int, row: row) =>, state.board, ), ), ) ) /* ... */
Мы используем List.mapi
Метод, который дает нам индекс
Аргумент, что нам нужно однозначно определить наши идентификаторы.
При сопоставлении Список
Для элементов JSX нам нужно сделать две дополнительные вещи.
Во-первых, нам нужно преобразовать его в массив
Использование Array.of_list
Отказ Во-вторых, нам нужно преобразовать результат к Реактиция
Использование Разумность.array
, так как мы (как уже упоминалось) не могут просто пройти строку в элемент JSX, как в реакции.
Чтобы добраться до значений полей, нам нужно отобразить карту на каждом ряд
также. Мы делаем это внутри Собственность
составная часть. Здесь каждый элемент из ряд
затем сопоставлен на Квадрат
составная часть.
let component = ReasonReact.statelessComponent("BoardRow"); let make = (~gameState: gameState, ~row: row, ~onMark, ~index: int, _children) => { ...component, render: (_) =>(ReasonReact.array( Array.of_list( List.mapi( (ind: int, value: field) => { let id = string_of_int(index) ++ string_of_int(ind);, };onMark(id)) gameState />; }, row, ), ), ))
Используя эти два сопоставления, наша доска отображается. Вы согласитесь со мной, что читаемость этого кода не так хороша из-за всех функциональных обертов.
Чтобы улучшить его, мы можем использовать труба
Оператор, который берет наше Список
Данные и трубы это через наши функции. Вот второй пример отображения – на этот раз, используя труба
Отказ
let component = ReasonReact.statelessComponent("BoardRow"); let make = (~gameState: gameState, ~row: row, ~onMark, ~index: int, _children) => { ...component, render: (_) =>( row |> List.mapi((ind: int, value: field) => { let id = string_of_int(index) ++ string_of_int(ind, };onMark(id)) gameState />; }) |> Array.of_list |> ReasonReact.array )
Это делает наш код гораздо более читаемым, не так ли? Во-первых, мы берем ряд
и пропустите его к методу картографирования. Затем мы преобразуем наш результат к массив
Отказ Наконец, мы преобразуем его в Реактиция
Отказ
Как сопоставив нашу доску, мы оказываем кучу Квадрат
Компоненты на экран и делаем это, мы создаем всю игровую доску.
Мы проезжаем пару реквизит к Квадрат
Отказ Так как мы хотим нашего ID
Чтобы быть уникальным, мы создаем его, объединяя индексы из обоих отображений. Мы также передаем по ценность
который содержит поле
Тип, который может быть либо Пустой
или Отмечен
Отказ
Наконец, мы передаем Gamestate
и onmark
обработчик, который будет вызывать, когда определенный Квадрат
нажат.
Ввод поля
let component = ReasonReact.statelessComponent("Square"); let make = (~value: field, ~gameState: gameState, ~onMark, _children) => { ...component, render: _self => , };
Квадрат
Компонент оказывает кнопку и передает его некоторые реквизиты. Здесь мы используем пару функций помощника, но я не буду говорить обо всех них подробно. Вы можете найти их все в репо Отказ
Класс кнопки рассчитывается с помощью getclass
Функция помощника, которая превращает квадратную зеленую, когда один из игроков выигрывает. Когда это происходит, все Квадрат
S будет также отключен.
Чтобы сделать кнопку ценность
Мы используем два помощника.
let toValue = (field: field) => switch (field) { | Marked(Cross) => "X" | Marked(Circle) => "O" | Empty => "" };
Tovalue
преобразует поле
Введите в строку с использованием сопоставления шаблона. Мы поговорим о схеме сопоставления позже. На данный момент вам нужно знать, что мы подходим к поле
данные для наших трех узоров. Итак, результат будет Х
, O
или пустая строка. Тогда мы используем TOSTRING
преобразовать его в Реактиция
Отказ
Флаг Мы только что сделали игровую доску. Давайте быстро переправимся, как мы это сделали.
Наш верхний уровень Приложение
Компонент оказывает Игра
Компонент, который содержит игровое состояние и передает его вместе с обработчиками к Доска
составная часть.
Доска
Затем принимает доску состояния PROP и отображает строки к Собственность
компонент, который отображает строки к Квадрат
составные части. Каждый Квадрат
Имеет обработчик OnClick, который заполнит его квадратом или кругом.
Заставить его сделать что-то уже!
Давайте посмотрим, как наша логика управляет игрой.
Поскольку у нас есть доска, мы можем позволить игроку нажать на любой квадрат. Когда это происходит, OnClick
обработчик уволен и onmark
обработчик называется.
/* Square component */
onmark
Обработчик передан из Собственность
компонент, но он был изначально определен в Игра
Компонент, который заботится о состоянии.
/* Game component */ render: ({state, send}) =>,send(Restart)) onMark=(id => send(ClickSquare(id))) />
Мы можем видеть, что onmark
опора – это Clicksquare
Редуктор, что означает, что мы используем его для обновления состояния (как в redux). onrestart
обработчик работает аналогично.
Обратите внимание, что мы проезжаем уникальные ID
к onmark
обработчик внутри Собственность
составная часть.
/* BoardRow component */ ( row |> List.mapi((ind: int, value: field) => { let id = string_of_int(index) ++ string_of_int(indonMark(id)) gameState />; }) |> Array.of_list |> ReasonReact.array )
Прежде чем посмотрите на наши редукторы в деталях, нам нужно определить действия, к которым наши редукторы будут отвечать.
type action = | ClickSquare(string) | Restart;
Что касается глобальных вариантов типов, это заставляет нас думать о нашей логике, прежде чем мы начнем реализовать его. Мы определяем два варианта действия. Clicksquare
принимает один аргумент, который будет иметь тип строка
Отказ
Теперь давайте посмотрим на наших редукторов.
let updateBoard = (board: board, gameState: gameState, id) => board |> List.mapi((ind: int, row: row) => row |> List.mapi((index: int, value: field) => string_of_int(ind) ++ string_of_int(index) === id ? switch (gameState, value) { | (_, Marked(_)) => value | (Playing(player), Empty) => Marked(player) | (_, Empty) => Empty } : value ) ); reducer: (action: action, state: state) => switch (action) { | Restart => ReasonReact.Update(initialState) | ClickSquare((id: string)) => let updatedBoard = updateBoard(state.board, state.gameState, id); ReasonReact.Update({ board: updatedBoard, gameState: checkGameState3x3(updatedBoard, state.board, state.gameState), }); },
Clicksquare
Редуктор берет ID
конкретного Квадрат
Отказ Как мы видели, мы проходим в Собственность
составная часть. Затем наш редуктор рассчитывает новое состояние.
Для доска
Государственное обновление, мы позвоним ОБНОВЛЕНИЕ
функция. Он использует ту же картографическую логику, которую мы использовали в Доска
и Собственность
составная часть. Внутри этого мы рассмотрим на Государство .board
Чтобы получить строки, а затем набрать через строки, чтобы получить значения поля.
Так как ID
Из каждого квадрата является состав идентификаторов из обоих отображений, мы будем использовать его, чтобы найти поле, которое нажал плеер. Когда мы его найдем, мы будем использовать сопоставление шаблона, чтобы определить, что с этим делать. В противном случае мы покинем квадрат ценность
немодифицированный.
Экскурсия II: сопоставление шаблона
Мы используем сопоставление шаблона для обработки наших данных. Мы определяем Шаблоны Что мы будем соответствовать нашему данные Отказ При выполнении сопоставления шаблона мы используем Переключатель
утверждение.
switch (state.gameState, value) { | (_, Marked(_)) => value | (Playing(player), Empty) => Marked(player) | (_, Empty) => Empty }
В нашем случае мы используем кортеж представлять наш данные Отказ Кортежи – это структуры данных, которые отделяют данные запятыми. Наше кортеж
содержит . Gamestate
и ценность
(содержащий поле
тип).
Тогда мы определяем несколько Шаблоны что мы будем соответствовать с нашими данными. Первый матч определяет результат всего сопоставления паттерна.
Написав подчеркивание внутри шаблона, мы говорим компилятору, что нам все равно, что такое конкретное значение. Другими словами, мы хотим иметь совпадение каждый раз.
Например, первый шаблон соответствует, когда ценность
это Отмечен
любым игроком. Итак, мы не заботимся о Gamestate
И нам все равно на типе игрока.
Когда этот шаблон соответствует, результат является оригиналом ценность
Отказ Этот шаблон предотвращает переопределение игроков, уже отмеченных Квадраты
Отказ
Второй шаблон обращается к ситуации, когда играет любой игрок, и поле это Пустой
Отказ Здесь мы используем Игрок
Введите рисунок, а затем снова в результате. Мы в основном говорим, что нам все равно, каким игроком играет ( Circle
или Cross
) Но мы все еще хотим отметить квадрат в соответствии с игроком, который на самом деле играет.
Последний шаблон действует как по умолчанию. Если первый или второй шаблон не совпадает, третий всегда будет соответствовать. Здесь мы не заботимся о Gamestate
Отказ
Тем не менее, так как мы проверяем Играть
Игровое состояние в предыдущем шаблоне мы сейчас проверяем Рисовать
или Победитель
Gamestate
тип. Если это так, мы покинем поле Пустой
Отказ Этот сценарий по умолчанию не позволяет игрокам продолжать играть, когда игра закончится.
Прохладная вещь о соположении с рисунком в разуме заключается в том, что компилятор предупреждает вас, если вы не покрывали все возможные шаблоны шаблонов. Это сэкономит вам много неприятностей, потому что вы всегда будете знать, охватываете ли вы все возможные сценарии. Итак, если компилятор не дает вам никаких предупреждений, ваше сопоставление образец никогда не потерпит неудачу.
Когда сопоставление картины закончено, конкретное поле обновляется. Когда все отображения сделаны, мы получаем новое состояние доски и храните его как Обновленная доска
Отказ Затем мы можем обновить состояние компонента, позвонив Разумность .Update
Отказ
ReasonReact.Update({ board: updatedBoard, gameState: checkGameState3x3(updatedBoard, state.board, state.gameState),
Мы обновляем доска
состояние, используя результат сопоставления шаблона. При обновлении Gamestate
мы называем CheckGamestate3x3
Помощник, который рассчитывает состояние игры для нас.
У нас есть победитель?
Давайте посмотрим, что CheckGamestate3x3
делает.
Во-первых, нам нужно определить все возможные комбинации выигрышных полей (для платы 3×3) и хранить их как WinningCombs
Отказ Мы также должны определить Уиннингры
тип.
type winningRows = list(list(int)); let winningCombs = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6], ];
Мы передали этот список для CheckGamestate
Функция как первый аргумент.
let checkGameState3x3 = checkGameState(winningCombs);
Делая это, мы используем преимущества Carrying принцип. Когда мы передаем WinningCombs
к CheckGamestate
Функция, мы вернемся к новой функции, ожидающей, что остальные аргументы будут переданы. Мы храним эту новую функцию как CheckGamestate3x3
Отказ
Это поведение действительно полезно, так как мы можем настроить CheckGamestate
Функция в зависимости от ширины и высоты доски.
Посмотрим, что происходит внутри CheckGamestate
функция.
let checkGameState = ( winningRows: winningRows, updatedBoard: board, oldBoard: board, gameState: gameState, ) => oldBoard == updatedBoard ? gameState : { let flattenBoard = List.flatten(updatedBoard); let rec check = (rest: winningRows) => { let head = List.hd(rest); let tail = List.tl(rest); switch ( getWinner(flattenBoard, head), gameEnded(flattenBoard), tail, ) { | (Cross, _, _) => Winner(Cross) | (Circle, _, _) => Winner(Circle) | (_, true, []) => Draw | (_, false, []) => whosPlaying(gameState) | _ => check(tail) }; }; check(winningRows); };
Во-первых, мы проверяем, отличается ли государством доски от предыдущего. Если это не так, мы вернем без изменений Gamestate
Отказ В противном случае мы рассчитаем новое игровое состояние.
Расчет новых состояний
Мы начинаем определение нашего нового игрового состояния, преобразуя доска
Часть государства, состоит из списка строк, простым Список
Использование Список .flatten
Отказ Сравненный результат будет иметь такую структуру:
[Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty]
Вернуться в функцию, мы определяем Проверьте
Функция, которая получает один Отдых
аргумент, который имеет тип Уиннингры
Отказ Rec
Ключевое слово до его определения означает, что он может быть вызван рекурсивно. Однако для рекурсивных вызовов функций нам также нужны рекурсивные данные. К счастью, Список
это рекурсивная структура данных.
Мы уже узнали, что списки в разуме связаны. Эта функция позволяет нам переживать Списки с использованием рекурсии без труда.
Внизу CheckGamestate
мы называем Проверьте
Функция впервые и пропустите ее WinningCombs
список. Внутри функции мы извлекаем первый элемент из Список
и хранить его как голова
Отказ Остальные Список
хранится как хвост
Отказ
После этого мы снова используем сопоставление шаблона. Мы уже знаем, как это работает, поэтому я не буду в курсе. Но стоит проверять, как мы определяем наши данные и модели.
type winner = | Cross | Circle | NoOne; switch ( getWinner(flattenBoard, head), gameEnded(flattenBoard), tail, ) { ...
Внутри Переключатель
Заявление, мы используем кортеж
снова, чтобы представлять наши данные. Наше кортеж
содержит три типа победителя элементов в результате Getwinner
Функция, логический в результате игра
Функция и оставаясь Список
Элементы ( хвост
).
Прежде чем идти дальше, давайте немного поговорим об этих двух функциях помощника.
Мы посмотрим внутрь Getwinner
Функция сначала.
let getWinner = (flattenBoard, coords) => switch ( List.nth(flattenBoard, List.nth(coords, 0)), List.nth(flattenBoard, List.nth(coords, 1)), List.nth(flattenBoard, List.nth(coords, 2)), ) { | (Marked(Cross), Marked(Cross), Marked(Cross)) => Cross | (Marked(Circle), Marked(Circle), Marked(Circle)) => Circle | (_, _, _) => NoOne };
Когда мы называем Проверьте
Рекурсивная функция впервые, голова
будет первый элемент Уиннингры
то есть [0, 1, 2]
который является Список
Отказ Мы проходим голова
к Getwinner
Функция как Координы
аргумент вместе с Флаттетен
Отказ
Опять же, мы используем сопоставление шаблона с кортеж
Отказ Внутри кортеж
мы используем Список Способ для доступа к эквивалентным положениям
Координы Координаты на сплющенной доске
Список Отказ
Список Функция занимает Список
и номер и возвращает элемент списка на эту позицию.
Итак, наш кортеж
состоит из трех выигрышных координат нашей доски, которую мы обращались с использованием Список Отказ
Теперь мы можем соответствовать нашему кортеж
данные против шаблонов. Первые два шаблона проверяют, отмечены ли все три поля того же игрока. Если они, мы вернем победитель – Крест
или Круг
Отказ В противном случае мы вернемся Никто
Отказ
Посмотрим, что происходит внутри игра
функция. Это проверяет, есть ли все поля Отмечен
и возвращает логию.
let gameEnded = board => List.for_all( field => field == Marked(Circle) || field == Marked(Cross), board, );
Поскольку мы знаем, какие значения могут быть возвращены из наших функций помощника, давайте вернемся к нашему Проверьте
функция.
switch ( getWinner(flattenBoard, head), gameEnded(flattenBoard), tail, ) { | (Cross, _, _) => Winner(Cross) | (Circle, _, _) => Winner(Circle) | (_, true, []) => Draw | (_, false, []) => whosPlaying(gameState) | _ => check(tail) };
Наш сопоставление образец теперь может определить, закончилась ли игра в выигрыше или нарисовать. Если эти случаи не совпадают, мы перейдем к следующему случаю. Если это совпадает, игра будет продолжаться и Whosplaying
Функция будет называться, а другой игрок повернет.
let whosPlaying = (gameState: gameState) => switch (gameState) { | Playing(Cross) => Playing(Circle) | _ => Playing(Cross) };
В противном случае мы позвоним Проверьте
Функция рекурсивно с новой комбинацией выигрышных полей.
Вот и все. Теперь вы знаете, как наш код управляет игровыми логическими работами.
Это все люди!
Я надеюсь, что этот пост помог вам понять основные особенности этого многообещающего и еще разрабатываемого языка. Тем не менее, чтобы полностью оценить силу этого нового синтаксиса на вершине Ocaml, вам нужно начать строить свои собственные вещи. Теперь вы готовы это сделать.
Удачи!
Если вам понравилась эта статья, дайте ему несколько хлопов Отказ Я бы очень признателен за это, и больше людей смогут также увидеть этот пост.
Этот пост был Первоначально опубликовано в моем блоге.
Если у вас есть какие-либо вопросы, критика, наблюдения или советы по улучшению, не стесняйтесь написать комментарий ниже или добраться до меня через Twitter Отказ