Вычислительные конструкции (монады, стрелки и т. Д.) - PullRequest
17 голосов
/ 09 марта 2012

Я довольно заинтересовался тем, как моделируются вычисления в Haskell.Несколько ресурсов описали монады как «составные вычисления», а стрелки - как «абстрактные представления вычислений».Я никогда не видел моноидов, функторов или аппликативных функторов, описанных таким образом.Кажется, что им не хватает необходимой структуры.

Я нахожу эту идею интересной и задаюсь вопросом, есть ли другие конструкции, которые делают что-то подобное.Если да, то какие ресурсы я могу использовать, чтобы познакомиться с ними?Есть ли на Hackage какие-нибудь пакеты, которые могут пригодиться?

Примечание: Этот вопрос похож на Monads vs. Arrows и https://stackoverflow.com/questions/2395715/resources-for-learning-monads-functors-monoids-arrows-etc,, но яя ищу конструкты за пределами фюнторов, аппликативных функторов, монад и стрел.

Редактировать: Я допускаю, что аппликативные функторы следует рассматривать как "вычислительные конструкции", но я действительно чего-то ищуЯ еще не сталкивался.Это включает аппликативные функторы, монады и стрелки.

Ответы [ 3 ]

24 голосов
/ 10 марта 2012

Arrows обобщены по категориям и, таким образом, по классу типов Category.

 class Category f where
     (.) :: f a b -> f b c -> f a c
     id :: f a a

Определение класса типов Arrow имеет Category как суперкласс.Категории (в смысле haskell) обобщают функции (вы можете их составлять, но не применять их) и поэтому, безусловно, являются «моделью вычислений».Arrow предоставляет Category дополнительную структуру для работы с кортежами.Итак, в то время как Category отражает что-то о функциональном пространстве Haskell, Arrow распространяет это на что-то о типах продуктов.

Каждый Monad порождает нечто, называемое «Kleisli Category», и эта конструкция дает вам экземплярыArrowApply.Вы можете построить Monad из любого ArrowApply так, чтобы полный круг не изменил вашего поведения, поэтому в некотором глубоком смысле Monad и ArrowApply - это одно и то же.

 newtype Kleisli m a b = Kleisli { runKleisli :: a -> m b }

 instance Monad m => Category (Kleisli m) where
     id = Kleisli return
     (Kleisli f) . (Kleisli g) = Kleisli (\b -> g b >>= f)

 instance Monad m => Arrow (Kleisli m) where
     arr f = Kleisli (return . f)
     first (Kleisli f) = Kleisli (\ ~(b,d) -> f b >>= \c -> return (c,d))
     second (Kleisli f) = Kleisli (\ ~(d,b) -> f b >>= \c -> return (d,c))

Фактически каждый Arrow порождает Applicative (универсальное количественное определение для правильной сортировки) в дополнение к Category суперклассу, и я считаю, что комбинация подходящего Categoryи Applicative достаточно, чтобы восстановить Arrow.

Итак, эти структуры глубоко связаны.

Внимание: впереди неуместный комментарий .Одно из главных различий между мышлением Functor / Applicative / Monad и мышлением Category / Arrow заключается в том, что хотя Functor и его аналог являются обобщениями на уровне объекта (типы в Haskell), Category / Arrow - это генерация понятия morphism (функции в Haskell).Я считаю, что мышление на уровне обобщенного морфизма предполагает более высокий уровень абстракции, чем мышление на уровне обобщенного объектов .Иногда это хорошо, а иногда нет.С другой стороны, несмотря на то, что Arrows имеют категориальную основу, и никто по математике не считает Applicative интересным, я понимаю, что Applicative обычно лучше понимают, чем Arrow.

В основном вы можете думать о «Category

Более конкретно ниже: Что касается других структур для моделирования вычислений: часто можно поменять направление «стрелок» (просто обозначающих здесь морфизмы) в категориальных конструкциях, чтобы получить «двойное» или «совместное»строительство".Итак, если монада определена как

