正如customcommander所说,函数式编程中不易实现抛出异常的原因是存在更难以理解的问题。
“你的函数返回什么?”
“一个数字。”
“总是这样吗?”
“是的……除非它抛出异常。”
“那么它返回什么?”
“嗯,它不返回任何东西。”
“所以它返回一个数字还是什么都没有返回?”
“我想是的。”
“嗯。”
函数式编程中最常见的操作之一是组合两个函数。但是,如果第一个函数可能会抛出异常,则很难进行推理。
为了解决这个问题,函数式编程使用捕获失败概念的类型。您可能已经看到过Maybe类型的讨论,该类型处理可能为null的值。另一个常见的类型是Either(有时称为Result),它有两个子类型,分别用于错误情况和成功情况(对于Either分别为Left和Right,对于Result分别为Error和Ok)。在这些类型中,第一个找到的错误被捕获并传递给需要它的人,而成功情况则继续处理。(还有Validation类型可以捕获错误列表。)
这些类型有许多实现方式。请参考
fantasy-land list以获取一些建议。
Ramda曾经拥有自己的这些类型,但已经停止维护。我们通常推荐使用Folktale和Sanctuary。但是,即使是Ramda的旧实现也应该可以胜任。此版本使用
Folktale的data.either
,因为我更熟悉它,但后来的Folktale版本将其替换为
Result
。
以下代码块显示了如何使用
Either
来处理失败的概念,特别是我们如何使用
R.sequence
将
Eithers
数组转换为包含数组的
Either
。如果输入包含任何
Left
,则输出只是一个
Left
。如果全部都是
Right
,则输出是一个包含它们值的
Right
数组。通过这种方式,我们可以将所有列名转换为捕获值或错误的
Either
,然后将它们合并成单个结果。
需要注意的是,这里没有抛出任何异常。我们的函数将正确组合。失败的概念封装在类型中。
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))
console.log(map(i => i.isLeft, goodIndices))
console.log(map(i => i.isRight, goodIndices))
console.log(map(i => i.value, goodIndices))
console.log('--------------------------------------------')
const allGoods = sequence(of, goodIndices)
console.log(allGoods.toString())
console.log(allGoods.isLeft)
console.log(allGoods.isRight)
console.log(allGoods.value)
console.log('============================================')
const badIndices = getTargetIndices(['CurrencyCode', 'Name', 'FooBar'])
console.log('============================================')
console.log(map(i => i.toString(), badIndices))
console.log(map(i => i.isLeft, badIndices))
console.log(map(i => i.isRight, badIndices))
console.log(map(i => i.value, badIndices))
console.log('--------------------------------------------')
const allBads = sequence(of, badIndices)
console.log(allBads.toString())
console.log(allBads.isLeft)
console.log(allBads.isRight)
console.log(allBads.value)
console.log('============================================')
.as-console-wrapper {height: 100% !important}
<script src="//bundle.run/ramda@0.26.1"></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')]
而这就是这些类型的全部内容。