haskell printf variadic-functions polyvariadic

¿Cómo funciona Haskell printf?



variadic-functions polyvariadic (1)

El truco es usar clases de tipo. En el caso de printf , la clave es la clase de tipo PrintfType . No expone ningún método, pero la parte importante está en los tipos de todos modos.

class PrintfType r printf :: PrintfType r => String -> r

Entonces printf tiene un tipo de devolución sobrecargado. En el caso trivial, no tenemos argumentos adicionales, por lo que debemos poder instanciar r a IO () . Para esto, tenemos la instancia

instance PrintfType (IO ())

A continuación, para admitir un número variable de argumentos, necesitamos usar la recursión en el nivel de instancia. En particular, necesitamos una instancia para que si r es un PrintfType , una función tipo x -> r también sea PrintfType .

-- instance PrintfType r => PrintfType (x -> r)

Por supuesto, solo queremos admitir argumentos que en realidad pueden formatearse. Ahí es donde entra la segunda clase tipo PrintfArg . Entonces, la instancia real es

instance (PrintfArg x, PrintfType r) => PrintfType (x -> r)

Aquí hay una versión simplificada que toma cualquier cantidad de argumentos en la clase Show y simplemente los imprime:

{-# LANGUAGE FlexibleInstances #-} foo :: FooType a => a foo = bar (return ()) class FooType a where bar :: IO () -> a instance FooType (IO ()) where bar = id instance (Show x, FooType r) => FooType (x -> r) where bar s x = bar (s >> print x)

Aquí, bar toma una acción IO que se construye recursivamente hasta que no haya más argumentos, en cuyo punto simplemente lo ejecutamos.

*Main> foo 3 :: IO () 3 *Main> foo 3 "hello" :: IO () 3 "hello" *Main> foo 3 "hello" True :: IO () 3 "hello" True

QuickCheck también usa la misma técnica, donde la clase Testable tiene una instancia para el caso base Bool , y una recursiva para funciones que toman argumentos en la clase Arbitrary .

class Testable a instance Testable Bool instance (Arbitrary x, Testable r) => Testable (x -> r)

La seguridad de tipo de Haskell es insuperable solo para los idiomas de tipo dependiente. Pero hay algo de magia profunda pasando con Text.Printf que parece algo poco Text.Printf .

> 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 la función Text.Printf.printf 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: al parecer, se pierde algún tipo de seguridad al usar esta técnica).

> :t printf "%d/n" "foo" printf "%d/n" "foo" :: (PrintfType ([Char] -> t)) => t