Scalaz:Cokleisli 组合用例请求
这个问题并不是为了引火烧身!显而易见,我最近一直在研究 Scalaz。我试图理解为什么我需要该库提供的一些功能。事情是这样的:
import scalaz._
import Scalaz._
type NEL[A] = NonEmptyList[A]
val NEL = NonEmptyList
我在我的函数中放置了一些 println 语句来查看发生了什么(旁白:如果我试图避免这样的副作用,我会做什么?)。我的功能是:
val f: NEL[Int] => String = (l: NEL[Int]) => {println("f: " + l); l.toString |+| "X" }
val g: NEL[String] => BigInt = (l: NEL[String]) => {println("g: " + l); BigInt(l.map(_.length).sum) }
然后我通过 cokleisli 将它们组合起来并传入 NEL[Int]
val k = cokleisli(f) =>= cokleisli(g)
println("RES: " + k( NEL(1, 2, 3) ))
这会打印什么?
f: NonEmptyList(1, 2, 3)
f: NonEmptyList(2, 3)
f: NonEmptyList(3)
g: NonEmptyList(NonEmptyList(1, 2, 3)X, NonEmptyList(2, 3)X, NonEmptyList(3)X)
RES: 57
RES 值是最终 NEL 中(字符串)元素的字符数。我想到了两件事:
- 我怎么知道我的 NEL 将以这种方式从所涉及的方法签名中减少? (我根本没想到结果)
- 这有什么意义?可以为我提炼出一个相当简单且易于遵循的用例吗?
这个问题是对像 retronym< /a> 解释这个强大的库实际上是如何工作的。
This question isn't meant as flame-bait! As it might be apparent, I've been looking at Scalaz recently. I'm trying to understand why I need some of the functionality that the library provides. Here's something:
import scalaz._
import Scalaz._
type NEL[A] = NonEmptyList[A]
val NEL = NonEmptyList
I put some println statements in my functions to see what was going on (aside: what would I have done if I was trying to avoid side effects like that?). My functions are:
val f: NEL[Int] => String = (l: NEL[Int]) => {println("f: " + l); l.toString |+| "X" }
val g: NEL[String] => BigInt = (l: NEL[String]) => {println("g: " + l); BigInt(l.map(_.length).sum) }
Then I combine them via a cokleisli and pass in a NEL[Int]
val k = cokleisli(f) =>= cokleisli(g)
println("RES: " + k( NEL(1, 2, 3) ))
What does this print?
f: NonEmptyList(1, 2, 3)
f: NonEmptyList(2, 3)
f: NonEmptyList(3)
g: NonEmptyList(NonEmptyList(1, 2, 3)X, NonEmptyList(2, 3)X, NonEmptyList(3)X)
RES: 57
The RES value is the character count of the (String) elements in the final NEL. Two things occur to me:
- How could I have known that my NEL was going to be reduced in this manner from the method signatures involved? (I wasn't expecting the result at all)
- What is the point of this? Can a reasonably simple and easy-to-follow use case be distilled for me?
This question is a thinly-veiled plea for some lovely person like retronym to explain how this powerful library actually works.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
要了解结果,您需要了解
Comonad[NonEmptyList]
实例。Comonad[W]
本质上提供了三个函数(Scalaz 中的实际接口有点不同,但这有助于解释):因此,
Comonad
为某些容器< code>W 具有一个独特的“head”元素 (copure
) 和一种公开容器内部结构的方法,以便我们为每个元素获得一个容器 (cojoin< /code>),每个在头部都有一个给定的元素。
NonEmptyList
的实现方式是,copure
返回列表的头部,cojoin
返回列表的列表,此列表位于该列表的头部,所有尾部位于尾部。示例(我将
NonEmptyList
缩写为Nel
):=>=
函数是 coKleisli 组合。如何组合两个函数f: W[A] => B
和g: W[B] => C
,除了W
是一个Comonad
之外,对它们一无所知?f
的输入类型和g
的输出类型不兼容。但是,您可以map(f)
来获取W[W[A]] => W[B]
,然后与g
组合。现在,给定一个W[A]
,您可以共同连接
它以将W[W[A]]
输入到该函数中。因此,唯一合理的组合是执行以下操作的函数k
:因此,对于非空列表:
To understand the result, you need to understand the
Comonad[NonEmptyList]
instance.Comonad[W]
essentially provides three functions (the actual interface in Scalaz is a little different, but this helps with explanation):So,
Comonad
provides an interface for some containerW
that has a distinguished "head" element (copure
) and a way of exposing the inner structure of the container so that we get one container per element (cojoin
), each with a given element at the head.The way that this is implemented for
NonEmptyList
is thatcopure
returns the head of the list, andcojoin
returns a list of lists, with this list at the head and all tails of this list at the tail.Example (I'm shortening
NonEmptyList
toNel
):The
=>=
function is coKleisli composition. How would you compose two functionsf: W[A] => B
andg: W[B] => C
, knowing nothing about them other than thatW
is aComonad
? The input type off
and the output type ofg
aren't compatible. However, you canmap(f)
to getW[W[A]] => W[B]
and then compose that withg
. Now, given aW[A]
, you cancojoin
it to get theW[W[A]]
to feed into that function. So, the only reasonable composition is a functionk
that does the following:So for your nonempty list:
还为 scalaz.Tree< 定义了 Cojoin /a> 和 scalaz.TreeLoc。这可能会被利用 查找从树根到每个叶节点的所有路径的流。
使用 coKleisli 箭头组合,我们可以做到这一点,例如:
leafDist
获取一棵树并返回它的副本,每个节点都注释有与叶子的最大距离。Cojoin is also defined for scalaz.Tree and scalaz.TreeLoc. This can be exploited to find a stream of all paths from the root of the tree to each leaf node.
Using coKleisli arrow composition, we can do this, for example:
leafDist
takes a Tree and returns a copy of it with each node annotated with its maximum distance from a leaf.