Как говорит customcommander, есть веская причина, по которой этот стиль создания исключений не облегчается функциональным программированием: о нем гораздо сложнее рассуждать.
«Что возвращает функция?»
«Число».
«Всегда?»
«Да, ...ну, если только он не выдает исключение. "
" Что он возвращает? "
" Ну, это не так. "
" Таким образом, он возвращает число или ничеговообще? "
" Полагаю, что так. "
" Хммм. "
Одной из самых распространенных операций в функциональном программировании является составление двух функций.Но это работает, только если выходные данные одной функции совпадают с входными данными ее преемника.Это сложно, если первый может выдать исключение.
Чтобы справиться с этим, мир FP использует типы, которые фиксируют понятия неудачи.Возможно, вы видели разговор о типе Maybe
, который обрабатывает значения, которые могут быть null
.Другим распространенным является Either
(иногда Result
), который имеет два подтипа, для случая ошибки и успешного (соответственно Left
и Right
для Either
или Error
и Ok
для Result
.) В этих типах первая найденная ошибка перехватывается и передается по линии тому, кто в ней нуждается, в то время как успешный пример продолжает обрабатываться.(Существуют также Validation
типы, которые фиксируют список ошибок.)
Существует много реализаций этих типов.См. Список предложений fantasy-land .
У Рамды был свой набор этих типов, но он отказался от его обслуживания.Мы часто рекомендуем для этого народные сказки и святилище.Но даже старая реализация Рамды должна подойти.Эта версия использует Folktale's data.either
, так как я знаю это лучше, но более поздние версии Folktale заменяют его на Result
.
Следующий блок кода показывает, как я мог бы использовать Either
s для обработки этого понятия отказа, особенно как мы можем использовать R.sequence
для преобразования массива Eithers
вEither
держит массив.Если на входе есть какие-либо значения Left
с, то на выходе будет просто Left
.Если это все Right
с, то на выходе получается Right
, содержащий массив их значений.Благодаря этому мы можем преобразовать все имена наших столбцов в Either
s, которые фиксируют значение или ошибку, но затем объединяют их в один результат.
Следует отметить, что здесь не выдается никаких исключений.Наши функции составятся правильно.Понятие ошибки заключено в типе.
const header = [ 'CurrencyCode', 'Name', 'CountryCode' ]
const getIndices = (header) => (targetColumns) =>
map((h, idx = header.indexOf(h)) => idx > -1
? Right(idx)
: Left(`Target Column Name not found in CSV header column: ${h}`)
)(targetColumns)
const getTargetIndices = getIndices(header)
// ----------
const goodIndices = getTargetIndices(['CurrencyCode', 'Name'])
console.log('============================================')
console.log(map(i => i.toString(), goodIndices)) //~> [Right(0), Right(1)]
console.log(map(i => i.isLeft, goodIndices)) //~> [false, false]
console.log(map(i => i.isRight, goodIndices)) //~> [true, true]
console.log(map(i => i.value, goodIndices)) //~> [0, 1]
console.log('--------------------------------------------')
const allGoods = sequence(of, goodIndices)
console.log(allGoods.toString()) //~> Right([0, 1])
console.log(allGoods.isLeft) //~> false
console.log(allGoods.isRight) //~> true
console.log(allGoods.value) //~> [0, 1]
console.log('============================================')
//----------
const badIndices = getTargetIndices(['CurrencyCode', 'Name', 'FooBar'])
console.log('============================================')
console.log(map(i => i.toString(), badIndices)) //~> [Right(0), Right(1), Left('Target Column Name not found in CSV header column: FooBar')
console.log(map(i => i.isLeft, badIndices)) //~> [false, false, true]
console.log(map(i => i.isRight, badIndices)) //~> [true, true, false]
console.log(map(i => i.value, badIndices)) //~> [0, 1, 'Target Column Name not found in CSV header column: FooBar']
console.log('--------------------------------------------')
const allBads = sequence(of, badIndices)
console.log(allBads.toString()) //~> Left('Target Column Name not found in CSV header column: FooBar')
console.log(allBads.isLeft) //~> true
console.log(allBads.isRight) //~> false
console.log(allBads.value) //~> 'Target Column Name not found in CSV header column: FooBar'
console.log('============================================')
.as-console-wrapper {height: 100% !important}
<script src="//bundle.run/ramda@0.26.1"></script>
<!--script src="//bundle.run/ramda-fantasy@0.8.0"></script-->
<script src="//bundle.run/data.either@1.5.2"></script>
<script>
const {map, includes, sequence} = ramda
const Either = data_either;
const {Left, Right, of} = Either
</script>
Главное для меня то, что такие значения, как goodIndices
и badIndices
, полезны сами по себе.Если мы хотим сделать больше обработки с ними, мы можем просто map
над ними.Например, обратите внимание, что
map(n => n * n, Right(5)) //=> Right(25)
map(n => n * n, Left('oops')) //=> Left('oops'))
Таким образом, наши ошибки оставлены в покое, и наши успехи обрабатываются далее.
map(map(n => n + 1), badIndices)
//=> [Right(1), Right(2), Left('Target Column Name not found in CSV header column: FooBar')]
И это то, что эти типы все о.