class Functor m => Monad m where
   return :: a -> m a
   join :: m (m a) -> m a

(хорошо, я знаю, что Хаскелл определяет вещи не так, но ma >>= f = join $ fmap f ma и join x = x >>= id, так что это вполне может быть), тогда комонадis

class Functor m => Comonad m where
   extract :: m a -> a -- this is co-return
   duplicate :: m a -> m (m a) -- this is co-join

Эта штука также довольно распространена.Оказывается, Comonad является базовой структурой клеточных автоматов .Для полноты, я должен отметить, что Control.Comonad Эдварда Кметта помещает duplicate в класс между функтором и Comonad для "расширяемых функторов", потому что вы также можете определить

   extend :: (m a -> b) -> m a -> m b -- Looks familiar? this is just the dual of >>=
   extend f = fmap f . duplicate
   --this is enough
   duplicate = extend id

Оказывается, что всеMonad s также являются "расширяемыми"

   monadDuplicate :: Monad m => m a -> m (m a)
   monadDuplicate = return

, в то время как все Comonads являются "присоединяемыми"

   comonadJoin :: Comonad m => m (m a) -> m a
   comonadJoin = extract

, поэтому эти структуры очень близки друг к другу.

9 голосов
/ 09 марта 2012

Все монады являются стрелками (монада изоморфна ArrowApply).По-другому, все монады являются экземплярами Applicative , где <*> равно Control.Monad.ap, а *> равно >>.Applicative слабее, потому что не гарантирует работу >>=.Таким образом, Applicative захватывает вычисления, которые не проверяют предыдущие результаты и не изменяют значения.В ретроспективе большая часть монадического кода на самом деле является аппликативной, и при чистом переписывании это произойдет.

Расширение монад с последними видами ограничений в GHC 7.4.1 теперь могут быть более удачные конструкции для ограниченных монад .И есть также люди, которые смотрят на параметризованные монады , и, конечно, я включаю ссылку на что-то от Олег .

5 голосов
/ 10 марта 2012

В библиотеках эти структуры приводят к разным типам вычислений.

Например, аппликативы могут использоваться для реализации статических эффектов.Под этим я подразумеваю эффекты, которые определены заранее.Например, при реализации конечного автомата, отклонении или принятии состояния ввода.Их нельзя использовать для манипулирования их внутренней структурой с точки зрения их вклада.

Тип говорит сам за себя:

 <*> :: f (a -> b) -> f a -> f b

Легко рассуждать, структура f не может зависеть от ввода a.Потому что не может достичь f на уровне типа.

Монады могут использоваться для динамических эффектов.Это также может быть рассмотрено из сигнатуры типа:

 >>= :: m a -> (a -> m b) -> m b

Как вы можете это увидеть?Потому что а находится на том же «уровне», что и м.Математически это двухэтапный процесс.Bind представляет собой композицию из двух функций: fmap и join.Сначала мы используем fmap вместе с монадическим действием для создания новой структуры, встроенной в старую:

fmap :: (a -> b) -> m a -> m b
f :: (a -> m b)
m :: m a
fmap f :: m a -> m (m b)
fmap f m :: m (m b)

Fmap может создать новую структуру на основе входного значения.Затем мы сворачиваем структуру с помощью соединения, таким образом, мы можем манипулировать структурой из монадических вычислений способом, который зависит от ввода:

join :: m (m a) -> m a
join (fmap f m) :: m b

Многие монады проще реализовать с помощью соединения:

(>>=) = join . fmap 

Это возможно с монадами:

addCounter :: Int -> m Int () 

Но не с аппликативами, но аппликативы (и любая монада) могут делать такие вещи как:

addOne :: m Int ()

Стрелки даютбольше контроля над типами ввода и вывода, но для меня они действительно похожи на аппликативные.Может быть, я ошибаюсь по этому поводу.

...