Как работает Haskell printf?
безопасность типа Haskell является вторым нет только для зависимо-типизированных языков. Но есть какая-то глубокая магия происходит с текст.Printf это как-то типа-шаткий.
> printf "%dn" 3
3
> printf "%s %f %d" "foo" 3.3 3
foo 3.3 3
что за глубокая магия стоит за этим? Как Text.Printf.printf функции принимают аргументы вариативная такой?
каков общий метод, используемый для учета вариативных аргументов в Haskell, и как он работает?
(Примечание: некоторая безопасность типа, по-видимому, теряется при использовании этой техники.)
> :t printf "%dn" "foo"
printf "%dn" "foo" :: (PrintfType ([Char] -> t)) => t
1 ответов
фокус в том, чтобы использовать классы типа. В случае printf, ключ PrintfType тип класса. Он не предоставляет никаких методов, но важная часть-это в любом случае.
class PrintfType r
printf :: PrintfType r => String -> r
так printf имеет перегруженный тип возвращаемого значения. В тривиальном случае у нас нет дополнительных аргументов, поэтому нам нужно иметь возможность создавать экземпляры r to IO (). Для этого у нас есть экземпляр
instance PrintfType (IO ())
далее, чтобы поддержать переменное количество аргументов, нам нужно использовать рекурсию на уровне экземпляра. В частности, нам нужен экземпляр, чтобы if r это PrintfType тип функции x -> r тоже PrintfType.
-- instance PrintfType r => PrintfType (x -> r)
конечно, мы хотим только поддерживать аргументы, которые действительно могут быть отформатированы. Вот где второй тип class 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)