В Haskell вы можете автоматически выводить Functor
, Foldable
и Traversable
, используя deriving
. Однако нет способа получить Applicative
. Учитывая, что существует один очевидный способ определить экземпляр Applicative
(который будет равен заархивированному приложению), нет ли способа включить deriving Applicative
?
Почему нет способа получить аппликативные функторы в Haskell?
Ответ 1
Нет, это совсем не очевидно. Сравните следующие экземпляры Applicative
:
[]
ZipList
Data.Sequence.Seq
, чье объявлениеApplicative
содержит несколько сотен строк.IO
(->) r
- Парсеры в
parsec
,attoparsec
,regex-applicative
. - Прокси-сервер в пакете
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