17

这个问题不是诱饵!很明显,我最近一直在研究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 中 (String) 元素的字符数。我想到两件事:

  1. 我怎么能从所涉及的方法签名中知道我的 NEL 将以这种方式减少?(我根本没想到结果)
  2. 这有什么意义?可以为我提炼出一个相当简单且易于理解的用例吗?

这个问题是对像retronym这样可爱的人的一个隐晦的请求,以解释这个强大的库实际上是如何工作的。

4

2 回答 2

18

要了解结果,您需要了解Comonad[NonEmptyList]实例。Comonad[W]本质上提供了三个函数(Scalaz 中的实际接口有点不同,但这有助于解释):

map:    (A => B) => W[A] => W[B]
copure: W[A] => A
cojoin: W[A] => W[W[A]]

因此,Comonad为一些容器提供了一个接口,该容器W具有一个可区分的“头”元素 ( copure),并提供了一种暴露容器内部结构的方法,以便我们每个元素 ( cojoin) 获得一个容器,每个容器的头部都有一个给定的元素。

它的实现方式NonEmptyListcopure返回列表的头部,并cojoin返回一个列表列表,这个列表在头部,这个列表的所有尾部都在尾部。

示例(我缩短NonEmptyListNel):

Nel(1,2,3).copure = 1
Nel(1,2,3).cojoin = Nel(Nel(1,2,3),Nel(2,3),Nel(3))

=>=函数是coKleisli 组成的。您将如何组合两个函数f: W[A] => B,并且除了ag: W[B] => C之外对它们一无所知?的输入类型和输出类型不兼容。但是,您可以获取然后使用. 现在,给定 a ,您可以将其输入到该函数中。因此,唯一合理的组合是执行以下操作的函数:WComonadfgmap(f)W[W[A]] => W[B]gW[A]cojoinW[W[A]]k

k(x) = g(x.cojoin.map(f))

所以对于你的非空列表:

g(Nel(1,2,3).cojoin.map(f))
= g(Nel(Nel(1,2,3),Nel(2,3),Nel(3)).map(f))
= g(Nel("Nel(1,2,3)X","Nel(2,3)X","Nel(3)X"))
= BigInt(Nel("Nel(1,2,3)X","Nel(2,3)X","Nel(3)X").map(_.length).sum)
= BigInt(Nel(11,9,7).sum)
= 27
于 2010-04-01T19:35:22.677 回答
9

Cojoin 也为scalaz.Treescalaz.TreeLoc定义。这可以被利用来找到从树的根到每个叶节点的所有路径的流。

def leafPaths[T](tree: Tree[T]): Stream[Stream[T]]
  = tree.loc.cojoin.toTree.flatten.filter(_.isLeaf).map(_.path)

使用 coKleisli 箭头组合,我们可以做到这一点,例如:

def leafDist[A] = (cokleisli(leafPaths[A]) &&& cokleisli(_.rootLabel))
  =>= (_.map(s => (s._2, s._1.map(_.length).max)))

leafDist获取一棵树并返回它的副本,其中每个节点都用其与叶子的最大距离进行注释。

于 2010-04-02T08:17:14.927 回答