Come funziona Haskell printf?


104

La sicurezza dei tipi di Haskell non è seconda a nessuno solo ai linguaggi tipizzati in modo dipendente. Ma c'è una profonda magia in corso con Text.Printf che sembra piuttosto instabile.

> printf "%d\n" 3
3
> printf "%s %f %d" "foo" 3.3 3
foo 3.3 3

Qual è la profonda magia dietro questo? Come può la Text.Printf.printffunzione accettare argomenti variadici come questo?

Qual è la tecnica generale utilizzata per consentire argomenti variadici in Haskell e come funziona?

(Nota a margine: una certa sicurezza di tipo è apparentemente persa quando si utilizza questa tecnica.)

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

15
È possibile ottenere solo un printf indipendente dai tipi utilizzando i tipi dipendenti.
agosto

9
Lennart ha perfettamente ragione. L'indipendenza dai tipi di Haskell è seconda alle lingue con tipi ancora più dipendenti di Haskell. Ovviamente, puoi rendere sicuro un tipo di oggetto simile a printf se scegli un tipo più informativo rispetto a String per il formato.
Pigworker

3
vedere oleg per più varianti di printf: okmij.org/ftp/typed-formatting/FPrintScan.html#DSL-In
sclv

1
@augustss È possibile ottenere solo un printf indipendente dai tipi utilizzando tipi dipendenti O MODELLO HASKELL! ;-)
MathematicalOrchid

3
@MathematicalOrchid Template Haskell non conta. :)
agosto

Risposte:


131

Il trucco sta nell'usare classi di tipo. Nel caso di printf, la chiave è la PrintfTypeclasse del tipo. Non espone alcun metodo, ma la parte importante è comunque nei tipi.

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

Quindi printfha un tipo di ritorno sovraccarico. Nel caso banale, non abbiamo argomenti extra, quindi dobbiamo essere in grado di istanziare rin IO (). Per questo, abbiamo l'istanza

instance PrintfType (IO ())

Successivamente, per supportare un numero variabile di argomenti, è necessario utilizzare la ricorsione a livello di istanza. In particolare, abbiamo bisogno di un'istanza in modo che se rè a PrintfType, anche un tipo di funzione x -> rè a PrintfType.

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

Naturalmente, vogliamo supportare solo argomenti che possono essere effettivamente formattati. È qui che PrintfArgentra in gioco la seconda classe di tipo . Quindi l'istanza reale è

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

Ecco una versione semplificata che accetta un numero qualsiasi di argomenti nella Showclasse e li stampa semplicemente:

{-# 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)

Qui, baresegue un'azione IO che viene costruita in modo ricorsivo fino a quando non ci sono più argomenti, a quel punto la eseguiamo semplicemente.

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

QuickCheck utilizza anche la stessa tecnica, dove la Testableclasse ha un'istanza per il caso base Boole una ricorsiva per le funzioni che accettano argomenti nella Arbitraryclasse.

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

Bella risposta. Volevo solo sottolineare che haskell sta cercando di capire il tipo di Foo in base agli argomenti applicati. Per capirlo, potresti voler specificare il tipo di esplicita Foo come segue: λ> (foo :: (Show x, Show y) => x -> y -> IO ()) 3 "hello"
redfish64

1
Anche se capisco come viene implementata la parte dell'argomento della lunghezza variabile, non capisco ancora come il compilatore rifiuta printf "%d" True. Questo è molto mistico per me, in quanto sembra che il valore di runtime (?) "%d"Venga decifrato in fase di compilazione per richiedere un file Int. Questo è assolutamente sconcertante per me. . . soprattutto perché il codice sorgente non usa cose come DataKindso TemplateHaskell(ho controllato il codice sorgente, ma non lo
capivo

2
@ThomasEding Il motivo per cui il compilatore rifiuta printf "%d" Trueè perché non esiste alcuna Boolistanza di PrintfArg. Se si passa un argomento del tipo sbagliato che non hanno un'istanza PrintfArg, lo fa di compilazione e genera un'eccezione in fase di esecuzione. Es:printf "%d" "hi"
Travis Sunderland
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.