GADT는 암시 적 forall의
GADT 구문이 더 낫다는 일반적인 동의가 있다고 생각합니다. GADT가 모든 것을 암시 적으로 제공하기 때문이 아니라 ExistentialQuantification
확장 기능을 사용하는 원래 구문 이 혼란스럽고 오해의 소지 가 있기 때문 입니다. 물론이 구문은 다음과 같습니다.
data SomeType = forall a. SomeType a
또는 제약 조건이있는 경우 :
data SomeShowableType = forall a. Show a => SomeShowableType a
그리고 나는 forall
여기 에 키워드 를 사용하면 유형이 완전히 다른 유형과 쉽게 혼동 될 수 있다고 생각합니다 .
data AnyType = AnyType (forall a. a) -- need RankNTypes extension
더 나은 구문은 별도의 exists
키워드를 사용했을 수 있으므로 다음과 같이 작성하십시오.
data SomeType = SomeType (exists a. a) -- not valid GHC syntax
암시 적이든 명시 적이든 GADT 구문 forall
은 이러한 유형에서 더 균일하며 이해하기 쉽습니다. 명시 적 forall
인 경우에도 다음 정의는 모든 유형의 값을 가져 와서 a
단형 안에 넣을 수 있다는 아이디어를 얻 습니다 SomeType'
.
data SomeType' where
SomeType' :: forall a. (a -> SomeType') -- parentheses optional
그리고 그 유형과 다음의 차이점을 쉽게보고 이해할 수 있습니다.
data AnyType' where
AnyType' :: (forall a. a) -> AnyType'
존재 유형은 포함하는 유형에 관심이없는 것처럼 보이지만 패턴 일치는 일부 유형이 있으며 유형 지정 또는 데이터를 사용하지 않는 한 유형이 무엇인지 알 수 없습니다.
유형을 숨기고 싶을 때 (예 : 이기종 목록의 경우) 또는 컴파일 타임에 유형이 무엇인지 실제로 알지 못합니다.
나는 존재하지 않는 유형 을 사용 Typeable
하거나 사용할 필요는 없지만 이것들은 너무 멀지 않은 것 같습니다 Data
. 필자는 실존 유형이 지정되지 않은 유형 주위에 잘 유형화 된 "상자"를 제공한다고 말하는 것이 더 정확하다고 생각합니다. 상자는 유형을 어떤 의미로 "숨겨"서, 상자에 포함 된 유형을 무시하고 이기종 목록을 만들 수 있습니다. SomeType'
위와 같이 제한되지 않은 실재 는 쓸모가 없지만 제약 유형입니다.
data SomeShowableType' where
SomeShowableType' :: forall a. (Show a) => a -> SomeShowableType'
"상자"내부를 들여다 볼 수 있도록 패턴 일치를 만들고 유형 클래스 기능을 사용할 수 있습니다.
showIt :: SomeShowableType' -> String
showIt (SomeShowableType' x) = show x
이것은 Typeable
또는 모든 유형 클래스에서 작동하지만Data
.
슬라이드 데크의 20 페이지에 대한 혼동과 관련하여 필자는 필자는 실존 적 인 기능 이 특정 인스턴스 Worker
를 요구하는 것이 불가능하다고 말합니다 . 다음 과 같은 특정 유형을 사용하여 작성하는 함수를 작성할 수 있습니다 .Worker
Buffer
Worker
Buffer
MemoryBuffer
class Buffer b where
output :: String -> b -> IO ()
data Worker x = forall b. Buffer b => Worker {buffer :: b, input :: x}
data MemoryBuffer = MemoryBuffer
instance Buffer MemoryBuffer
memoryWorker = Worker MemoryBuffer (1 :: Int)
memoryWorker :: Worker Int
그러나 Worker
인수로 사용 하는 함수를 작성하는 경우 일반 Buffer
유형 클래스 기능 (예 output
: function ) 만 사용할 수 있습니다 .
doWork :: Worker Int -> IO ()
doWork (Worker b x) = output (show x) b
b
패턴 일치를 통해서도 특정 유형의 버퍼 가 되도록 요구할 수는 없습니다 .
doWorkBroken :: Worker Int -> IO ()
doWorkBroken (Worker b x) = case b of
MemoryBuffer -> error "try this" -- type error
_ -> error "try that"
마지막으로, 존재하는 유형에 대한 런타임 정보는 관련된 유형 클래스에 대한 암시 적 "사전"인수를 통해 사용 가능합니다. Worker
버퍼와 입력에 대한 필드를 갖는 것 외에도 위 의 유형에는 Buffer
사전 을 가리키는 보이지 않는 암시 적 필드가 있습니다 (v-table과 거의 같지만 적절한 포인터를 포함하기 때문에 거의 크지 않습니다)output
함수에 않습니다).
내부적으로 유형 클래스 Buffer
는 함수 필드가있는 데이터 유형으로 표시되며 인스턴스는이 유형의 "사전"입니다.
data Buffer' b = Buffer' { output' :: String -> b -> IO () }
dBuffer_MemoryBuffer :: Buffer' MemoryBuffer
dBuffer_MemoryBuffer = Buffer' { output' = undefined }
실존 유형에는이 사전에 숨겨진 필드가 있습니다.
data Worker' x = forall b. Worker' { dBuffer :: Buffer' b, buffer' :: b, input' :: x }
doWork
존재 Worker'
값에서 작동하는 것과 같은 기능은 다음과 같이 구현됩니다.
doWork' :: Worker' Int -> IO ()
doWork' (Worker' dBuf b x) = output' dBuf (show x) b
함수가 하나만있는 형식 클래스의 경우 사전은 실제로 새 형식에 최적화되므로이 예에서 실존 Worker
형식에는 output
버퍼 의 함수에 대한 함수 포인터로 구성된 숨겨진 필드가 포함되며 필요한 런타임 정보입니다. 에 의해 doWork
.