Подтвердить что ты не робот

Почему нет способа получить аппликативные функторы в Haskell?

В Haskell вы можете автоматически выводить Functor, Foldable и Traversable, используя deriving. Однако нет способа получить Applicative. Учитывая, что существует один очевидный способ определить экземпляр Applicative (который будет равен заархивированному приложению), нет ли способа включить deriving Applicative?

4b9b3361

Ответ 1

Нет, это совсем не очевидно. Сравните следующие экземпляры Applicative:

  1. []
  2. ZipList
  3. Data.Sequence.Seq, чье объявление Applicative содержит несколько сотен строк.
  4. IO
  5. (->) r
  6. Парсеры в parsec, attoparsec, regex-applicative.
  7. Прокси-сервер в пакете pipes.

Здесь очень мало единообразия, и большинство случаев неочевидны.


Как Дэвид Янг комментирует, экземпляры [] и ZipList "в конечном итоге являются двумя разными, одинаково действительными экземплярами Applicative для типа списка".

Ответ 2

Теперь, когда DerivingVia выпущен (GHC-8.6 или новее), на самом деле можно получить Applicative с помощью DeriveGeneric для любого детерминированного типа данных! То есть любой тип данных с одним вариантом:

data Foo x = Foo x | Fe  -- This is non-deterministic and can't derive Applicative
data Bar x = Bar x x (Bar x) -- This is deterministic and can derive Applicative
data Baz x = Baz (Either Int x) [x] -- This is also ok, since [] and Either Int
                                    -- are both Applicative
data Void x -- This is not ok, since pure would be impossible to define.

Чтобы получить Applicative, нам сначала нужно определить оболочку для получения через дженерики:

{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE DeriveGeneric #-}
module Generically1 where

import GHC.Generics

newtype Generically1 f x = Generically1 { generically1 :: f x }

fromg1 :: Generic1 f => Generically1 f a -> Rep1 f a
fromg1 = from1 . generically1

tog1 :: Generic1 f => Rep1 f x -> Generically1 f x
tog1 = Generically1 . to1

instance (Functor f, Generic1 f, Functor (Rep1 f)) 
       => Functor (Generically1 f) where
  fmap f (Generically1 x) = Generically1 $ fmap f x

instance (Functor f, Generic1 f, Applicative (Rep1 f)) 
       => Applicative (Generically1 f) where
  pure = tog1 . pure
  f <*> x = tog1 $ fromg1 f <*> fromg1 x

instance (Functor f, Generic1 f, Monad (Rep1 f)) => Monad (Generically1 f) where
  return = pure
  m >>= f = tog1 $ fromg1 m >>= fromg1 . f

и для его использования мы сначала выводим Generic1 для нашего типа данных, а затем выводим Applicative через нашу новую оболочку Generically1:

data Foo x = Foo x (Int -> x) (Foo x)
  deriving (Functor, Generic1)
  deriving (Applicative, Monad) via Generically1 Foo

data Bar x = Bar x (IO x)
  deriving (Functor, Generic1)
  deriving (Applicative, Monad) via Generically1 Bar

data Baz f x = Baz (f x) (f x)
  deriving (Show, Functor, Generic1)
  deriving (Applicative, Monad) via Generically1 (Baz f)

Как видите, мы не только получили Applicative для наших типов данных, но также могли получить Monad.


Причина, по которой это работает, заключается в том, что существуют экземпляры для Applicative и Monad для представлений Generic1 этих типов данных. Смотрите, например, Тип продукта (: * :). Однако нет экземпляра Applicative для типа Sum (: + :), поэтому мы не можем получить его для недетерминированных типов.

Вы можете увидеть Generic1 представление типа данных, написав :kind! Rep1 Foo в GHCi. Вот упрощенные версии (исключая метаданные) представлений для указанных выше типов:

type family Simplify x where
  Simplify (M1 i c f) = Simplify f
  Simplify (f :+: g) = Simplify f :+: Simplify g
  Simplify (f :*: g) = Simplify f :*: Simplify g
  Simplify x = x

λ> :kind! Simplify (Rep1 Foo)
Simplify (Rep1 Foo) :: * -> *
= Par1 :*: (Rec1 ((->) Int) :*: Rec1 Foo)

λ> :kind! Simplify (Rep1 Bar)
Simplify (Rep1 Bar) :: * -> *
= Par1 :*: Rec1 IO

λ> :kind! forall f. Simplify (Rep1 (Baz f))
forall f. Simplify (Rep1 (Baz f)) :: k -> *
= forall (f :: k -> *). Rec1 f :*: Rec1 f

Изменение: Оболочка Generically1 также доступна здесь: https://hackage.haskell.org/package/generic-data-0.7.0.0/docs/Generic-Data.html#t:Generically1