A segurança de tipos de Haskell é incomparável apenas em relação às linguagens com tipos dependentes. Mas existe uma magia profunda acontecendo com Text.Printf que parece um tanto quanto duvidosa .
> printf "%d\n" 3
3
> printf "%s %f %d" "foo" 3.3 3
foo 3.3 3
Qual é a magia profunda por trás disso? Como a Text.Printf.printf
função pode aceitar argumentos variados como este?
Qual é a técnica geral usada para permitir argumentos variáveis em Haskell e como isso funciona?
(Nota lateral: algum tipo de segurança é aparentemente perdido ao usar esta técnica.)
> :t printf "%d\n" "foo"
printf "%d\n" "foo" :: (PrintfType ([Char] -> t)) => t
haskell
printf
variadic-functions
polyvariadic
Dan Burton
fonte
fonte
Respostas:
O truque é usar classes de tipo. No caso de
printf
, a chave é aPrintfType
classe de tipo. Ele não expõe nenhum método, mas de qualquer maneira a parte importante está nos tipos.Então,
printf
tem um tipo de retorno sobrecarregado. No caso trivial, não temos argumentos extras, então precisamos ser capazes de instanciarr
paraIO ()
. Para isso, temos a instânciaEm seguida, para suportar um número variável de argumentos, precisamos usar a recursão no nível da instância. Em particular, precisamos de uma instância para que se
r
for aPrintfType
, um tipo de funçãox -> r
também seja aPrintfType
.Claro, queremos apenas suportar argumentos que possam realmente ser formatados. É aí que
PrintfArg
entra a segunda classe de tipo . Portanto, a instância real éEsta é uma versão simplificada que pega qualquer número de argumentos na
Show
classe e apenas os imprime:Aqui,
bar
executa uma ação IO que é construída recursivamente até que não haja mais argumentos, momento em que simplesmente a executamos.QuickCheck também usa a mesma técnica, onde a
Testable
classe tem uma instância para o caso baseBool
e uma recursiva para funções que recebem argumentos daArbitrary
classe.fonte
printf "%d" True
. Isso é muito místico para mim, pois parece que o valor do tempo de execução (?) É"%d"
decifrado em tempo de compilação para exigir umInt
. Isso é absolutamente desconcertante para mim. . . especialmente porque o código-fonte não usa coisas comoDataKinds
ouTemplateHaskell
(eu verifiquei o código-fonte, mas não o compreendi.)printf "%d" True
é porque não háBool
instância dePrintfArg
. Se você passar um argumento do tipo errado de que não tem uma instânciaPrintfArg
, ele faz de compilação e lança uma exceção em tempo de execução. Ex:printf "%d" "hi"