2013-05-16 5 views
6

Nella mia semplice Haskell DSL, ho le seguenti funzioni per richiamare altre funzioni:Typeclass per le funzioni con un diverso numero di argomenti

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] 

vorrei generalizzare questo per qualsiasi numero di argomenti che utilizzano typeclasses.

Questo è quello che ho provato, ma funziona solo per 0 o 1 argomenti, e non impone funzioni di chiamata con il numero corretto di argomenti.

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

Ricorda che 'V a -> V b -> V c' è in realtà' V a -> (V b -> V c) '. Puoi farlo attraverso l'induzione. Caso base: 'V a' è callable. Caso induttivo: dato che 'V a' è callable, quindi' V b -> V a' è chiamabile. La testa di istanza del caso induttivo avrà la forma generale 'istanza Callable k => Callable (m -> k) dove'. –

risposta

9

La tecnica normale per le funzioni con più argomenti - come printf --is utilizzare un typeclass ricorsiva. Per printf, questo viene fatto con una classe chiamata PrintfType. L'intuizione importante è l'istanza ricorsiva:

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

Questo in sostanza dice che se è possibile restituire un PrintfType, la vostra funzione è anche un'istanza. Il "caso base" è quindi un tipo come String. Quindi, se si desidera chiamare printf con un argomento , si genera due istanze: PrintfType String e PrintfType (a -> r) dove r è String. Se si desidera due argomenti, va: String, (a -> r) dove r è String e (a -> r) dove r è il precedente (a -> r).

Tuttavia, il problema è in realtà un po 'più complesso. Si desidera disporre di un'istanza che gestisca le due attività due. Desideri che la tua istanza si applichi a funzioni di diverso tipo (ad esempio V (V a -> V b), V (V a -> V b -> V c) e così via) e che venga garantito il giusto numero di argomenti.

Il primo passo da fare è smettere di usare [String] per passare argomenti. Il tipo [String] perde informazioni su quanti valori ha, quindi non è possibile verificare che ci sia un numero appropriato di argomenti. Invece, dovresti usare un tipo per gli elenchi di argomenti che riflette quanti argomenti ha.

Questo tipo potrebbe sembrare qualcosa di simile:

data a :. b = a :. b 

è solo un tipo per la combinazione di due altri tipi, che possono essere utilizzati in questo modo:

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

Ora non vi resta che scrivere un typeclass con un'istanza ricorsiva che attraversa sia la lista degli argomenti di tipo livello sia la funzione stessa. Ecco uno schizzo autonomo molto approssimativo di ciò che intendo; dovrai adottarlo personalmente per il tuo problema specifico.

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 

Avrete anche per consentire un paio di estensioni: FlexibleInstances, FucntionalDepenedencies e UndecidableInstances.

È quindi possibile utilizzare in questo modo:

*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" 

Se si passa il numero errato di argomenti, si otterrà un errore di tipo. Certo, non è il messaggio di errore più bello del mondo! Detto questo, la parte importante dell'errore (Couldn't match type `()' with `[Char] :.()') fa evidenzia il problema principale (liste di argomenti che non corrispondono), che dovrebbe essere abbastanza facile da seguire.

*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" :.()) 

Si noti che questo potrebbe essere un po 'troppo complicato per il vostro compito particolare - io non sono convinto che sia la soluzione migliore al problema. Ma è un ottimo esercizio implementare invarianti di livello di testo più complicati usando alcune funzionalità più avanzate di tipizzazione.