2010-04-01 5 views
17

Этот вопрос не предназначен как пламенная приманка! Как это могло бы быть очевидно, я недавно смотрел Scalaz. Я пытаюсь понять почему Мне нужна часть функциональности, которую предоставляет библиотека. Вот кое-что:Scalaz: запрос на использование для композиции Cokleisli

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 - это количество символов элементов (String) в конечном NEL. Мне приходят две вещи:

  1. Как я мог узнать, что мой NEL будет уменьшен таким образом от используемых подписи метода? (Я не ожидал результата вообще)
  2. В чем смысл этого? Можно ли для меня дистиллировать достаточно простой и простой в использовании вариант использования?

Этот вопрос представляет собой тонко завуалированный призыв к какой-то прекрасный человек, как retronym объяснить, как на самом деле работает эта мощная библиотека.

ответ

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), каждый с заданным элементом в голове.

То, как это реализовано для 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] => B и g: W[B] => C, ничего не зная о них, кроме этого W - это Comonad? Тип ввода f и тип вывода g несовместимы.Однако вы можете map(f) получить W[W[A]] => W[B], а затем составить, что с g. Теперь, учитывая W[A], вы можете cojoin, чтобы получить 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 
+0

Спасибо за этот ответ - я принял * retronym's *, потому что он ответил на запрос в случае использования, но этот по-прежнему велик! –

+0

Нет пота, я отредактировал ответ retronym, чтобы добавить пример композиции cokleisli. – Apocalisp

+0

Я считаю, что вы можете «сопоставить» (f) ', чтобы получить' W [W [A]] => W [B] '", увы, у меня нет прав на редактирование. –

9

Cojoin также определяется для scalaz.Tree и scalaz.TreeLoc. Это может быть exploited, чтобы найти поток всех путей от корня дерева к каждому листовому узлу.

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 берет дерево и возвращает копию с каждым узлом аннотированный с максимальным расстоянием от листа.