首页 > 解决方案 > 如何处理 Ramda 中的错误

问题描述

我对 Ramda 和函数式编程非常陌生,并试图用 Ramda 重写脚本,但不确定如何以干净的方式处理 Ramda 的错误。这就是我所拥有的,是否有人对如何使用 Ramda 以功能方式重写它有任何指示?

const targetColumnIndexes = targetColumns.map(h => {
    if (header.indexOf(h) == -1) {
      throw new Error(`Target Column Name not found in CSV header column: ${h}`)
    }
    return header.indexOf(h)
  })

作为参考,这些是headertargetColumns

const header = [ 'CurrencyCode', 'Name', 'CountryCode' ]
const targetColumns = [ 'CurrencyCode', 'Name' ]

所以我需要:

标签: javascriptfunctional-programmingramda.js

解决方案


正如 customcommander 所说,这种抛出异常的方式在函数式编程中并不容易,这是有充分理由的:它更难推理。

“你的函数返回什么?”

“一个号码。”

“总是?”

“是的,……好吧,除非它抛出异常。”

“那它返回什么?”

“好吧,它没有。”

“所以它返回一个数字还是什么都不返回?”

“大概吧。”

“嗯。”

函数式编程中最常见的操作之一是组合两个函数。但这只有在一个函数的输出与其后继函数的输入匹配时才有效。如果第一个可能抛出异常,这很困难。

为了解决这个问题,FP 世界使用捕获失败概念的类型。您可能已经看到有关Maybe类型的讨论,它处理的值可能是null. 另一个常见的类型是Either(sometimes Result),它有两个子类型,用于错误情况和成功情况(分别为for orLeft和for 。)在这些类型中,找到的第一个错误被捕获并传递给需要它的人,而成功案例还在继续处理中。(还有一些类型可以捕获错误列表。)RightEitherErrorOkResultValidation

这些类型有很多实现。有关一些建议,请参阅幻想世界列表

Ramda 曾经拥有自己的一组这些类型,但已放弃维护它。Folktale和Sanctuary是我们经常推荐的。但即使是 Ramda 的旧实现也应该这样做。这个版本使用Folktale 的data.either,因为它是我更了解的版本,但后来的 Folktale 版本将其替换为Result.

下面的代码块展示了我如何使用Eithers 来处理这种失败的概念,尤其是我们如何R.sequence将一个数组转换Eithers为一个Either持有数组。如果输入包含任何Lefts,则输出只是 a Left。如果都是Rights,那么输出是一个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')]

这就是这些类型的全部内容。


推荐阅读