与 Monads 的功能组合......不工作

Function Composition With Monads...not working

我有一些难看的数据,需要大量难看的空值检查。我的目标是以无点、声明式的方式为 access/modify 编写一套函数,使用 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()' 十亿次似乎不是很枯燥,看起来也不是很好。我宁愿有一个特殊的 pipe/compose 函数,将每个函数包装在一个 map() 中。

注意到我是如何使用 R.pipe() 而不是我的自定义管道 () 的吗?我的自定义实现总是在执行传递给它的最后一个函数时抛出错误 'isNothing() is not a function,'。

我不确定这里出了什么问题,或者是否有更好的方法,但欢迎提出任何建议!

要事第一

  1. 那个 Maybe 实现(link) is pretty much junk - you might want to consider picking an implementation that doesn't require you to implement the Functor interface (like you did with map) – I might suggest Data.Maybe 来自民间故事。或者既然你显然不害怕自己实现东西,那就自己做吧 ^_^

  1. 您的 map 实现不适合在任何实现仿函数接口的仿函数上工作。即,您的适用于Maybe,但map应该足够通用以适用于任何 ]mappable,如果有这样的话。

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


  1. 您的 curry 实现没有正确地柯里化函数。它只适用于元数为 2 的函数——curry 应该适用于任何长度的函数。

    // 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
    

    如果你已经在使用 Ramda,你真的没有理由自己实现 curry,因为它已经包含 curry


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

    同样,不确定您为什么使用 Ramda 然后重新发明了很多。 Ramda 已经包含 pipe

    我注意到的另一件事

    // you're doing
    R.pipe (a,b,c) (Maybe(x))
    
    // but that's the same as
    R.pipe (Maybe,a,b,c) (x)
    

  1. 你做的Either可能不是你想的functor/monad。有关更完整的实现,请参阅 Data.Either(来自民间故事)

  1. 没有观察到一个 monad – 你的问题是关于 monad 的函数组合,但你只在代码中使用了仿函数接口。这里的一些混淆可能来自 Maybe 实现 Functor Monad 的事实,因此它可以同时作为(和任何它实现的其他接口)! Either也是如此,在本例中

    您可能想查看 Kleisli category 的单子函数组合,尽管对于这个特定问题它可能与您无关。


功能接口受法律约束

您的问题是由于缺少 exposure/understanding 函子法则而产生的 – 这些意味着 如果 您的数据类型遵守这些法则,只有 then能不能说你的类型一个函子。在所有其他情况下,您可能正在处理 类似 仿函数,但实际上不是仿函数的东西。

functor laws

where map :: Functor f => (a -> b) -> f a -> f b, id is the identity function a -> a, and f :: b -> c and g :: a -> b

// identity
map(id) == id

// composition 
compose(map(f), map(g)) == map(compose(f, g))

这告诉我们的是,我们可以单独为每个函数编写对 map 的多个调用,或者我们可以先编写所有函数,然后 map 一次。 – 请注意在组合法则的左侧,我们如何调用 .map 两次以应用两个函数,但在右侧 .map 仅被调用一次。每个表达式的结果是相同的。

monad laws

While we're at it, we can cover the monad laws too – again, if your data type obeys these laws, only then can it be called a monad.

where mreturn :: Monad m => a -> m a, mbind :: Monad m => m a -> (a -> m b) -> m b

// 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))

使用 Kleisli 组合函数可能更容易看到定律,composek – 现在很明显 Monad 确实遵守结合律

monad laws defined using Kleisli composition

where composek :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c)

// 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))

寻找解决方案

那么这一切对您来说意味着什么?简而言之,您正在做的工作比您必须做的要多——尤其是实施您选择的库 Ramda 已经附带的很多东西。现在,这没什么不对的(事实上,如果你审计我的许多 网站上的其他答案),但如果您的某些实现有误,可能会造成混淆。

既然你好像主要挂在map方面,我就帮你看个简单的改造。这利用了上面说明的 Functor 组合法则:

注意,这使用 R.pipe 组成从左到右而不是像 R.compose 从右到左。虽然 ,但使用 pipe 还是 compose 取决于您 – 这只是符号差异;无论哪种方式,法律都得到了履行。

// 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))

我想提供更多帮助,但我不能 100% 确定您的函数实际要做什么。

  1. Maybe(person)
  2. 开始
  3. 阅读person.names属性
  4. 获取person.names的第一个索引——它是一个数组还是什么?还是名字的第一个字母?
  5. 阅读.value属性??我们在这里期待 monad 吗? (查看我从 folktale 链接的 MaybeEither 实现中的 .chain.map 的比较)
  6. 拆分值 /
  7. 将值加入 ''
  8. 如果我们有一个值,return它,否则return一些替代

这是我对正在发生的事情的最佳猜测,但我无法在此处描绘您的数据或理解您尝试进行的计算。如果您提供更具体的数据示例和预期输出,我也许可以帮助您制定更具体的答案。


备注

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

函数式编程提供的大部分好处只有在将函数式技术应用于整个系统时才能观察到。一开始,你会觉得你必须引入大量的依赖项才能重写 "functional way" 中的一个函数。但是,一旦您在程序的 more 处使用了这些依赖项,您就可以开始左右削减复杂性。看到它真的很酷,但是需要一段时间才能让你的程序(和你的头脑)到那里。

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