Paw*_*mar 10 haskell existential-type gadt
我试图理解 Haskell 中的存在类型并遇到了一个 PDF http://www.ii.uni.wroc.pl/~dabi/courses/ZPF15/rlasocha/prezentacja.pdf
请纠正我到目前为止的以下理解。
GADT's 通过提供隐式forall's为使用存在类型的代码提供清晰和更好的语法我的疑惑
:: Worker MemoryBuffer Int如果他们真的想在抽象缓冲区他们能有一个求和型data Buffer = MemoryBuffer | NetBuffer | RandomBuffer和有型状:: Worker Buffer Intdata Worker x = forall b. Buffer b => Worker {buffer :: b, input :: x}
data MemoryBuffer = MemoryBuffer
memoryWorker = Worker MemoryBuffer (1 :: Int)
memoryWorker :: Worker Int
Run Code Online (Sandbox Code Playgroud)
GADT 通过提供隐式 forall 为使用 Existential Types 的代码提供清晰和更好的语法
我认为大家普遍认为 GADT 语法更好。我不会说这是因为 GADT 提供了隐式 foralls,而是因为使用ExistentialQuantification扩展启用的原始语法可能会造成混淆/误导。当然,这种语法看起来像:
data SomeType = forall a. SomeType a
Run Code Online (Sandbox Code Playgroud)
或有约束:
data SomeShowableType = forall a. Show a => SomeShowableType a
Run Code Online (Sandbox Code Playgroud)
我认为大家的共识是forall这里使用关键字很容易将类型与完全不同的类型混淆:
data AnyType = AnyType (forall a. a) -- need RankNTypes extension
Run Code Online (Sandbox Code Playgroud)
更好的语法可能使用了单独的exists关键字,因此您可以这样写:
data SomeType = SomeType (exists a. a) -- not valid GHC syntax
Run Code Online (Sandbox Code Playgroud)
GADT 语法,无论是与隐式还是显式一起使用forall,在这些类型中都更加统一,并且似乎更容易理解。即使使用显式forall,以下定义也传达了您可以获取任何类型的值a并将其放入单态的想法SomeType':
data SomeType' where
SomeType' :: forall a. (a -> SomeType') -- parentheses optional
Run Code Online (Sandbox Code Playgroud)
并且很容易看到和理解该类型与以下类型之间的区别:
data AnyType' where
AnyType' :: (forall a. a) -> AnyType'
Run Code Online (Sandbox Code Playgroud)
Existential Types 似乎对它们包含的类型不感兴趣,但是模式匹配它们说存在某种类型,我们不知道它是什么类型,除非我们使用 Typeable 或 Data。
当我们想要隐藏类型(例如:用于异构列表)或者我们在编译时不知道类型是什么时,我们会使用它们。
我想这些不会太远,尽管您不必使用Typeable或Data使用存在类型。我认为更准确地说,存在类型在未指定的类型周围提供了一个类型良好的“框”。盒子在某种意义上确实“隐藏”了类型,这允许您制作此类盒子的异类列表,而忽略它们包含的类型。事实证明,像SomeType'上面这样的不受约束的存在是非常无用的,但是受约束的类型:
data SomeShowableType' where
SomeShowableType' :: forall a. (Show a) => a -> SomeShowableType'
Run Code Online (Sandbox Code Playgroud)
允许您进行模式匹配以查看“盒子”内部并使类型类工具可用:
showIt :: SomeShowableType' -> String
showIt (SomeShowableType' x) = show x
Run Code Online (Sandbox Code Playgroud)
请注意,这适用于任何类型的类,而不仅仅是Typeableor 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
Run Code Online (Sandbox Code Playgroud)
但是如果你编写一个以 aWorker作为参数的函数,它只能使用通用Buffer类型类工具(例如, function output):
doWork :: Worker Int -> IO ()
doWork (Worker b x) = output (show x) b
Run Code Online (Sandbox Code Playgroud)
b即使通过模式匹配,它也不能尝试要求成为特定类型的缓冲区:
doWorkBroken :: Worker Int -> IO ()
doWorkBroken (Worker b x) = case b of
MemoryBuffer -> error "try this" -- type error
_ -> error "try that"
Run Code Online (Sandbox Code Playgroud)
最后,有关存在类型的运行时信息通过所涉及的类型类的隐式“字典”参数提供。Worker上面的类型除了有缓冲区和输入的字段外,还有一个不可见的隐式字段,指向Buffer字典(有点像 v-table,虽然它并不大,因为它只包含一个指向适当output函数的指针)。
在内部,类型类Buffer表示为具有函数字段的数据类型,实例是这种类型的“字典”:
data Buffer' b = Buffer' { output' :: String -> b -> IO () }
dBuffer_MemoryBuffer :: Buffer' MemoryBuffer
dBuffer_MemoryBuffer = Buffer' { output' = undefined }
Run Code Online (Sandbox Code Playgroud)
存在类型对于这个字典有一个隐藏字段:
data Worker' x = forall b. Worker' { dBuffer :: Buffer' b, buffer' :: b, input' :: x }
Run Code Online (Sandbox Code Playgroud)
并且像doWork这样对存在Worker'值进行操作的函数实现为:
doWork' :: Worker' Int -> IO ()
doWork' (Worker' dBuf b x) = output' dBuf (show x) b
Run Code Online (Sandbox Code Playgroud)
对于只有一个函数的类型类,字典实际上被优化为一个新Worker类型,所以在这个例子中,存在类型包括一个隐藏字段,它包含一个指向output缓冲区函数的函数指针,这是唯一需要的运行时信息由doWork.