功能构成与Monads ......无法正常工作

时间:2017-06-13 08:00:27

标签: javascript functional-programming ramda.js

我有一些丑陋的数据,需要大量丑陋的空检查。我的目标是编写一套函数来以无点的声明式样式访问/修改它,使用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()不是函数,'在执行传递给它的最后一个函数时。

我不确定这里出了什么问题,或者是否有更好的方法可以做到这一点,但任何建议都值得赞赏!

1 个答案:

答案 0 :(得分:8)

第一件事

  1. Maybe实现(link)几乎是垃圾 - 您可能需要考虑选择一个不需要您实现Functor接口的实现(就像您使用{{{ 1}}) - 我可能会从民间故事中建议Data.Maybe。或者,因为你显然不害怕自己实施的事情,所以制作你自己的Maybe ^ _ ^
    1. 您的map实现不适用于任何实现仿函数接口的仿函数。即,您的map一起使用,但Maybe 应该足够通用,可以与任何 mappable 一起使用,如果有这样一个词。

      不用担心,Ramda在框中包含map - 只需将其与实现map方法的Maybe一起使用(例如上面引用的Data.Maybe)

      1. 您的.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

        1. 您的curry实现混合了函数组合和映射函数的问题(通过使用pipe)。我建议专门为功能组合保留map

          同样,不确定为什么你会使用Ramda然后重新发明它。 Ramda已经包含pipe

          我注意到的另一件事

          pipe
          1. 你所做的// 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(来自民间故事)
            1. 没有观察到单个monad - 您的问题是关于monad的函数组合,但您只在代码中使用functor接口。这里的一些混淆可能来自于Either实现Maybe Functor的事实,因此它可以表现为两者(和任何其他界面一样)实施)!在这种情况下,Monad也是如此。

              您可能希望查看Kleisli category的monadic函数组合,尽管它可能与此特定问题无关。

            2. 功能接口受法律管辖

              你的问题源于缺乏对仿函数法律的曝光/理解 - 这些是什么意思如果你的数据类型符合这些法律,只有然后可以可以说你的类型是一个仿函数。在所有其他情况下,您可能正在处理之类的仿函数,但实际上并不是仿函数。

                

              仿函数法

                   

              其中Eithermap :: Functor f => (a -> b) -> f a -> f b是标识函数ida -> af :: b -> c

              g :: a -> b

              这对我们说的是,我们可以单独为每个函数组合多次调用// identity map(id) == id // composition compose(map(f), map(g)) == map(compose(f, g)) ,或者我们可以先组合所有函数,然后map 一次。 - 请注意组成法的左侧我们如何调用map两次以应用两个函数,但在右侧.map只调用一次。每个表达式的结果都是相同的。

                

              monad法律

                   

              虽然我们正在处理它,但我们也可以涵盖monad法则 - 再次,如果您的数据类型遵守这些法律,只有然后可以调用它一个单子。

                   

              其中.mapmreturn :: 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.composepipe的选择取决于您 - 它只是一个符号差异;无论哪种方式,法律都得到了满足。

              compose

              我想提供更多帮助,但我不能100%确定您的功能实际上是在做什么。

              1. // 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))
              2. 开头
              3. 阅读Maybe(person)属性
              4. 获取person.names的第一个索引 - 它是一个数组还是什么?或者名字的第一个字母?
              5. 阅读person.names属性?我们在这里期待一个单子? (请看.value.chain以及我从民间故事中链接的.map实施中的Maybe进行比较。
              6. 拆分Either
              7. 上的值
              8. 使用/
              9. 加入值
              10. 如果我们有一个值,则返回它,否则返回一些替代
              11. 这是我对正在发生的事情的最好猜测,但我无法在这里描绘你的数据或理解你正在尝试做的计算。如果您提供更具体的数据示例和预期输出,我可以帮助您制定更具体的答案。

                <强>备注

                几年前我也在你的船上;我的意思是,进入函数式编程。我想知道所有小部件是如何组合在一起的,并且实际上产生了一个人类可读的程序。

                只有在将功能技术应用于整个系统时,才能观察到函数式编程提供的大多数好处。首先,你会觉得你必须引入大量的依赖关系,只是以一种功能性的方式重写一个函数&#34;。但是,一旦您在程序中的 more 位置中使用了这些依赖项,就可以开始左右削减复杂性。这真的很酷,但需要一段时间才能让你的程序(以及你的头脑)在那里。

                事后看来,这可能不是一个好的答案,但我希望这能帮助你。这对我来说是一个非常有趣的话题,我很乐意协助回答你的任何其他问题^ _ ^