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 и передаю в a 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. Какой смысл этого? Может довольно простой и понятный вариант использования быть дистиллированным для меня?

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

17
задан Community 23 May 2017 в 11:53
поделиться

2 ответа

Чтобы понять результат, вам необходимо понять экземпляр Comonad [NonEmptyList] . Comonad [W] по существу предоставляет три функции (фактический интерфейс в Scalaz немного отличается, но это помогает с объяснением):

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

Итак, Comonad предоставляет интерфейс для некоторого контейнера W , который имеет выделенный элемент «голова» ( cojoin ) и способ раскрытия внутренней структуры контейнера, так что мы получаем один контейнер на элемент ( 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 - это Комонада ? Тип ввода 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
18
ответ дан 30 November 2019 в 12:57
поделиться

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

9
ответ дан 30 November 2019 в 12:57
поделиться
Другие вопросы по тегам:

Похожие вопросы: