2013-05-16 1 views
6

В моем простом Haskell DSL, У меня есть следующие функции для вызова других функций:для функций класс типов с различным числом аргументов

callF :: forall a. (Typeable a) 
    => (V a) -> (V a) 
callF [email protected](V (FunP name)) = 
    pack $ FunAppl (prettyV fp) [] 

callF1 :: forall a b. (Typeable a, Typeable b) 
    => (V (V a -> V b)) -> V a -> (V b) 
callF1 [email protected](V (FunP name)) arg = 
    pack $ FunAppl (prettyV fp) [prettyV arg] 

callF2 :: forall a b c. (Typeable a, Typeable b, Typeable c) 
    => (V (V a -> V b -> V c)) -> V a -> V b -> (V c) 
callF2 [email protected](V (FunP name)) arg1 arg2 = 
    pack $ FunAppl (prettyV fp) [prettyV arg1, prettyV arg2] 

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

Это то, что я пробовал, но он работает только для 0 или 1 аргументов, и он не обеспечивает выполнение функций вызова с правильным количеством аргументов.

class Callable a b | a -> b where 
    call :: V a -> [String] -> b 

instance Callable a (V b) where 
    call [email protected](V (FunP name)) x = pack $ FunAppl (prettyV fp) x 

instance (Typeable c, Typeable d) => Callable (V a -> V b) (V c -> V d) where 
    call [email protected](V (FunP name)) list arg = call fun (list ++ [prettyV arg]) 
+2

Имейте в виду, что 'V a -> V b -> V c' на самом деле' V a -> (V b -> V c) '. Вы можете сделать это через индукцию. Базовый регистр: 'V a' является вызываемым. Индуктивный случай: при условии, что 'V a' является вызываемым, тогда' V b -> V a' является вызываемым. Глава экземпляра индуктивного случая будет иметь общую форму 'instance Callable k => Callable (m -> k) где'. –

ответ

9

Нормальная техника для функций с несколькими аргументами - как printf --is использовать рекурсивный класс типов. Для printf это делается с классом PrintfType. Важным является понимание рекурсивный экземпляр:

(PrintfArg a, PrintfType r) => PrintfType (a -> r) 

Это в основном говорит, что если вы можете вернуть PrintfType, ваша функция также экземпляр. «Базовый регистр» - это тип типа String. Итак, если вы хотите позвонить printf с один аргумент, он запускает два экземпляра: PrintfType String и PrintfType (a -> r), где r - String. Если вы хотите два аргументов, он идет: String, (a -> r) где r является String и (a -> r) где r является предыдущей (a -> r).

Однако ваша проблема на самом деле немного сложнее. Вы хотите иметь экземпляр, который обрабатывает две задачи. Вы хотите, чтобы ваш экземпляр применим к функциям разных типов (например, V (V a -> V b), V (V a -> V b -> V c) и т. Д.), А также для обеспечения правильного количества аргументов.

Первым шагом для этого является прекращение использования аргументов [String]. Тип [String] теряет информацию о том, сколько значений у него есть, поэтому вы не можете проверить наличие соответствующего количества аргументов. Вместо этого вы должны использовать тип для списков аргументов, который отражает количество аргументов.

Этот тип может выглядеть примерно так:

data a :. b = a :. b 

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

"foo" :. "bar"   :: String :. String 
"foo" :. "bar" :. "baz" :: String :. String :. String 

Теперь вам просто нужно написать typeclass с рекурсивным экземпляром, который пересекает как список аргументов типа, так и сама функция. Вот очень грубый автономный эскиз того, что я имею в виду; вам придется принять его к вашей конкретной проблеме самостоятельно.

infixr 8 :. 
data a :. b = a :. b 

class Callable f a b | f -> a b where 
    call :: V f -> a -> b 

instance Callable rf ra (V rb) => Callable (String -> rf) (String :. ra) (V rb) where 
    call (V f) (a :. rest) = call (V (f a)) rest 

instance Callable String() (V String) where 
    call (V f)() = V f 

Вы также должны включить несколько расширений: FlexibleInstances, FucntionalDepenedencies и UndecidableInstances.

Вы могли бы использовать его как это:

*Main> call (V "foo")() 
V "foo" 
*Main> call (V (\ x -> "foo " ++ x)) ("bar" :.()) 
V "foo bar" 
*Main> call (V (\ x y -> "foo " ++ x ++ y)) ("bar" :. " baz" :.()) 
V "foo bar baz" 

Если вы передаете в неправильном количестве аргументов, вы получите ошибку типа. По общему признанию, это не самое приятное сообщение об ошибке в мире! Тем не менее, важная часть ошибки (Couldn't match type `()' with `[Char] :.()') делает, указывая на основную проблему (списки аргументов, которые не совпадают), что должно быть достаточно простым, чтобы следовать.

*Main> call (V (\ x -> "foo " ++ x)) ("bar" :. "baz" :.()) 

<interactive>:101:1: 
    Couldn't match type `()' with `[Char] :.()' 
    When using functional dependencies to combine 
     Callable String() (V String), 
     arising from the dependency `f -> a b' 
     in the instance declaration at /home/tikhon/Documents/so/call.hs:16:14 
     Callable [Char] ([Char] :.()) (V [Char]), 
     arising from a use of `call' at <interactive>:101:1-4 
    In the expression: 
     call (V (\ x -> "foo " ++ x)) ("bar" :. "baz" :.()) 
    In an equation for `it': 
     it = call (V (\ x -> "foo " ++ x)) ("bar" :. "baz" :.()) 

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