2015-06-16 5 views
28

Я часто сталкиваюсь с этой ситуацией, так как это раздражает.Как избежать написания этого типа шаблона Haskell

Скажем, у меня есть тип суммы, которая может содержать экземпляр x или кучу других вещей, не связанных с x -

data Foo x = X x | Y Int | Z String | ...(other constructors not involving x) 

Чтобы объявить экземпляр Functor я должен это сделать -

instance Functor Foo where 
    fmap f (X x) = X (f x) 
    fmap _ (Y y) = Y y 
    fmap _ (Z z) = Z z 
    ... And so on 

Принимая во внимание то, что я хотел бы сделать это -

instance Functor Foo where 
    fmap f (X x) = X (f x) 
    fmap _ a = a 

Т.е. я только забочусь о конструкторе X, все остальные конструкторы просто «пройдены». Но, конечно, это не скомпилировалось, потому что a с левой стороны - это другой тип от a в правой части уравнения.

Есть ли способ, которым я могу избежать написания этого шаблона для других конструкторов?

+0

Вы можете написать сообщение об ошибке? – AJFarmar

+0

Это всего лишь пример, и фактический код намного сложнее, поэтому я не имею точного сообщения об ошибке. Но легко увидеть ошибку - Тип fmap должен быть '(a -> b) -> Foo a -> Foo b' Но последнее уравнение имеет' fmap fa = a', где 'f: :(a-> b) 'и' a :: Foo a', подразумевая, что тип fmap является '(a-> b) -> Foo a -> Foo a'. Поэтому в сообщении об ошибке говорится, что 'a' и' b' не могут быть унифицированы. –

+0

@AJFarmar rhs имеет другой тип, чем lhs. GHC знает, что типы проверяются, только если мы выписываем конструкторы. –

ответ

10

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

Мы можем различать два случая.

В простейшем случае тип данных не является рекурсивным. Здесь prisms являются фитинга решение:

{-# LANGUAGE TemplateHaskell #-} 

import Control.Lens 

data Foo x y = X x | Y y | Z String 

makePrisms ''Foo 

mapOverX :: (x -> x') -> Foo x y -> Foo x' y 
mapOverX = over _X 

Если наши данные рекурсивно, то все становится более сложным. Теперь makePrisms не создает приемопередатчиков, изменяющих тип. Мы можем избавиться от рекурсии в определении, разложив ее на явную фиксированную точку.Таким образом, наши призмы остаются типа изменения:

import Control.Lens 

newtype Fix f = Fix {out :: f (Fix f)} 

-- k marks the recursive positions 
-- so the original type would be "data Foo x y = ... | Two (Foo x y) (Foo x y)" 
data FooF x y k = X x | Y y | Z String | Two k k deriving (Functor) 

type Foo x y = Fix (FooF x y) 

makePrisms ''FooF 

mapOverX :: (x -> x') -> Foo x y -> Foo x' y 
mapOverX f = 
    Fix .    -- rewrap 
    over _X f .   -- map f over X if possible 
    fmap (mapOverX f) . -- map over recursively 
    out     -- unwrap 

Или мы можем вынесем снизу вверх преобразование:

cata :: (Functor f) => (f a -> a) -> Fix f -> a 
cata f = go where go = f . fmap go . out 

mapOverX :: (x -> x') -> Foo x y -> Foo x' y 
mapOverX f = cata (Fix . over _X f) 

Там в ощутимой литературе по использованию твердых точек функторов для обобщенного программирования, а также количество библиотек, например this или this. Возможно, вы захотите найти «схемы рекурсии» для дальнейших ссылок.

+0

'over' идеально! Черт возьми, мне нравится библиотека линз. –

+0

Это также работает для 'Traversable':' traverse = _X'. – dfeuer

12

Существует два основных простых решения.

Во-первых, для простых типов, только deriving (Functor) он использует необходимое расширение.

Другим решением является определение другого типа данных:

data Bar = S String | B Bool | I Int -- "Inner" type 
data Foo a = X a | Q Bar    -- "Outer" type 

instance Functor Foo where 
    fmap f (X a) = X (f a) 
    fmap _ (Q b) = Q b -- `b' requires no type change. 

Таким образом, вы можете написать еще одну строку, чтобы удалить много.

Это не идеальный вариант для сопоставления с образцом, но он хотя бы решает эту проблему.

8

Похож на работу на призмы.

Отказ от ответственности: Я новичок в объективе/призме.

{-# LANGUAGE TemplateHaskell #-} 

import Control.Lens 
import Control.Lens.Prism 

data Foo x = X x | Y Int | Z String deriving Show 

makePrisms ''Foo 

instance Functor Foo where 
    -- super simple impl, by András Kovács 
    fmap = over _X 
    -- My overly complicated idea 
    -- fmap f = id & outside _X .~ (X . f) 
    -- Original still more complicated implementation below 
    --  fmap f (X x) = X (f x) 
    --  fmap _ a = id & outside _X .~ undefined $ a 

Использование:

*Main> fmap (++ "foo") (Y 3) 
Y 3 
*Main> fmap (++ "foo") (X "abc") 
X "abcfoo" 
+4

'fmap = over _X' будет приятнее. –

+1

@ AndrásKovács Я сказал, что я новичок, 'over' для меня новый. Я подозревал, что подобное должно существовать, но не знало, где искать. –

+1

, если бы вы могли включить чистую версию без всяких подробных комментариев и старого кода. –

3

В основном для полноты картины, вот еще способ сделать это:

import Unsafe.Coerce 

instance Functor Foo where 
    fmap f (X x) = X (f x) 
    fmap _ a = unsafeCoerce a 

в ситуации, описанной вами, это будет фактически безопасное использование unsafeCoere. Но есть веские причины, чтобы этого избежать:

  • Безопасность зависит от того, как GHC собирает структуры данных и код; знание, которое нормальный программист не должен иметь.
  • Он также не является надежным: если тип данных расширен с помощью нового конструктора X 'x, предупреждение не будет генерироваться, потому что catch-all делает это определение исчерпывающим, и тогда все будет идти. (ТНХ @gallais для этого комментария)

Таким образом, это решение, безусловно, не рекомендуется.

+2

Он также не является надежным: если тип данных расширен с помощью нового конструктора 'X 'x', предупреждение не будет генерироваться, потому что catch-all делает это определение исчерпывающим, и тогда все будет идти. – gallais

+0

Спасибо, что упомянул об этом. Это образовательный, хотя это плохая идея! Я попробовал «принуждение», но это не работает в разных типах. Я предположил, что unsafeCoerce также не будет работать, потому что GHC будет представлять два типа по-разному, но я думаю, что я ошибся. –

+0

@ gallais хорошая вещь! –