Як працює Haskell printf?


104

Безпека типів Haskell є другою ніхто НЕ тільки до залежного від типізованих мов. Але з Text.Printf відбувається деяка глибока магія, яка здається досить хитрою .

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

У чому полягає глибока магія цього? Як Text.Printf.printfфункція може приймати подібні різні аргументи?

Який загальний прийом використовується для врахування різних аргументів у Haskell, і як це працює?

(Бічна примітка: певна безпека типу очевидно втрачається при використанні цієї методики.)

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

15
Ви можете отримати безпечний друк типу типу лише за допомогою залежних типів.
серпень

9
Леннарт цілком прав. Безпека типу Haskell є другою для мов з ще більш залежними типами, ніж Haskell. Звичайно, ви можете зробити тип речі, що нагадує printf, безпечним, якщо для формату виберете більш інформативний тип, ніж String.
свинарник

3
див. oleg для кількох варіантів printf: okmij.org/ftp/typed-formatting/FPrintScan.html#DSL-In
sclv

1
@augustss Ви можете отримати безпечний для друку тип друку лише за допомогою залежних типів АБО ТЕМПЛАТУВАТИ ХАСКЕЛЬ! ;-)
MathematicalOrchid

3
@MathematicalOrchid Шаблон Haskell не враховується. :)
серпень

Відповіді:


131

Хитрість полягає у використанні класів типів. У випадку з printfключовим є PrintfTypeклас типу. Він не викриває жодних методів, але важлива частина все-таки є у типах.

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

Таким чином, printfмає перевантажений тип повернення. У тривіальному випадку, у нас немає ніяких додаткових аргументів, тому ми повинні бути в змозі створити екземпляр rв IO (). Для цього у нас є екземпляр

instance PrintfType (IO ())

Далі, щоб підтримувати змінну кількість аргументів, нам потрібно використовувати рекурсію на рівні примірника. Зокрема, нам потрібен екземпляр, щоб, якщо rє a PrintfType, тип функції x -> rтакож є a PrintfType.

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

Звичайно, ми хочемо лише підтримати аргументи, які насправді можуть бути відформатовані. Тут PrintfArgнадходить клас другого типу . Отже, власне екземпляр є

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

Ось спрощена версія, яка бере будь-яку кількість аргументів у Showкласі та просто роздруковує їх:

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

Тут barробиться дія вводу-виводу, яка створюється рекурсивно, поки не буде більше аргументів, і тоді ми просто виконуємо її.

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

QuickCheck також використовує ту саму методику, коли Testableклас має екземпляр для базового випадку Bool, а рекурсивний - для функцій, які беруть аргументи в Arbitraryкласі.

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

Чудова відповідь. Я просто хотів зазначити, що haskell з'ясовує тип Foo на основі застосованих аргументів. Щоб зрозуміти це, ви, можливо, захочете вказати тип виразності Foo наступним чином: λ> (foo :: (Показати x, показати y) => x -> y -> IO ()) 3 "привіт"
redfish64

1
Хоча я розумію, як реалізована частина аргументу змінної довжини, я все ще не розумію, як компілятор відкидає printf "%d" True. Для мене це дуже містично, оскільки, здається, значення виконання (?) "%d"Розшифровується під час компіляції, щоб зажадати an Int. Це мене абсолютно бентежить. . . тим більше, що вихідний код не використовує такі речі, як ( DataKindsабо TemplateHaskellя перевірив вихідний код, але не зрозумів його)
Thomas Eding

2
@ThomasEding Причина, яку компілятор відхиляє, printf "%d" Trueполягає в тому, що немає жодного Boolпримірника PrintfArg. Якщо передати аргумент неправильного типу , що робить є екземпляр PrintfArg, він компілюється і кидає виняток у час виконання. Напр .:printf "%d" "hi"
Травіс Сандерленд
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.