2013-07-17 5 views
4

ПроблемуТип вывода экземпляра функции

Я хочу, чтобы иметь возможность создать 2 data types: A и B и создать 2 функции f:

  • f :: A -> Int -> Int
  • f :: B -> String -> String -> String

Единственный w Я могу это сделать (насколько я знаю) использовать type classes и instances.

Проблема в том, что я не хочу явно писать f подписи - я хочу, чтобы тип checker сделал это для меня. Является ли это возможным?

Пример кода

{-# LANGUAGE FlexibleInstances, FunctionalDependencies, UndecidableInstances #-} 

data A = A{ax::Int} deriving(Show) 
data B = B{bx::Int} deriving(Show) 
data C = C{cx::Int} deriving(Show) 

-- I don't want to explicit say the signature is Int->Int 
-- I would love to write: 
-- instance Func_f A (a->b) where 
instance Func_f A (Int->Int) where 
    f _ i = i*2 

-- I don't want to explicit say the signature is String->String->String 
-- I would love to write: 
-- instance Func_f B (a->b->c) where 
instance Func_f B (String->String->String) where 
    f _ s1 s2 = "test"++s1++s2 

-- I don't want to explicit say the signature is a->a 
-- I would love to write: 
-- instance Func_f C (a->b) where 
instance Func_f C (a->a) where 
    f _ i = i 

class Func_f a b | a -> b where 
    f :: a -> b 

f2 _ s1 s2 = "test"++s1++s2 -- Here the type inferencer automaticly recognizes the signature 

main :: IO() 
main = do 
    let 
     a = A 1 
     b = B 2 
     c = C 3 
     a_out = f a 5 
     b_out = f b "a" "b" 
     c_out = c 6 

    print a_out 
    print b_out 
    print c_out 

Explaination

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

Если я пишу функцию, как f2 _ s1 s2 = "test"++s1++s2, я делаю , а не должен явно писать свою подпись - потому что компилятор может ее вывести. Можем ли мы каким-то образом попросить компилятор вывести подписи f в приведенном выше примере?

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

+0

Невозможно (что я знаю) указать тип параметра класса типа, выведенного из данной реализации класса. Это может быть сложно, так как ваш первый пример может быть выведен для того, чтобы иметь тип 'f :: (Num a) => A -> a -> a'. Будет ли какая-либо из реализаций 'f' использовать первый аргумент или просто используется, чтобы определить, какую версию' f' использовать? – sabauma

+0

@sabauma - я знаю это - но я действительно не хочу делать очень подробный тип типа 'Int-> Int' - я был бы счастлив, если бы Haskell вывел бы его как' f :: (Num a) => A -> a -> a' или что-то еще - если я могу позже использовать его (если это возможно). Иногда можно использовать первый аргумент - вы можете думать об этом как «это» в императивных языках. –

+0

@sabauma - я отредактировал пример - просмотрите комментарии. –

ответ

5

Вот один из способов такого рода работ. Есть еще много случаев для покрытия, если ваш fA fB имеет переменные типа в своих предполагаемых типах. В этом случае следующий код будет иметь некоторые ошибки совпадения шаблонов во время компиляции.

{-# LANGUAGE FlexibleInstances, FunctionalDependencies, TemplateHaskell #-} 

import Language.Haskell.TH 

data A = A{ax::Int} deriving(Show) 
data B = B{bx::Int} deriving(Show) 

fA A{} i = i*(2 :: Int) 

fB B{} s1 s2 = "test"++s1++s2 

class Func_f a b | a -> b where 
    f :: a -> b 

let 
    getLetter (AppT (AppT _ x) _) = x 
    getSnd (AppT x y) = y 
    mkInst name0 = do 
     (VarI n ty _ _) <- reify name0 
     fmap (:[]) $ instanceD (return []) 
      [t| Func_f 
        $(return $ getLetter ty) 
        $(return $ getSnd ty) |] 
      [valD (varP 'f) (normalB (varE name0)) []] 

    in fmap concat $ mapM mkInst ['fB, 'fA] 


main :: IO() 
main = do 
    let 
     a = A 1 
     b = B 2 
     a_out = f a 5 
     b_out = f b "a" "b" 

    print a_out 
    print b_out 

Это еще один вопрос, связанный с языком, который вы компилируете в haskell.


Добавлены подсказки

Если тип полиморфные, вы увидите материализовать давая что-то, что не подпадает под мой пример кода выше.

> :set -XTemplateHaskell 
> :m +IPPrint Language.Haskell.TH 
> putStrLn $(reify 'id >>= stringE . pshow) 

Печатает то, что описывает (а-> а):

VarI GHC.Base.id 
    (ForallT [PlainTV a_1627394484] [] 
    (AppT (AppT ArrowT (VarT a_1627394484)) (VarT a_1627394484))) 
    Nothing 
    (Fixity 9 InfixL) 

С небольшим количеством работы, вы можете разделить этот тип там в CxtQ и , что типа ц инстансовых потребности.

Я не знаю, сколько смысла он производит для создания (a-> b).Вы могли бы пойти о замене всех переменных типа новыми уникальными, что, вероятно, лучше всего сделано с чем-то вроде Data.Generics.everywhereM, потому что у данных Type много многих конструкторов.

Вы все еще есть вопрос, что является недопустимым:

instance Func_f (a -> b) where 
    f _ = id 

Этот подход к получению (а-> б) может сделать ваш сбой программы:

instance Func_f (a -> b) where 
    f _ = unsafeCoerce id 

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

instance (a~b) => Func_f (a->b) where 
    f _ = unsafeCoerce id 
+0

спасибо за этот пример - он работает очень хорошо. Не могли бы вы рассказать мне, возможно ли каким-то образом использовать эту технику, чтобы также поддерживать функции, которые не строго типизированы, например 'fA A {} i = i'? –

+0

Я отредактировал вопрос - добавил тип данных 'C' и соответствующий экземпляр, чтобы лучше показать проблему. Если бы вы могли использовать вас с такими функциями, как 'fA A {} i = i', это было бы для меня спасением :) –

+0

Я добавил еще несколько советов для получения этого C (или вашего оригинала A) : template haskell не известен тем, что вам нравится, поэтому я оставил вас с выполнением написания этого кода;) – aavogt

Смежные вопросы