如何处理Ramda中的错误

时间:2019-03-19 11:10:05

标签: javascript functional-programming ramda.js

我对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' ]

所以我需要:

  • 在targetColumns上映射
  • 从标头中返回targetColumn的index
  • 如果索引为-1会引发错误

3 个答案:

答案 0 :(得分:6)

正如customcommander所说,有充分的理由说明,通过函数式编程无法使这种引发异常的样式变得容易:这很难推理。

  

“您的函数返回什么?”

     

“一个数字。”

     

“总是吗?”

     

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

     

“返回的内容是什么?”

     

“嗯,不是。”

     

“那么它返回数字还是什么都没有?”

     

“我想是。”

     

“嗯。”

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

为此,FP世界使用了捕获故障概念的类型。您可能已经听说过Maybe类型,该类型处理可能是null的值。另一个常见的类型是Either(有时是Result),对于错误情况和成功情况,它具有两个子类型(对于Left,它具有两个子类型(分别为RightEither)或ErrorOk代表Result。)在这些类型中,捕获的第一个错误将被捕获并向下传递给任何需要它的人,而成功案例将继续进行处理。 (也有Validation个类型捕获错误列表。)

这些类型有许多实现。有关一些建议,请参见fantasy-land list

Ramda曾经有自己的一组此类类型,但已放弃维护它。我们经常为此推荐民间故事和圣所。但是,即使Ramda的旧实现也应该做到。这个版本使用Folktale's data.either,因为我更了解它,但是Folktale的更高版本将其替换为Result

以下代码块显示了如何使用Either来处理这种失败的概念,尤其是如何使用R.sequenceEithers的数组转换为{{1 }}持有一个数组。如果输入包含任何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))  //~> [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'))

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

答案 1 :(得分:0)

我将提出不同意见:Either是一种通过静态类型系统的圆孔敲打方钉的好方法。大量的认知开销使JavaScript的收益减少(缺乏正确性保证)。

如果有问题的代码需要快速(通过性能分析和记录的性能预算证明),则应以命令式的方式编写。

如果不需要快速(或者在执行以下操作时足够快),则只需检查您要迭代的对象是否是CSV标头的正确子集(或与之完全匹配)即可:

// We'll assume sorted data. You can check for array equality in other ways,
// but for arrays of primitives this will work.
if (`${header}` !== `${targetColumns}`) throw new Error('blah blah');

这将关注点与检查数据是否有效并进行所需的转换完全分开。

如果您担心的只是长度,那就检查一下,等等。

答案 2 :(得分:0)

如果抛出异常无法进行函数式编程,我想这会让我成为无政府主义者。这是如何以功能性方式编写函数的方法

const { map, switchCase } = require('rubico')

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

const ifHeaderDoesNotExist = h => header.indexOf(h) === -1

const getHeaderIndex = h => header.indexOf(h)

const throwHeaderNotFound = h => {
  throw new Error(`Target Column Name not found in CSV header column: ${h}`)
}

const getTargetColumnIndex = switchCase([
  ifHeaderDoesNotExist, throwHeaderNotFound,
  getHeaderIndex,
])

const main = () => {
  const targetColumns = ['CurrencyCode', 'Name']
  const targetColumnIndexes = map(getTargetColumnIndex)(targetColumns)
  console.log(targetColumnIndexes) // => [0, 1]
}

main()

地图就像拉姆达的地图

您可以将上述switchCase视为

const getTargetColumnIndex = x =>
  ifHeaderDoesNotExist(x) ? throwHeaderNotFound(x) : getHeaderIndex(x)