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の(文字列)要素の文字数です。私には2つのことが起こります。

  1. 関係するメソッドシグネチャから、この方法でNELが削減されることをどのようにして知ることができましたか?(私は結果をまったく期待していませんでした)
  2. これのポイントは何ですか?適度にシンプルでわかりやすいユースケースを私のために蒸留することはできますか?

この質問は、レトロニムのような素敵な人がこの強力なライブラリが実際にどのように機能するかを説明するための薄いベールのある嘆願です。

4

2 に答える 2

18

Comonad[NonEmptyList]結果を理解するには、インスタンスを理解する必要があります。Comonad[W]基本的に3つの機能を提供します(Scalazの実際のインターフェイスは少し異なりますが、これは説明に役立ちます)。

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

したがって、識別された「ヘッド」要素()を持つComonadコンテナのインターフェイスと、コンテナの内部構造を公開する方法を提供します。これにより、要素ごとに1つのコンテナが取得され()、それぞれがヘッドに特定の要素を持ちます。Wcopurecojoin

これを実装する方法NonEmptyListcopure、リストの先頭を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構成です。どのように2つの関数を構成f: W[A] => Bg: W[B] => C、それ以外は何も知らないWComonadですか?の入力タイプfとの出力タイプにgは互換性がありません。ただし、map(f)を取得W[W[A]] => W[B]して作成することはできますg。ここで、を指定すると、をその関数にフィードさせることW[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.Treeおよび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ツリーを取得し、そのコピーを返します。各ノードには、リーフからの最大距離が注釈として付けられています。

于 2010-04-02T08:17:14.927 に答える