Я думаю, что когда-то я уже спрашивал об этом на Haskell-Cafe, но черт побери, если я смогу найти ответ сейчас... Поэтому я задаю его снова здесь, чтобы надеяться, что в будущем я смогу найти ответ!
Haskell - фантастика в работе с параметрическим полиморфизмом. Но проблема в том, что не все является параметрическим. В качестве тривиального примера предположим, что мы хотим получить первый элемент данных из контейнера. Для параметрического типа это тривиально:
class HasFirst c where first :: c x -> Maybe x instance HasFirst [] where first [] = Nothing first (x:_) = Just x
Теперь попробуйте написать экземпляр для ByteString
. Не получится. Его тип не упоминает тип элемента. Вы также не можете написать экземпляр для Set
, потому что он требует ограничения Ord
- но глава класса не упоминает тип элемента, поэтому вы не можете его ограничить.
Ассоциированные типы предоставляют изящный способ полностью устранить эти проблемы:
class HasFirst c where type Element c :: * first :: c -> Maybe (Element c) instance HasFirst [x] where type Element [x] = x first [] = Nothing first (x:_) = Just x instance HasFirst ByteString where type Element ByteString = Word8 first b = b ! 0 instance Ord x => HasFirst (Set x) where type Element (Set x) = x first s = findMin s
Однако теперь у нас появилась новая проблема. Рассмотрим попытку "исправить" Functor
так, чтобы он работал для всех типов контейнеров:
class Functor f where type Element f :: * fmap :: (Functor f2) => (Element f -> Element f2) -> f -> f2
Это совсем не работает. Здесь говорится, что если у нас есть функция от типа элемента f
к типу элемента f2
, то мы можем превратить f
в f2
. Пока все хорошо. Однако, очевидно, нет никакого способа потребовать, чтобы f
и f2
были одним и тем же видом контейнера!
Согласно существующему определению Functor
, мы имеем
fmap :: (x -> y) -> [x] -> [y] fmap :: (x -> y) -> Seq x -> Seq y fmap :: (x -> y) -> IO x -> IO y
Но мы не имеем fmap :: (x -> y) -> IO x -> [y]
. Это совершенно невозможно. Но приведенное выше определение класса позволяет это сделать.
Кто-нибудь знает, как объяснить системе типов, что я на самом деле имел в виду?
Edit
Вышеописанное работает, определяя способ вычисления типа элемента из типа контейнера. Что произойдет, если вы попытаетесь сделать это наоборот? Определить функцию для вычисления типа контейнера из типа элемента? Получится ли это проще?