存在类型的多态函数

Eva*_*rge 14 haskell existential-type

所以说我有一节课:

class C a where
  reduce :: a -> Int
Run Code Online (Sandbox Code Playgroud)

现在我想将其打包成数据类型:

data Signal = forall a. (C a) => Signal [(Double, a)]
Run Code Online (Sandbox Code Playgroud)

由于存在量化,我可以在信号上调用C方法,但信号不公开类型参数:

reduceSig :: Signal -> [(Double, Int)]
reduceSig (Signal sig) = map (second reduce) sig
Run Code Online (Sandbox Code Playgroud)

现在因为C有很多方法,我自然而然的下一步就是拉出'reduce'函数,这样我就可以替换任何方法:

mapsig :: (C a) => (a -> a) -> Signal -> Signal
mapsig f (Signal sig) = Signal (map (second f) sig)
Run Code Online (Sandbox Code Playgroud)

输入错误!无法推断(a1~a).进一步思考,我认为它的含义是'f'是某个C实例的函数,但我无法保证它与信号中的C实例相同,因为类型参数是隐藏的!我想要它,我明白了.

那么这是否意味着不可能推广reduceSig?我可以忍受这个,但我已经习惯于在haskell中自由地分解函数了,不得不编写样板文件感觉很奇怪.另一方面,我想不出任何方式来表达一个类型等于Signal内部的类型,而不是给Signal一个类型参数.

ehi*_*ird 18

你需要表达的是f,就像reduce在中使用的那样reduceSig,它可以应用于任何类型的实例C,而不是当前类型,它f适用于作为实例的单个类型C.这可以这样做:

mapsig :: (forall a. (C a) => a -> a) -> Signal -> Signal
mapsig f (Signal sig) = Signal (map (second f) sig)
Run Code Online (Sandbox Code Playgroud)

你需要RankNTypes扩展名,正如你在使用存在类型时经常做的那样; 注意,执行mapsig是一样的,类型刚刚被一概而论.

基本上,这种类型的,mapsig获取决定哪个一个函数被调用上; 与以前的类型,呼叫者mapsig到达决定,这是不行的,因为只有mapsig知道正确的一个,即内部的一个Signal.

但是,mapsig reduce不起作用,原因很明显reduce :: (C a) => a -> Int,而且你不知道那是一个 Int!您需要提供mapsig更通用的类型(具有相同的实现):

mapsig :: (C b) => (forall a. (C a) => a -> b) -> Signal -> Signal
Run Code Online (Sandbox Code Playgroud)

即,f是一个函数,它采用任何类型的实例C,并生成一个类型的实例C(该类型在mapsig调用时被固定并由调用者选择;即,该值mapsig f可以在任何信号上调用,它总是会产生具有相同的信号一个结果(这并不是说,你可以从外部检查此).)

Existentials和rank-N类型确实很棘手,所以这可能需要一些时间来消化.:)


作为附录,值得指出的是,如果所有函数C看起来都a -> r适合某些r,那么你最好创建一个记录,即转向

class C a where
  reduce :: a -> Int
  foo :: a -> (String, Double)
  bar :: a -> ByteString -> Magic

data Signal = forall a. (C a) => Signal [(Double, a)]

mapsig :: (C b) => (forall a. (C a) => a -> b) -> Signal -> Signal
Run Code Online (Sandbox Code Playgroud)

data C = C
  { reduce :: Int
  , foo :: (String, Double)
  , bar :: ByteString -> Magic
  }

data Signal = Signal [(Double, C)]

mapsig :: (C -> C) -> Signal -> Signal
Run Code Online (Sandbox Code Playgroud)

这两种信号类型实际上是等价的!前一种解决方案的好处只有在您使用其他数据类型C 而不进行存在量化时才会出现,这样您就可以拥有使用特定实例的特殊知识和操作的代码C.如果您这个类的主要用例是通过存在量化,那么您可能首先不需要它.但我不知道你的程序是什么样的:)