2014-12-28 4 views
3

Связано с this question Я спросил сегодня.Производя экземпляр functor, а не аргумент последнего типа

У меня есть тип AST данных с большим числом случаев, которое спараметрированное с помощью «аннотаций» типа

data Expr ann def var = Plus a Int Int 
    | ... 
    | Times a Int Int 
    deriving (Data, Typeable, Functor) 

Я получил конкретные примеры для четкости и вара, скажу Def и Var.

Я хочу, чтобы автоматически выводить fmap, который работает как функтор по первому аргументу. Я хочу, чтобы получить функцию, которая выглядит следующим образом:

fmap :: (a -> b) -> (Expr a Def Var) -> (Expr b Def Var) 

Когда я использую нормальный fmap, я получаю сообщение компилятора, который указывает БПМЖ пытается применить свои функции до последнего типа аргумента, а не первым.

Есть ли способ, который я могу получить, как описано, без написания кучи шаблона? Я попытался сделать это:

newtype Expr' a = E (Expr a Def Var) 
    deriving (Data, Typeable, Functor) 

Но я получаю следующее сообщение об ошибке:

Constructor `E' must use the type variable only as the last argument of a data type 

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

+1

Вы можете сделать безобразный хак, где вы определить другой, одинаковый тип с аргументами типа в правильный порядок, выведите экземпляр Functor для этого, а затем используйте 'unsafeCoerce'. Это действительно очень плохо, и я не рекомендую его.Самый простой вариант - сворачивать свои собственные функции вывода; или просто реорганизовать существующий код. Если вы не возражаете потянуть большую зависимость, посмотрите на пакет [получить] (http://hackage.haskell.org/package/derive-2.5.5/docs/Data-Derive-Functor.html) , – user2407038

+2

'Expr ann def' не может иметь экземпляр« Functor », если' var' - это переменная типа, которая изменяется с помощью 'fmap'. – Cirdec

+1

Жаль, что для пакета 'hask' нет функции типа DeriveFunctor. Пакет 'hask' предоставляет, среди прочего, поликинный класс типа« Functor », который способен« fmap »поверх переменных типа глубже первого (до тех пор, пока все переменные типа справа от него имеют« Functor » а также, похоже, в этом случае было бы тривиально верно). –

ответ

3

Короткий ответ: это невозможно, потому что Functor требует, чтобы переменная изменяемого типа находилась в последней позиции. Только конструкторы типов вида * -> * могут иметь Functor экземпляров, а ваш Expr не имеет такого типа.

Вам действительно нужен экземпляр Functor? Если вы просто хотите избежать шаблона написания fmap-подобной функции, что-то вроде SYB - лучшее решение (но на самом деле шаблон шаблона не так уж плох, и вы только пишете его один раз).

Если вам нужно Functor по какой-либо другой причине (возможно, вы хотите использовать эту структуру данных в некоторой функции с ограничением Functor), вам нужно будет выбрать, хотите ли вы, чтобы экземпляр или переменные типа в текущем порядке.

2

Вы можете использовать синоним типа для минимизации изменений в исходный код:

data Expr' def var ann = Plus a Int Int -- change this to Expr', correct order 
    | ... 
    | Something (Expr ann def var)  -- leave this as it is, with the original order 
    deriving (Data, Typeable, Functor) 

type Expr ann def var = Expr' def var ann 

Остальная часть кода может продолжать использовать Expr без изменений. Единственными исключениями являются экземпляры класса, такие как Functor, которые, как вы заметили, требуют определенного порядка в параметрах. Надеюсь, Functor - единственный такой класс, в котором вы нуждаетесь.

автоматического происхождения fmap функция имеет тип

fmap :: (a -> b) -> Expr' def var a -> Expr' def var b 

, который может быть записан в виде

fmap :: (a -> b) -> Expr a def var -> Expr b def var 
+1

К сожалению, это не так полезно, как кажется. Синонимы типов не могут быть частично применены, поэтому вы никогда не сможете использовать 'Expr ann def' в любом месте (за исключением, возможно, расширения' LiberalTypeSynonyms', но есть все еще ограничения). Можно использовать новый тип, но это все еще не решает основной проблемы OP, которая хочет получить функцию fmap-like, не записывая ее вручную. –