Список, чей "Нил" несет ценность?

Определяет ли в стандартной библиотеке Haskell тип данных, подобный этому

data ListWithEnd e a = Cons a (ListWithEnd e a)
                     | End e

Это список, у которого завершающий элемент несет значение определенного типа?

So ListWithEnd () изоморфно [], а ListWithEnd Void изоморфно бесконечным потокам. Или, по-разному, ListWithEnd e a очень близок к ConduitM () a Identity e..

Ответ 1

Мы можем определить ListWithEnd следующим образом:

import Control.Monad.Free

type LWE a e = Free ((,) a) e

Как правило, мы ожидаем, что абстрактные или общие представления должны вознаградить нас за общее сокращение шаблона. Посмотрим, что это дает нам.

В любом случае мы определяем синоним шаблона для случая cons:

{-# LANGUAGE PatternSynonyms #-}

pattern x :> xs = Free (x, xs)
infixr 5 :>

Мы можем отображать, складывать и перемещаться по концевому элементу:

fmap (+1) (0 :> Pure 0) == (0 :> Pure 1)
traverse print (0 :> Pure 1) -- prints 1

Экземпляр Applicative дает нам очень аккуратную конкатенацию:

xs = 1 :> 2 :> Pure 10
ys = 3 :> 4 :> Pure 20

xs *> ys          == 1 :> 2 :> 3 :> 4 :> Pure 20 -- use right end
xs <* ys          == 1 :> 2 :> 3 :> 4 :> Pure 10 -- use left end
(+) <$> xs <*> ys == 1 :> 2 :> 3 :> 4 :> Pure 30 -- combine ends

Мы можем сопоставить элементы списка, если немного извиняем:

import Data.Bifunctor -- included in base-4.8!

hoistFree (first (+10)) xs == 11 :> 12 :> Pure 10

И мы можем использовать iter, конечно.

iter (uncurry (+)) (0 <$ xs) == 3 -- sum list elements

Было бы неплохо, если бы LWE мог быть BitraversableBifunctor и Bifoldable), потому что тогда мы могли бы получить доступ к элементам списка более общим и принципиальным способом. Для этого нам определенно нужен новый тип:

newtype LWE a e = LWE (Free ((,) a) e) deriving (lots of things)

instance Bifunctor LWE where bimap = bimapDefault
instance Bifoldable LWE where bifoldMap = bifoldMapDefault
instance Bitraversable LWE where bitraverse = ...

Но в этот момент мы могли бы подумать о том, чтобы просто написать простой ADT и записать экземпляры Applicative, Monad и Bitraversable в пару строк кода. В качестве альтернативы мы могли бы использовать lens и написать Traversal для элементов списка:

import Control.Lens

elems :: Traversal (LWE a e) (LWE b e) a b
elems f (Pure e)  = pure (Pure e)
elems f (x :> xs) = (:>) <$> f x <*> elems f xs

Подумав далее, мы должны сделать lens для конечного элемента. Это немного бонус за общий интерфейс Free, так как мы знаем, что каждый конечный LWE должен содержать ровно один конечный элемент, и мы можем сделать это явным, имея для него lens (а не a Traversal или Prism).

end :: Lens (LWE a e) (LWE a e') e e'
end f (Pure e)  = Pure <$> f e
end f (x :> xs) = (x :>) <$> end f xs