La seguridad de tipos de Haskell es insuperable solo para los lenguajes de escritura dependiente. Pero hay algo de magia profunda sucediendo con Text.Printf que parece bastante inestable .
> printf "%d\n" 3
3
> printf "%s %f %d" "foo" 3.3 3
foo 3.3 3
¿Cuál es la magia profunda detrás de esto? ¿Cómo puede elText.Printf.printf
función tomar argumentos variados como este?
¿Cuál es la técnica general utilizada para permitir argumentos variados en Haskell y cómo funciona?
(Nota al margen: aparentemente se pierde algún tipo de seguridad cuando se usa esta técnica).
> :t printf "%d\n" "foo"
printf "%d\n" "foo" :: (PrintfType ([Char] -> t)) => t
haskell
printf
variadic-functions
polyvariadic
Dan Burton
fuente
fuente
Respuestas:
El truco consiste en utilizar clases de tipos. En el caso de
printf
, la clave es laPrintfType
clase de tipo. No expone ningún método, pero la parte importante está en los tipos de todos modos.También
printf
tiene un tipo de retorno sobrecargado. En el caso trivial, no tenemos argumentos adicionales, por lo que debemos poder crearr
una instancia deIO ()
. Para esto, tenemos la instanciaA continuación, para admitir un número variable de argumentos, debemos utilizar la recursividad a nivel de instancia. En particular, necesitamos una instancia para que si
r
es aPrintfType
, un tipo de funciónx -> r
también sea aPrintfType
.Por supuesto, solo queremos admitir argumentos que realmente se puedan formatear. Ahí es donde
PrintfArg
entra en juego la segunda clase de tipos . Así que la instancia real esAquí hay una versión simplificada que toma cualquier número de argumentos en la
Show
clase y simplemente los imprime:Aquí,
bar
toma una acción IO que se construye de forma recursiva hasta que no hay más argumentos, momento en el que simplemente la ejecutamos.QuickCheck también usa la misma técnica, donde la
Testable
clase tiene una instancia para el caso baseBool
y una recursiva para funciones que toman argumentos en laArbitrary
clase.fuente
printf "%d" True
. Esto es muy místico para mí, ya que parece que el valor de tiempo de ejecución (?) Se"%d"
descifra en tiempo de compilación para requerir unInt
. Esto es absolutamente desconcertante para mí. . . especialmente porque el código fuente no usa cosas comoDataKinds
oTemplateHaskell
(revisé el código fuente, pero no loprintf "%d" True
es porque no hay unaBool
instancia dePrintfArg
. Si pasa un argumento del tipo equivocado de que no tiene una instancia dePrintfArg
, lo hace de compilación y se produce una excepción en tiempo de ejecución. Ej .:printf "%d" "hi"