我对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)
})
作为参考,这些是header
和targetColumns
的值
const header = [ 'CurrencyCode', 'Name', 'CountryCode' ]
const targetColumns = [ 'CurrencyCode', 'Name' ]
所以我需要:
-1
会引发错误答案 0 :(得分:6)
正如customcommander所说,有充分的理由说明,通过函数式编程无法使这种引发异常的样式变得容易:这很难推理。
“您的函数返回什么?”
“一个数字。”
“总是吗?”
“是的,好吧,除非抛出异常。”
“返回的内容是什么?”
“嗯,不是。”
“那么它返回数字还是什么都没有?”
“我想是。”
“嗯。”
函数式编程中最常见的操作之一是组成两个函数。但这仅在一个函数的输出与其后继输入匹配时才有效。如果第一个可能引发异常,这将很困难。
为此,FP世界使用了捕获故障概念的类型。您可能已经听说过Maybe
类型,该类型处理可能是null
的值。另一个常见的类型是Either
(有时是Result
),对于错误情况和成功情况,它具有两个子类型(对于Left
,它具有两个子类型(分别为Right
和Either
)或Error
和Ok
代表Result
。)在这些类型中,捕获的第一个错误将被捕获并向下传递给任何需要它的人,而成功案例将继续进行处理。 (也有Validation
个类型捕获错误列表。)
这些类型有许多实现。有关一些建议,请参见fantasy-land list。
Ramda曾经有自己的一组此类类型,但已放弃维护它。我们经常为此推荐民间故事和圣所。但是,即使Ramda的旧实现也应该做到。这个版本使用Folktale's data.either
,因为我更了解它,但是Folktale的更高版本将其替换为Result
。
以下代码块显示了如何使用Either
来处理这种失败的概念,尤其是如何使用R.sequence
将Eithers
的数组转换为{{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)