Haskell printf nasıl çalışır?


104

Haskell'ın tip emniyet ikinci hiçbiri sadece bağımlı-daktilo dillere. Ancak Text.Printf ile oldukça kötü görünen derin bir sihir var .

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

Bunun arkasındaki derin sihir nedir? Text.Printf.printfİşlev bunun gibi çeşitli argümanları nasıl alabilir ?

Haskell'de çeşitli argümanlara izin vermek için kullanılan genel teknik nedir ve nasıl çalışır?

(Yan not: Bu teknik kullanılırken belli bir güvenlik türü kaybolur.)

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

15
Bağımlı türleri kullanarak yalnızca bir tür güvenli printf alabilirsiniz.
augustss

9
Lennart oldukça haklı. Haskell'in tür güvenliği, Haskell'den daha bağımlı türlere sahip dillerden sonra ikinci sıradadır. Elbette, format için String'den daha bilgilendirici bir tür seçerseniz, printf benzeri bir şeyi güvenli hale getirebilirsiniz.
domuz işçisi


1
@augustss Yalnızca bağımlı türler veya HASKELL ŞABLONU kullanarak tür güvenli bir printf alabilirsiniz! ;-)
MathematicalOrchid

3
@MathematicalOrchid Template Haskell sayılmaz. :)
augustss

Yanıtlar:


131

İşin püf noktası, tür sınıflarını kullanmaktır. Durumda printf, anahtar PrintfTypetür sınıfıdır. Herhangi bir yöntemi açığa çıkarmaz, ancak önemli olan kısım zaten türlerdedir.

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

Yani printfaşırı yüklenmiş bir dönüş türü vardır. Biz örneğini gerekiyor çok önemsiz durumda, biz hiçbir ekstra argümanlar var riçin IO (). Bunun için örneğimiz var

instance PrintfType (IO ())

Sonra, değişken sayıda argümanı desteklemek için, örnek düzeyinde özyineleme kullanmamız gerekir. Özellikle bir örneğe ihtiyacımız var, böylece ra ise PrintfType, bir işlev türü x -> rde a PrintfType.

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

Tabii ki, sadece gerçekten biçimlendirilebilen argümanları desteklemek istiyoruz. İkinci tür sınıfın PrintfArgdevreye girdiği yer burasıdır . Yani gerçek örnek

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

ShowSınıftaki herhangi bir sayıda argümanı alan ve yalnızca bunları yazdıran basitleştirilmiş bir sürüm :

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

Burada, barargüman kalmayıncaya kadar özyinelemeli olarak oluşturulan bir IO eylemi alır, bu noktada onu basitçe çalıştırırız.

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

QuickCheck ayrıca, Testablesınıfın temel durum için bir örneğe sahip olduğu ve sınıfta Boolbağımsız değişkenler alan işlevler için özyinelemeli bir örneğe sahip olduğu aynı tekniği kullanır Arbitrary.

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

Mükemmel cevap. Sadece haskell'in uygulanan argümanlara dayanarak Foo'nun türünü bulduğunu belirtmek istedim. Bunu anlamak için, Foo explicity türünü şu şekilde belirtmek isteyebilirsiniz: λ> (foo :: (Show x, Show y) => x -> y -> IO ()) 3 "merhaba"
redfish64

1
Değişken uzunluk bağımsız değişkeninin nasıl uygulandığını anlasam da, derleyicinin nasıl reddettiğini hala anlamıyorum printf "%d" True. Bu benim için çok mistik, çünkü çalışma zamanı (?) Değeri "%d"derleme sırasında bir Int. Bu benim için kesinlikle şaşırtıcı. . . özellikle kaynak kodu DataKindsveya gibi şeyler kullanmadığından TemplateHaskell(kaynak kodunu kontrol ettim ama anlamadım.)
Thomas Eding

2
@ThomasEding Derleyicinin reddetmesinin nedeni, örneğinin printf "%d" Trueolmamasıdır . Yanlış türde bir argüman iletirseniz gelmez bir örneğini var , bu derleme yapar ve çalışma zamanında bir istisna atar. Ör:BoolPrintfArgPrintfArgprintf "%d" "hi"
Travis Sunderland
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.