如何处理Ramda中的错误

问题描述 投票:4回答:2

我对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
  • 从头部返回indexOf targetColumn
  • 如果索引是-1则抛出错误
javascript functional-programming ramda.js
2个回答
6
投票

正如customcommander所说,有一个很好的理由,这种抛出异常的方式并不容易通过函数编程来实现:它更难以推理。

“你的功能是什么?”

“一个号码。”

“总是?”

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

“它归还了什么?”

“好吧,它没有。”

“所以它返回一个数字或什么也没有?”

“大概吧。”

“嗯。”

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

为了解决这个问题,FP世界使用捕获失败概念的类型。你可能已经看过Maybe类型的谈话,它处理可能是null的值。另一个常见的是Either(有时Result),它有两个子类型,用于错误情况和成功的一个(分别为LeftRightEitherError以及OkResult。)在这些类型中,发现的第一个错误被捕获并通过无论谁需要它,而成功案例继续处理。 (还有Validation类型可以捕获错误列表。)

这些类型有很多实现。请参阅fantasy-land list以获取一些建议。

Ramda曾经拥有自己的这些类型,但已经放弃了维护它。 Folktale和Sanctuary是我们经常推荐的。但即使拉姆达的旧实施也应如此。这个版本使用Folktale's data.either,因为它是我所知道的更好,但后来版本的Folktale用Result取而代之。

下面的代码块显示了我如何使用Eithers来处理这种失败的概念,特别是我们如何使用R.sequenceEithers数组转换为持有数组的Either。如果输入包含任何Lefts,则输出只是Left。如果它是所有Rights,那么输出是包含它们的值数组的Right。有了这个,我们可以将所有列名称转换为捕获值或错误的Eithers,然后将它们组合成单个结果。

需要注意的是,这里没有例外。我们的功能将正确构成。失败的概念被封装在类型中。

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/[email protected]"></script>
<!--script src="//bundle.run/[email protected]"></script-->
<script src="//bundle.run/[email protected]"></script>
<script>
const {map, includes, sequence} = ramda
const Either = data_either;
const {Left, Right, of} = Either
</script>

对我而言,主要的一点是像goodIndicesbadIndices这样的价值观本身就很有用。如果我们想要用它们做更多的处理,我们可以简单地对它们进行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')]

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


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');

这样就可以清楚地分离问题,检查数据是否有效并进行所需的转换。

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

© www.soinside.com 2019 - 2024. All rights reserved.