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.
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'. –