Автор оригинала: FreeCodeCamp Community Member.
Эта статья ориентирована с аудиторией, выпускной из функциональных библиотек, таких как Рамда
Использование алгебраических типов данных. Мы используем отличные Crocks Библиотека для наших объявлений и помощников, хотя эти концепции могут обращаться к другим. Мы будем сосредоточены на демонстрации практических приложений и паттернов, не углубляясь в много теории.
Безопасно выполнять опасные функции
Допустим, у нас есть ситуация, когда мы хотим использовать функцию под названием темнеть
из сторонней библиотеки. темнеть
принимает множитель, цвет и возвращает более темный оттенок этого цвета.
// darken :: Number -> String -> String darken(0.1)("gray") //=> "#343434"
Довольно удобно для наших потребностей CSS. Но оказывается, что функция не такая невинная, как кажется. темнеть
бросает ошибки, когда он получает неожиданные аргументы!
darken(0.1)(null) => // Error: Passed an incorrect argument to a color function, please pass a string representation of a color.
Это, конечно, очень полезно для отладки – но мы не хотели бы взорвать нашу приложение только потому, что мы не могли вывести цвет. Вот где Trucatch
приходит к спасению.
import { darken } from "polished" import { tryCatch, compose, either, constant, identity, curry } from "crocks" // safeDarken :: Number -> String -> String const safeDarken = curry(n => compose( either(constant("inherit"), identity), tryCatch(darken(n)) ) )
Trucatch
Выполняет предоставленную функцию в блоке Try-Catch и возвращает тип суммы, называемого Результат
Отказ По его сути, тип суммы в основном является «или» тип. Это означает, что Результат
может быть либо Хорошо
Если операция успешна или Ошибка
. в случае сбоев. Другие примеры типов сумм включают Может быть
, Либо
, Async
и так далее. либо
Бесплатный помощник Point нарушает значение из Результат
коробка и возвращает CSS по умолчанию наследство
Если бы все пошло на юг или затемненный цвет, если все прошло хорошо.
safeDarken(0.5)(null) //=> inherit safeDarken(0.25)('green') //=> '#004d00'
Принудительные типы, используя, возможно, помощники
С JavaScript мы часто бегаем в случаях, когда наши функции взорваются, потому что мы ожидаем конкретного типа данных, но вместо этого получаем другой. Crocks
предоставляет безопасный
, Посоветочная
и SafeLift
Функции, которые позволяют нам выполнить код более предсказуемо, используя Может быть
тип. Давайте посмотрим на способ преобразования CAMELCASED Строки в заголовок.
import { safeAfter, safeLift, isArray, isString, map, compose, option } from "crocks" // match :: Regex -> String -> Maybe [String] const match = regex => safeAfter(isArray, str => str.match(regex)) // join :: String -> [String] -> String const join = separator => array => array.join(separator) // upperFirst :: String -> String const upperFirst = x => x.charAt(0) .toUpperCase() .concat(x.slice(1).toLowerCase()) // uncamelize :: String -> Maybe String const uncamelize = safeLift(isString, compose( option(""), map(compose(join(" "), map(upperFirst))), match(/(((^[a-z]|[A-Z])[a-z]*)|[0-9]+)/g), )) uncamelize("rockTheCamel") //=> Just "Rock The Camel" uncamelize({}) //=> Nothing
Мы создали функцию помощника Матч
что использует Посоветочная
утюгнуть String.prototype.match
поведение возврата undefined
В случае, если нет матчей. ИСАРАЙ
предикат гарантирует, что мы получим Ничего
Если нет найденных совпадений, а A Просто [строка]
в случае матчей. Посоветочная
отлично подходит для выполнения существующих или сторонних функций надежным безопасным способом.
(Совет: Buffter
очень хорошо работает с Ramda
Функции, которые возвращают A | undefined
.)
Наше безвременно?
Функция выполняется с SafeLift (IsString)
Это означает, что он только будет выполнен, когда вход возвращает true для isString
предикат.
В дополнение к этому, Crocks также предоставляет опора
и Progpath
помощники, которые позволяют выбрать недвижимость от Объект
S и Массив
с.
import { prop, propPath, map, compose } from "crocks" const goodObject = { name: "Bob", bankBalance: 7999, address: { city: "Auckland", country: "New Zealand", }, } prop("name")(goodObject) //=> Just "Bob" propPath(["address", "city"])(goodObject) //=> Just "Auckland" // getBankBalance :: Object -> Maybe String const getBankBalance = compose( map(balance => balance.toFixed(2)), prop("bankBalance") ) getBankBalance(goodObject) //=> Just '7999.00' getBankBalance({}) //=> Nothing
Это отлично, особенно если мы имеем дело с данными из побочных эффектов, которые не находятся под нашим контролем, как ответы API. Но что произойдет, если разработчики API внезапно решили обрабатывать форматирование в их конце?
const badObject = { name: "Rambo", bankBalance: "100.00", address: { city: "Hope", country: "USA" } } getBankBalance(badObject) // TypeError: balance.toFixed is not a function :-(
Ошибки времени выполнения! Мы пытались вызвать Тофикс
Способ на строке, который на самом деле не существует. Нам нужно убедиться, что Банкбаланс
действительно является Номер
прежде чем мы вызовем Тофикс
в теме. Давайте попробуем решить это с нашими безопасный
помощник.
import { prop, propPath, compose, map, chain, safe, isNumber } from "crocks" // getBankBalance :: Object -> Maybe String const getBankBalance = compose( map(balance => balance.toFixed(2)), chain(safe(isNumber)), prop("bankBalance") ) getBankBalance(badObject) //=> Nothing getBankBalance(goodObject) //=> Just '7999.00'
Мы проводят результаты опора
Функция для нашего Безопасный (Isnumber)
Функция, которая также возвращает Может быть
в зависимости от того, будь результат опора
удовлетворяет предикату. Трубопровод выше гарантирует, что последний карта
который содержит Тофикс
будет называться только когда Банкбаланс
это Номер
Отказ
Если вы собираетесь иметь дело с большим количеством подобных случаев, это имеет смысл извлечь этот шаблон в качестве помощника:
import { Maybe, ifElse, prop, chain, curry, compose, isNumber } from "crocks" const { of, zero } = Maybe // propIf :: (a -> Boolean) -> [String | Number] -> Maybe a const propIf = curry((fn, path) => compose( chain(ifElse(fn, of, zero)), prop(path) ) ) propIf(isNumber, "age", goodObject) //=> Just 7999 propIf(isNumber, "age", badObject) //=> Nothing
Использование заявок, чтобы сохранить функции чистым
Часто мы находимся в ситуациях, когда мы хотели бы использовать существующую функцию со значениями, завернуты в контейнер. Давайте попробуем разработать безопасную Добавить
Функция, которая позволяет только номерам, используя концепции из предыдущего раздела. Вот наша первая попытка.
import { Maybe, safe, isNumber } from "crocks" // safeNumber :: a -> Maybe a const safeNumber = safe(isNumber) // add :: a -> b -> Maybe Number const add = (a, b) => { const maybeA = safeNumber(a) const maybeB = safeNumber(b) return maybeA.chain( valA => maybeB.map(valB => valA + valB) ) } add(1, 2) //=> Just 3 add(1, {}) //=> Nothing
Это делает именно то, что нам нужно, но наш Добавить
Функция больше не просто A + B
Отказ Это должно сначала поднять наши ценности в Может быть
S, затем достичь их для доступа к значениям, а затем вернуть результат. Нам нужно найти способ сохранить основные функциональные возможности нашего Добавить
Функция, позволяющая ему работать со значениями, содержащимися в объявлении! Вот где пригодны прикладные функторы.
Применительный функтор просто как обычный функтор, но вместе с карта
Это также реализует два дополнительных метода:
of :: Applicative f => a -> f a
Это совершенно тупой конструктор, и поднимает любое значение, которое вы придаете в наш тип данных. Это также называется
чистый
на других языках.
Maybe.of(null) //=> Just null Const.of(42) //=> Const 42
И вот где все деньги – это AP
Метод:
ap :: Apply f => f a ~> f (a -> b) -> f b
Подпись выглядит очень похоже на карта
, с единственной разницей, чем наше A -> B
Функция также завернута в F
Отказ Давайте посмотрим на это в действии.
import { Maybe, safe, isNumber } from "crocks" // safeNumber :: a -> Maybe a const safeNumber = safe(isNumber) // add :: a -> b -> c const add = a => b => a + b // add :: a -> b -> Maybe Number const safeAdd = (a, b) => Maybe.of(add) .ap(safeNumber(a)) .ap(safeNumber(b)) safeAdd(1, 2) //=> Just 3 safeAdd(1, "danger") //=> Nothing
Сначала мы поднимаем наши каррики Добавить
функция в Может быть
, а затем подать заявку Может быть,
и Может быть, б
к этому. Мы использовали карта
Пока чтобы получить доступ к значению внутри контейнера и AP
ничем не отличается Внутренне, это карта
на SafEnumber (A)
Для доступа к А
и применяет его к Добавить
Отказ Это приводит к Может быть
который содержит частично примененное Добавить
Отказ Мы повторяем тот же процесс с SafeNumber (B)
выполнить наши Добавить
Функция, в результате чего Просто
результата, если оба А
и B
действительны или Ничего
иначе.
Клоки также дают нам Лифт2
и лифтн
помощники, чтобы выразить ту же концепцию в точке. Дривиальный пример следует:
liftA2(add)(Maybe(1))(Maybe(2)) //=> Just 3
Мы будем использовать этот хелпер широко в разделе Выражение параллелизма
Отказ
Совет: так как мы заметили, что AP
использует карта
Для доступа к значениям мы можем проделать крутые вещи, такие как генерация декартового продукта, когда два списка.
import { List, Maybe, Pair, liftA2 } from "crocks" const names = List(["Henry", "George", "Bono"]) const hobbies = List(["Music", "Football"]) List(name => hobby => Pair(name, hobby)) .ap(names) .ap(hobbies) // => List [ Pair( "Henry", "Music" ), Pair( "Henry", "Football" ), // Pair( "George", "Music" ), Pair( "George", "Football" ), // Pair( "Bono", "Music" ), Pair( "Bono", "Football" ) ]
Использование Async для предсказуемой обработки ошибок
Crocks
предоставляет Async
Тип данных, который позволяет создавать ленивые асинхронные вычисления. Чтобы узнать больше об этом, вы можете обратиться к обширной официальной документации здесь Отказ Этот раздел стремится предоставить примеры того, как мы можем использовать Async
Чтобы улучшить качество отчетов о нашей ошибки и сделать наш код устойчивости.
Часто мы бегаем в случаях, когда мы хотим сделать вызовы API, которые зависят друг над другом. Здесь GetUser
Конечная точка возвращает пользовательский объект из GitHub, а ответ содержит много встроенных URL для репозиториев, звезд, фаворитов и так далее. Мы увидим, как мы можем разработать этот случай с использованием Async
Отказ
import { Async, prop, compose, chain, safe, isString, maybeToAsync } from "crocks" const { fromPromise } = Async // userPromise :: String -> Promise User Error const userPromise = user => fetch(`https://api.github.com/users/${user}`) .then(res => res.json()) // resourcePromise :: String -> Promise Resource Error const resourcePromise = url => fetch(url) .then(res => res.json()) // getUser :: String -> Async User Error const getUser = compose( chain(fromPromise(userPromise)), maybeToAsync('getUser expects a string'), safe(isString) ) // getResource :: String -> Object -> Async Resource Error const getResource = path => user => { if (!isString(path)) { return Async.Rejected("getResource expects a string") } return maybeToAsync("Error: Malformed user response received", prop(path, user)) .chain(fromPromise(resourcePromise)) } // logError :: (...a) -> IO() const logError = (...args) => console.log("Error: ", ...args) // logResponse :: (...a) -> IO() const logSuccess = (...args) => console.log("Success: ", ...args) getUser("octocat") .chain(getResource("repos_url")) .fork(logError, logSuccess) //=> Success: { ...response } getUser(null) .chain(getResource("repos_url")) .fork(logError, logSuccess) //=> Error: The user must be as string getUser("octocat") .chain(getResource(null)) .fork(logError, logSuccess) //=> Error: getResource expects a string getUser("octocat") .chain(getResource("unknown_path_here")) .fork(logError, logSuccess) //=> Error: Malformed user response received
Использование MaybetoaAsync
Преобразование позволяет нам использовать все функции безопасности, которые мы получаем от использования Может быть
и принести их к нашему Async
потоки. Теперь мы можем открыть ввод и другие ошибки как часть нашего Async
потоки.
Используя моноиды эффективно
Мы уже использовали моноиды, когда мы выполняем такие операции, как Строка
/ Массив
Согласие и количество добавления в родном JavaScript. Это просто тип данных, который предлагает нам следующие методы.
concat :: Monoid m => m a -> m a -> m a
Concat
Позволяет нам объединить два моноида одного типа вместе с предварительно указанной операцией.
empty :: Monoid m => () => m a
Пустой
Метод предоставляет нам элемент идентичности, что когда Concat
с другими моноидами одного типа, вернет один и тот же элемент. Вот о чем я говорю.
import { Sum } from "crocks" Sum.empty() //=> Sum 0 Sum(10) .concat(Sum.empty()) //=> Sum 10 Sum(10) .concat(Sum(32)) //=> Sum 42
Само по себе это не выглядит очень полезным, но Crocks
предоставляет несколько дополнительных моноидов вместе с помощниками mconcat
, измеритель
, mconcatmap
и MreduceMap
Отказ
import { Sum, mconcat, mreduce, mconcatMap, mreduceMap } from "crocks" const array = [1, 3, 5, 7, 9] const inc = x => x + 1 mconcat(Sum, array) //=> Sum 25 mreduce(Sum, array) //=> 25 mconcatMap(Sum, inc, array) //=> Sum 30 mreduceMap(Sum, inc, array) //=> 30
mconcat
и измеритель
Методы возьмите моноид и список элементов для работы с и применяют Concat
ко всем их элементам. Единственная разница между ними в том, что mconcat
Возвращает экземпляр моноида во время измеритель
Возвращает необработанное значение. mconcatmap
и MreduceMap
Помощники работают так же, за исключением того, что они принимают дополнительную функцию, которая используется для отображения на каждом элементе перед вызовом Concat
Отказ
Давайте посмотрим на другой пример моноида из Crocks
, Первый
Моноид. При объединении, Первый
всегда будет возвращать первое, не пустое значение.
import { First, Maybe } from "crocks" First(Maybe.zero()) .concat(First(Maybe.zero())) .concat(First(Maybe.of(5))) //=> First (Just 5) First(Maybe.of(5)) .concat(First(Maybe.zero())) .concat(First(Maybe.of(10))) //=> First (Just 5)
Используя мощность Первый
, давайте попробуем создать функцию, которая пытается получить первое доступное свойство на объекте.
import { curry, First, mreduceMap, flip, prop, compose } from "crocks" /** tryProps -> a -> [String] -> Object -> b */ const tryProps = flip(object => mreduceMap( First, flip(prop, object), ) ) const a = { x: 5, z: 10, m: 15, g: 12 } tryProps(["a", "y", "b", "g"], a) //=> Just 12 tryProps(["a", "b", "c"], a) //=> Nothing tryProps(["a", "z", "c"], a) //=> Just 10
Довольно аккуратно! Вот еще один пример, который пытается создать бесперебойное устройство при предоставленном различных типах значений.
import { applyTo, mreduceMap, isString, isEmpty, mreduce, First, not, isNumber, chain compose, safe, and, constant, Maybe, map, equals, ifElse, isBoolean, option, } from "crocks"; // isDate :: a -> Boolean const isDate = x => x instanceof Date; // lte :: Number -> Number -> Boolean const lte = x => y => y <= x; // formatBoolean :: a -> Maybe String const formatBoolean = compose( map(ifElse(equals(true), constant("Yes"), constant("No"))), safe(isBoolean) ); // formatNumber :: a -> Maybe String const formatNumber = compose( map(n => n.toFixed(2)), safe(isNumber) ); // formatPercentage :: a -> Maybe String const formatPercentage = compose( map(n => n + "%"), safe(and(isNumber, lte(100))) ); // formatDate :: a -> Maybe String const formatDate = compose( map(d => d.toISOString().slice(0, 10)), safe(isDate) ); // formatString :: a -> Maybe String const formatString = safe(isString) // autoFormat :: a -> Maybe String const autoFormat = value => mreduceMap(First, applyTo(value), [ formatBoolean, formatPercentage, formatNumber, formatDate, formatString ]); autoFormat(true) //=> Just "Yes" autoFormat(10.02) //=> Just "10%" autoFormat(255) //=> Just "255.00" autoFormat(new Date()) //=> Just "2019-01-14" autoFormat("YOLO!") //=> Just "YOLO!" autoFormat(null) //=> Nothing
Выражая параллелизм в точке
Мы могли бы столкнуться с случаями, где хочется выполнять несколько операций на одном количестве данных и каким-то образом объединять результаты. Crocks
Предоставляет нам два метода для достижения этого. Первый шаблон использует типы продуктов Пара
и Кортеж
Отказ Давайте посмотрим на небольшой пример, где у нас есть объект, который выглядит так:
{ ids: [11233, 12351, 16312], rejections: [11233] }
Мы хотели бы написать функцию, которая принимает этот объект и возвращает Массив
IDS
исключая отклоненные. Наша первая попытка в родном JavaScript будет выглядеть так:
const getIds = (object) => object.ids.filter(x => object.rejections.includes(x))
Это, конечно, работает, но взорвалось бы на случай, если одно из свойств невыразится или не определено. Давайте сделаем Гетидс
вернуть Может быть
вместо. Мы используем Велочка
Помощник, который принимает две функции, управляет его на том же входе и возвращает Пара
результатов.
import { prop, compose, equals, filter, fanout, merge, liftA2 } from "crocks" /** * object :: Record * Record :: { * ids: [Number] * rejection: [Number] * } **/ const object = { ids: [11233, 12351, 16312], rejections: [11233] } // excludes :: [a] -> [b] -> Boolean const excludes = x => y => !x.includes(y) // difference :: [a] -> [a] -> [a] const difference = compose(filter, excludes) // getIds :: Record -> Maybe [Number] const getIds = compose( merge(liftA2(difference)), fanout(prop("rejections"), prop("ids")) ) getIds(object) //=> Just [ 12351, 16312 ] getIds({ something: [], else: 5 }) //=> Nothing
Одним из основных преимуществ использования подхода PointFree является то, что он побуждает нас нарушать нашу логику на меньшие кусочки. Теперь у нас есть многоразовый помощник Разница
(с Lifta2
, как видно ранее) что мы можем использовать для слияние
Оба половины Пара
все вместе.
Второй метод будет использовать сходятся
Комбинатор для достижения аналогичных результатов. сходятся
принимает три функции и входное значение. Затем он применяет вход во вторую и третью функцию и трубы результаты как первых. Давайте использовать его для создания функции, которая нормализует Массив
объектов на основе их ID
с. Мы будем использовать Назначить
Моноид, который позволяет нам объединять объекты.
import { mreduceMap, applyTo, option, identity, objOf, map, converge, compose, Assign, isString, constant } from "crocks" import propIf from "./propIf" // normalize :: String -> [Object] -> Object const normalize = mreduceMap( Assign, converge( applyTo, identity, compose( option(constant({})), map(objOf), propIf(isString, "id") ) ) ) normalize([{ id: "1", name: "Kerninghan" }, { id: "2", name: "Stallman" }]) //=> { 1: { id: '1', name: 'Kerninghan' }, 2: { id: '2', name: 'Stallman' } } normalize([{ id: null}, { id: "1", name: "Knuth" }, { totally: "unexpected" }]) //=> { 1: { id: '1', name: 'Knuth' } }
Использование траверса и последовательности, чтобы обеспечить данное здравомыслие
Мы видели, как использовать Может быть
И друзья, чтобы гарантировать, что мы всегда работаем с типовыми, которые мы ожидаем. Но что происходит, когда мы работаем с типом, который содержит другие значения, как Массив
или Список
Например? Давайте посмотрим на простую функцию, которая дает нам общую длину всех строк, содержащихся в Массив
Отказ
import { compose, safe, isArray, reduce, map } from "crocks" // sum :: [Number] -> Number const sum = reduce((a, b) => a + b, 0) // length :: [a] -> Number const length = x => x.length; // totalLength :: [String] -> Maybe Number const totalLength = compose( map(sum), map(map(length)), safe(isArray) ) const goodInput = ["is", "this", "the", "real", "life?"] totalLength(goodInput) //=> Just 18 const badInput = { message: "muhuhahhahahaha!"} totalLength(badInput) //=> Nothing
Отлично. Мы убедились, что наша функция всегда возвращает Ничего
Если это не получает Массив
Отказ Этого достаточно, хотя?
totalLength(["stairway", "to", undefined]) //=> TypeError: x is undefined
Не совсем. Наша функция не гарантирует, что содержимое списка не будет проводить сюрпризы. Один из способов решить, это было бы определить Девелофикация
Функция, которая работает только с строками:
// safeLength :: a -> Maybe Number const safeLength = safeLift(isString, length)
Если мы используем Девелофикация
вместо Длина
Как наша функция отображения, мы получим бы [Может быть, номер]
вместо [Номер]
И мы не можем использовать наш сумма
функция больше. Вот где последовательность
приходит в удобное.
import { sequence, Maybe, Identity } from "crocks" sequence(Maybe, Identity(Maybe.of(1))) //=> Just Identity 1 sequence(Array, Identity([1,2,3])) //=> [ Identity 1, Identity 2, Identity 3 ] sequence(Maybe, [Maybe.of(4), Maybe.of(2)]) //=> Just [ 4, 2 ] sequence(Maybe, [Maybe.of(4), Maybe.zero()]) //=> Nothing
последовательность
Помогает поменять внутренний тип с внешним типом при выполнении определенного эффект
Учитывая, что внутренний тип является примером. последовательность
на Личность
довольно тупой – это просто карта
S За внутренним типом и возвращает содержимое, завернутое в Личность
контейнер. Для Список
и Массив
, последовательность
использует Уменьшить
В списке, чтобы объединить его содержимое, используя AP
и Concat
Отказ Давайте посмотрим на это в действии в нашем рекакторе Только длина
выполнение.
// totalLength :: [String] -> Maybe Number const totalLength = compose( map(sum), chain(sequence(Maybe)), map(map(safeLength)), safe(isArray) ) const goodString = ["is", "this", "the", "real", "life?"] totalLength(goodString) //=> Just 18 totalLength(["stairway", "to", undefined]) //=> Nothing
Большой! Мы построили полностью пуленепробиваемый Только длина
Отказ Этот шаблон отображения над чем-то из A -> M B
а затем используете последовательность
настолько распространены, что у нас есть другой помощник по имени Traverse
который выполняет обе операции вместе. Давайте посмотрим, как мы можем использовать Traverse
Вместо последовательности в приведенном выше примере.
// totalLengthT :: [String] -> Maybe Number const totalLengthT = compose( map(sum), chain(traverse(Maybe, safeLength)), safe(isArray) )
Там! Это работает точно так же. Если мы думаем об этом, наше последовательность
Оператор в основном Traverse
, с личность
как функция картирования.
Примечание. Поскольку мы не можем сделать вывод внутреннего типа, используя JavaScript, мы должны явно предоставить конструктор типа в качестве первого аргумента Traverse
и последовательность
Отказ
Легко видеть, как последовательность
и Traverse
неоценимы для проверки данных. Давайте попробуем создать универсальный валидатор, который принимает схему и проверяет входной объект. Мы будем использовать Результат
Тип, который принимает полугруппу на левой стороне, которая позволяет нам собирать ошибки. Полугруппа похожа на моноид, и он определяет Concat
Метод – но в отличие от моноида, он не требует наличия Пустой
метод. Мы также представляем функцию трансформации MaybetOrosult
Ниже, это поможет нам взаимодействовать между Может быть
и Результат
Отказ
import { Result, isString, map, merge, constant, bimap, flip, propOr, identity, toPairs, safe, maybeToResult, traverse, and, isNumber, compose } from "crocks" // length :: [a] -> Int const length = x => x.length // gte :: Number -> a -> Result String a const gte = x => y => y >= x // lte :: Number -> a -> Result String a const lte = x => y => y <= x // isValidName :: a -> Result String a const isValidName = compose( maybeToResult("expected a string less than 20 characters"), safe(and(compose(lte(20), length), isString)) ) // isAdult :: a -> Result String a const isAdult = compose( maybeToResult("expected a value greater than 18"), safe(and(isNumber, gte(18))) ) /** * schema :: Schema * Schema :: { * [string]: a -> Result String a * } * */ const schema = { name: isValidName, age: isAdult, } // makeValidator :: Schema -> Object -> Result [String] Object const makeValidator = flip(object => compose( map(constant(object)), traverse(Result, merge((key, validator) => compose( bimap(error => [`${key}: ${error}`], identity), validator, propOr(undefined, key) )(object) ) ), toPairs ) ) // validate :: Object -> Result [String] Object const validate = makeValidator(schema) validate(({ name: "Car", age: 21, })) //=> Ok { name: "Car", age: 21 } validate(({ name: 7, age: "Old", })) //=> Err [ "name: expected a string less than 20 characters", "age: expected a value greater than 18" ]
Так как мы перевернули Макиядитель
Функция, чтобы сделать больше подходящей для карри, нашего составить
Цепь получает схему, которую мы должны подтвердить против первого. Сначала мы нарушаем схему в ключевое значение Пара
S и пропустите значение каждого свойства до соответствующей функции проверки. В случае неудачи функции, мы используем Bimap
Чтобы отобразить ошибку, добавьте еще несколько информации и верните ее как синглтон Массив
Отказ Traverse
будет тогда Concat
Все ошибки, если они существуют или верните исходный объект, если это действительно. Мы могли бы также вернуть Строка
вместо Массив
, но Массив
чувствует гораздо приятнее.
Благодаря Ян Хофманн-Хикс, Sinisa Louc. а также Дейл Фрэнсис Для их входов на этом посте.