与 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,'。
我不确定这里出了什么问题,或者是否有更好的方法,但欢迎提出任何建议!
要事第一
- 那个
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 来自民间故事。或者既然你显然不害怕自己实现东西,那就自己做吧 ^_^
您的 map
实现不适合在任何实现仿函数接口的仿函数上工作。即,您的仅适用于Maybe
,但map
应该足够通用以适用于任何 ]mappable,如果有这样的话。
不用担心,Ramda 在框中包含 map
——只需将它与实现 .map
方法的 Maybe
一起使用(例如上面提到的 Data.Maybe)
您的 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
您的 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)
- 你做的
Either
可能不是你想的functor/monad。有关更完整的实现,请参阅 Data.Either
(来自民间故事)
没有观察到一个 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% 确定您的函数实际要做什么。
- 从
Maybe(person)
开始
- 阅读
person.names
属性
- 获取
person.names
的第一个索引——它是一个数组还是什么?还是名字的第一个字母?
- 阅读
.value
属性??我们在这里期待 monad 吗? (查看我从 folktale 链接的 Maybe
和 Either
实现中的 .chain
与 .map
的比较)
- 拆分值
/
- 将值加入
''
- 如果我们有一个值,return它,否则return一些替代
这是我对正在发生的事情的最佳猜测,但我无法在此处描绘您的数据或理解您尝试进行的计算。如果您提供更具体的数据示例和预期输出,我也许可以帮助您制定更具体的答案。
备注
几年前我也在你的船上;我的意思是,刚刚进入函数式编程。我想知道所有的小片段如何组合在一起并实际生成一个人类可读的程序。
函数式编程提供的大部分好处只有在将函数式技术应用于整个系统时才能观察到。一开始,你会觉得你必须引入大量的依赖项才能重写 "functional way" 中的一个函数。但是,一旦您在程序的 more 处使用了这些依赖项,您就可以开始左右削减复杂性。看到它真的很酷,但是需要一段时间才能让你的程序(和你的头脑)到那里。
事后看来,这可能不是一个很好的答案,但我希望这对您有所帮助。这对我来说是一个非常有趣的话题,我很乐意协助回答您的任何其他问题^_^
我有一些难看的数据,需要大量难看的空值检查。我的目标是以无点、声明式的方式为 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,'。
我不确定这里出了什么问题,或者是否有更好的方法,但欢迎提出任何建议!
要事第一
- 那个
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 withmap
) – I might suggest Data.Maybe 来自民间故事。或者既然你显然不害怕自己实现东西,那就自己做吧 ^_^
您的
map
实现不适合在任何实现仿函数接口的仿函数上工作。即,您的仅适用于Maybe
,但map
应该足够通用以适用于任何 ]mappable,如果有这样的话。不用担心,Ramda 在框中包含
map
——只需将它与实现.map
方法的Maybe
一起使用(例如上面提到的 Data.Maybe)
您的
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
您的
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)
- 你做的
Either
可能不是你想的functor/monad。有关更完整的实现,请参阅Data.Either
(来自民间故事)
没有观察到一个 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 functiona -> a
, andf :: b -> c
andg :: 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% 确定您的函数实际要做什么。
- 从
Maybe(person)
开始
- 阅读
person.names
属性 - 获取
person.names
的第一个索引——它是一个数组还是什么?还是名字的第一个字母? - 阅读
.value
属性??我们在这里期待 monad 吗? (查看我从 folktale 链接的Maybe
和Either
实现中的.chain
与.map
的比较) - 拆分值
/
- 将值加入
''
- 如果我们有一个值,return它,否则return一些替代
这是我对正在发生的事情的最佳猜测,但我无法在此处描绘您的数据或理解您尝试进行的计算。如果您提供更具体的数据示例和预期输出,我也许可以帮助您制定更具体的答案。
备注
几年前我也在你的船上;我的意思是,刚刚进入函数式编程。我想知道所有的小片段如何组合在一起并实际生成一个人类可读的程序。
函数式编程提供的大部分好处只有在将函数式技术应用于整个系统时才能观察到。一开始,你会觉得你必须引入大量的依赖项才能重写 "functional way" 中的一个函数。但是,一旦您在程序的 more 处使用了这些依赖项,您就可以开始左右削减复杂性。看到它真的很酷,但是需要一段时间才能让你的程序(和你的头脑)到那里。
事后看来,这可能不是一个很好的答案,但我希望这对您有所帮助。这对我来说是一个非常有趣的话题,我很乐意协助回答您的任何其他问题^_^