Ответ 1
Ну, проблема в том, что неясно, что означает пересмотренный Functor
. Например, рассмотрим ByteString
. A ByteString
может отображаться только путем замены каждого элемента Word8
на элемент того же типа. Но Functor
- для параметрических отображаемых структур. На самом деле существуют два противоречивых понятия отображения:
- Жесткое отображение (т.е. преобразование каждого элемента структуры без изменения его типа)
- Параметрическое отображение (т.е. преобразование каждого элемента структуры в любой тип)
Итак, в этом случае вы не можете объяснить системе типов, что вы имели в виду, потому что это не имеет большого смысла. Вы можете, однако, изменить то, что вы имеете в виду:)
Жесткое отображение легко выразить с помощью семейств типов:
class RigidMap f where
type Element f :: *
rigidMap :: (Element f -> Element f) -> f -> f
Что касается параметрического отображения, существует множество способов сделать это. Самый простой способ - сохранить текущий Functor
как есть. Вместе эти классы охватывают такие структуры, как ByteString
, []
, Seq
и т.д. Однако все они падают на Set
и Map
из-за ограничения Ord
на значения. К счастью, расширение в GHC 7.4 позволяет решить эту проблему:
class RFunctor f where
type Element f a :: Constraint
type Element f a = () -- default empty constraint
fmap :: (Element f a, Element f b) => (a -> b) -> f a -> f b
Здесь мы говорим, что каждый экземпляр должен иметь связанное ограничение типа. Например, экземпляр Set будет иметь Element Set a = Ord a
, чтобы обозначить, что Set
можно построить, только если для типа доступен экземпляр Ord
. Все, что может появиться в левой части =>
, принимается. Мы можем определить наши предыдущие экземпляры точно так, как они были, но мы также можем сделать Set
и Map
:
instance RFunctor Set where
type Element Set a = Ord a
fmap = Set.map
instance RFunctor Map where
type Element Map a = Ord a
fmap = Map.map
Однако, это довольно раздражает необходимость использования двух отдельных интерфейсов для жесткого сопоставления и ограниченного параметрического отображения. На самом деле, не последнее ли обобщение первого? Рассмотрим разницу между Set
, которая может содержать только экземпляры Ord
и ByteString
, которые могут содержать только Word8
s. Неужели мы можем выразить это как просто другое ограничение?
Мы применяем тот же трюк, сделанный с помощью HasFirst
(т.е. даем экземпляры всей структуры и используем семейство типов для раскрытия типа элемента) и вводим новое связанное семейство ограничений:
class Mappable f where
type Element f :: *
type Result f a r :: Constraint
map :: (Result f a r) => (Element f -> a) -> f -> r
Идея здесь заключается в том, что Result f a r
выражает ограничения, необходимые для типа значения (например, Ord a
), а также ограничивает результирующий тип контейнера, но ему нужно; предположительно, чтобы гарантировать, что он имеет тип контейнера такого же типа a
s. Например, Result [a] b r
предположительно потребует, чтобы r
был [b]
, а Result ByteString b r
потребовал, чтобы b
был Word8
, а r
- ByteString
.
Типичные семейства уже дают нам то, что нам нужно, чтобы выразить "есть" здесь: ограничение равенства типа. Мы можем сказать, что (a ~ b) => ...
требует, чтобы a
и b
были одного типа. Разумеется, мы можем использовать это в определениях семейств ограничений. Итак, у нас есть все, что нам нужно; на экземпляры:
instance Mappable [a] where
type Element [a] = a
type Result [a] b r = r ~ [b]
-- The type in this case works out as:
-- map :: (r ~ [b]) => (a -> b) -> [a] -> r
-- which simplifies to:
-- map :: (a -> b) -> [a] -> [b]
map = Prelude.map
instance Mappable ByteString where
type Element ByteString = Word8
type Result ByteString a r = (a ~ Word8, r ~ ByteString)
-- The type is:
-- map :: (b ~ Word8, r ~ ByteString) => (Word8 -> b) -> ByteString -> r
-- which simplifies to:
-- map :: (Word8 -> Word8) -> ByteString -> ByteString
map = ByteString.map
instance (Ord a) => Mappable (Set a) where
type Element (Set a) = a
type Result (Set a) b r = (Ord b, r ~ Set b)
-- The type is:
-- map :: (Ord a, Ord b, r ~ Set b) => (a -> b) -> Set a -> r
-- (note the (Ord a) constraint from the instance head)
-- which simplifies to:
-- map :: (Ord a, Ord b) => (a -> b) -> Set a -> Set b
map = Set.map
Perfect! Мы можем определить экземпляры для любого типа контейнера, который мы хотим, жесткого, параметрического или параметрического, но ограниченного, и типы отлично работают.
Отказ от ответственности: я еще не пробовал GHC 7.4, поэтому я не знаю, действительно ли какой-либо из них действительно компилируется или работает, но я думаю, что основные идеи звучат.