我有一些丑陋的数据,需要大量丑陋的空检查。我的目标是编写一套函数来以无点的声明式样式访问/修改它,使用Maybe monad将空检查保持在最低限度。理想情况下,我可以将Ramda与monad一起使用,但它并没有如此出色。
这有效:
const Maybe = require('maybe');
const R = require('ramda');
const curry = fn => (...args) => fn.bind(null, ...args);
const map = curry((fn, monad) => (monad.isNothing()) ? monad : Maybe(fn(monad.value())));
const pipe = (...fns) => acc => fns.reduce((m, f) => map(f)(m), acc);
const getOrElse = curry((opt, monad) => monad.isNothing() ? opt : monad.value());
const Either = (val, d) => val ? val : d;
const fullName = (person, alternative, index) => R.pipe(
map(R.prop('names')),
map(R.nth(Either(index, 0))),
map(R.prop('value')),
map(R.split('/')),
map(R.join('')),
getOrElse(Either(alternative, ''))
)(Maybe(person));
但是,必须输入' map()'十亿次看起来不是很干,看起来也不是很好。我宁愿使用一个特殊的管道/撰写函数来包装map()中的每个函数。
注意我是如何使用R.pipe()而不是我的自定义管道()?我的自定义实现总是会出错,' isNothing()不是函数,'在执行传递给它的最后一个函数时。
我不确定这里出了什么问题,或者是否有更好的方法可以做到这一点,但任何建议都值得赞赏!
答案 0 :(得分:8)
第一件事
Maybe
实现(link)几乎是垃圾 - 您可能需要考虑选择一个不需要您实现Functor接口的实现(就像您使用{{{ 1}}) - 我可能会从民间故事中建议Data.Maybe。或者,因为你显然不害怕自己实施的事情,所以制作你自己的Maybe ^ _ ^ 您的map
实现不适用于任何实现仿函数接口的仿函数。即,您的仅与map
一起使用,但Maybe
应该足够通用,可以与任何 mappable 一起使用,如果有这样一个词。
不用担心,Ramda在框中包含map
- 只需将其与实现map
方法的Maybe
一起使用(例如上面引用的Data.Maybe)
您的.map
实施并不适合咖喱功能。它仅适用于arity为2的函数 - 咖喱应适用于任何函数长度。
curry
如果你已经在使用Ramda,那么你真的没有理由自己实施// given, f
const f = (a,b,c) => a + b + c
// what yours does
curry (f) (1) (2) (3) // => Error: curry(...)(...)(...) is not a function
// because
curry (f) (1) (2) // => NaN
// what it should do
curry (f) (1) (2) (3) // => 6
,因为它已经包含curry
您的curry
实现混合了函数组合和映射函数的问题(通过使用pipe
)。我建议专门为功能组合保留map
。
同样,不确定为什么你会使用Ramda然后重新发明它。 Ramda已经包含pipe
我注意到的另一件事
pipe
// you're doing
R.pipe (a,b,c) (Maybe(x))
// but that's the same as
R.pipe (Maybe,a,b,c) (x)
可能不是你想到的任何一个仿函数/ monad。有关更完整的实施,请参阅Data.Either
(来自民间故事)没有观察到单个monad - 您的问题是关于monad的函数组合,但您只在代码中使用functor接口。这里的一些混淆可能来自于Either
实现Maybe
和 Functor
的事实,因此它可以表现为两者(和任何其他界面一样)实施)!在这种情况下,Monad
也是如此。
您可能希望查看Kleisli category
的monadic函数组合,尽管它可能与此特定问题无关。
功能接口受法律管辖
你的问题源于缺乏对仿函数法律的曝光/理解 - 这些是什么意思如果你的数据类型符合这些法律,只有然后可以可以说你的类型是一个仿函数。在所有其他情况下,您可能正在处理之类的仿函数,但实际上并不是仿函数。
仿函数法
其中
Either
,map :: Functor f => (a -> b) -> f a -> f b
是标识函数id
,a -> a
和f :: b -> c
g :: a -> b
这对我们说的是,我们可以单独为每个函数组合多次调用// identity
map(id) == id
// composition
compose(map(f), map(g)) == map(compose(f, g))
,或者我们可以先组合所有函数,然后map
一次。 - 请注意组成法的左侧我们如何调用map
两次以应用两个函数,但在右侧.map
只调用一次。每个表达式的结果都是相同的。
monad法律
虽然我们正在处理它,但我们也可以涵盖monad法则 - 再次,如果您的数据类型遵守这些法律,只有然后可以调用它一个单子。
其中
.map
,mreturn :: Monad m => a -> m a
mbind :: Monad m => m a -> (a -> m b) -> m b
使用Kleisli组合函数// left identity
mbind(mreturn(x), f) == f(x)
// right identity
mbind(m, mreturn) == m
// associativity
mbind(mbind(m, f), g) == mbind(m, x => mbind(f(x), g))
可能更容易看到法则 - 现在很明显,Monads真正遵守相关性法则
使用Kleisli合成定义的monad法则
其中
composek
composek :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c)
寻找解决方案
那么这对你来说意味着什么呢?简而言之,您需要做的工作比以往更多 - 尤其是实施了您选择的图书馆Ramda所带来的许多东西。现在,这没有什么不妥(事实上,如果你审核我的很多,我是这个的大支持者) 网站上的其他答案),但如果你的某些实现错误,它可能会引起混淆。
由于您似乎大多挂在// kleisli left identity
composek(mreturn, f) == f
// kleisli right identity
composek(f, mreturn) == f
// kleisli associativity
composek(composek(f, g), h) == composek(f, composek(g, h))
方面,我会帮您看一个简单的转换。这利用了上面说明的Functor组成法:
注意,这使用map
从左到右而不是从右到左组成R.pipe
。在I prefer right-to-left composition期间,使用R.compose
与pipe
的选择取决于您 - 它只是一个符号差异;无论哪种方式,法律都得到了满足。
compose
我想提供更多帮助,但我不能100%确定您的功能实际上是在做什么。
// this
R.pipe(map(f), map(g), map(h), map(i)) (Maybe(x))
// is the same as
Maybe(x).map(R.pipe(f,g,h,i))
Maybe(person)
属性person.names
的第一个索引 - 它是一个数组还是什么?或者名字的第一个字母?person.names
属性?我们在这里期待一个单子? (请看.value
与.chain
以及我从民间故事中链接的.map
实施中的Maybe
进行比较。Either
/
这是我对正在发生的事情的最好猜测,但我无法在这里描绘你的数据或理解你正在尝试做的计算。如果您提供更具体的数据示例和预期输出,我可以帮助您制定更具体的答案。
<强>备注强>
几年前我也在你的船上;我的意思是,进入函数式编程。我想知道所有小部件是如何组合在一起的,并且实际上产生了一个人类可读的程序。
只有在将功能技术应用于整个系统时,才能观察到函数式编程提供的大多数好处。首先,你会觉得你必须引入大量的依赖关系,只是以一种功能性的方式重写一个函数&#34;。但是,一旦您在程序中的 more 位置中使用了这些依赖项,就可以开始左右削减复杂性。这真的很酷,但需要一段时间才能让你的程序(以及你的头脑)在那里。
事后看来,这可能不是一个好的答案,但我希望这能帮助你。这对我来说是一个非常有趣的话题,我很乐意协助回答你的任何其他问题^ _ ^