Scalaz:Cokleisli 组合用例请求

发布于 2024-08-27 21:39:14 字数 1385 浏览 8 评论 0原文

这个问题并不是为了引火烧身!显而易见,我最近一直在研究 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 中(字符串)元素的字符数。我想到了两件事:

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

这个问题是对像 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:

  1. 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)
  2. 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

梦与时光遇 2024-09-03 21:39:14

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

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

因此,Comonad 为某些容器< code>W 具有一个独特的“head”元素 (copure) 和一种公开容器内部结构的方法,以便我们为每个元素获得一个容器 (cojoin< /code>),每个在头部都有一个给定的元素。

NonEmptyList 的实现方式是,copure 返回列表的头部,cojoin 返回列表的列表,此列表位于该列表的头部,所有尾部位于尾部。

示例(我将 NonEmptyList 缩写为 Nel):

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] => Bg: W[B] => C,除了 W 是一个 Comonad 之外,对它们一无所知? f 的输入类型和 g 的输出类型不兼容。但是,您可以 map(f) 来获取 W[W[A]] => W[B],然后与 g 组合。现在,给定一个 W[A],您可以共同连接它以将 W[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

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

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

So, Comonad provides an interface for some container W 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 that copure returns the head of the list, and cojoin 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 to Nel):

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

The =>= function is coKleisli composition. How would you compose two functions f: W[A] => B and g: W[B] => C, knowing nothing about them other than that W is a Comonad? The input type of f and the output type of g aren't compatible. However, you can map(f) to get W[W[A]] => W[B] and then compose that with g. Now, given a W[A], you can cojoin it to get the W[W[A]] to feed into that function. So, the only reasonable composition is a function k that does the following:

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

So for your nonempty list:

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
ㄟ。诗瑗 2024-09-03 21:39:14

还为 scalaz.Tree< 定义了 Cojoin /a> 和 scalaz.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 获取一棵树并返回它的副本,每个节点都注释有与叶子的最大距离。

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.

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

Using coKleisli arrow composition, we can do this, for example:

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

leafDist takes a Tree and returns a copy of it with each node annotated with its maximum distance from a leaf.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文