태그 보관물: printf

printf

Haskell printf는 어떻게 작동합니까? 뒤지지 않습니다 . 그러나

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



답변

트릭은 유형 클래스를 사용하는 것입니다. 의 경우 printf키는 PrintfType유형 클래스입니다. 메서드를 노출하지 않지만 중요한 부분은 어쨌든 형식에 있습니다.

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

따라서 printf오버로드 된 반환 유형이 있습니다. 사소한 경우에는 추가 인수가 없기 때문에으로 인스턴스화 할 수 있어야 r합니다 IO (). 이를 위해 인스턴스가 있습니다.

instance PrintfType (IO ())

다음으로 가변 개수의 인수를 지원하려면 인스턴스 수준에서 재귀를 사용해야합니다. 경우 있도록 특히 우리는 인스턴스를 필요 r입니다 PrintfType, 함수 타입 x -> r도있다 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더 이상 인수가 없을 때까지 재귀 적으로 생성되는 IO 액션을 취합니다.이 시점에서 단순히 실행합니다.

*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) 


답변