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
종속 유형을 사용하는 경우에만 유형 안전 printf를 얻을 수 있습니다.
8

9
Lennart의 말이 맞습니다. Haskell의 유형 안전성은 Haskell보다 더 많은 종속 유형을 가진 언어에 뒤지지 않습니다. 물론 형식에 대해 String보다 더 유익한 유형을 선택하면 printf와 유사한 유형을 안전하게 만들 수 있습니다.
pigworker

3
printf의 여러 변형에 대해서는 oleg를 참조하십시오. okmij.org/ftp/typed-formatting/FPrintScan.html#DSL-In
sclv

1
@augustss 종속 유형 또는 템플릿 HASKELL을 사용하여 유형 안전 printf 만 얻을 수 있습니다! ;-)
MathematicalOrchid

3
@MathematicalOrchid Template Haskell은 포함되지 않습니다. :)
augustss

답변:


131

트릭은 유형 클래스를 사용하는 것입니다. 의 경우 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) 

좋은 대답입니다. 저는 haskell이 적용된 인수를 기반으로 Foo의 유형을 파악하고 있다는 점을 지적하고 싶었습니다. 이를 이해하기 위해 다음과 같이 명시 적으로 Foo 유형을 지정할 수 있습니다. λ> (foo :: (Show x, Show y) => x-> y-> IO ()) 3 "hello"
redfish64

1
가변 길이 인수 부분이 구현되는 방법을 이해하지만 컴파일러가 printf "%d" True. 이는 런타임 (?) 값 "%d"이 컴파일 시간에 해독되어 Int. 이것은 나에게 절대적으로 당혹 스럽다. . . 소스 코드가 같은 것을 사용하지 않습니다 특히 이후 DataKinds또는 TemplateHaskell(I 소스 코드를 확인하지만, 그것을 이해하지 않았다.)
토마스 대한 수정 사항을

2
컴파일러는 거부 이유를 @ThomasEding 것은 printf "%d" True어떤 없기 때문에입니다 Bool인스턴스 PrintfArg. 의 인스턴스 있는 잘못된 유형의 인수를 전달하면 PrintfArg런타임에 컴파일되고 예외가 발생합니다. 예 :printf "%d" "hi"
Travis Sunderland
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.