Как работает 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)