所以说我上课了:
class C a where
reduce :: a -> Int
现在我想将其打包成数据类型:
data Signal = forall a. (C a) => Signal [(Double, a)]
由于存在量化,我可以在信号上调用C方法,但信号不会公开类型参数:
reduceSig :: Signal -> [(Double, Int)]
reduceSig (Signal sig) = map (second reduce) sig
既然C有很多方法,我自然的下一步是拉出'reduce'函数,这样我就可以替换任何方法:
mapsig :: (C a) => (a -> a) -> Signal -> Signal
mapsig f (Signal sig) = Signal (map (second f) sig)
输入错误!无法推断(a1~a)。进一步思考,我认为它的含义是'f'是某个C实例的函数,但我无法保证它与C中的相同的实例一样,因为类型参数被隐藏了!我想要它,我明白了。
这是否意味着不可能概括reduceSig?我可以忍受这个,但我已经习惯于在haskell中自由地分解函数了,不得不编写样板文件感觉很奇怪。另一方面,我想不出任何方式来表达一个类型等于Signal内部的类型,而不是给Signal一个类型参数。
答案 0 :(得分:18)
您需要表达的是,f
与reduce
中使用的reduceSig
一样,可以应用于任何类型,即{C
的实例1}},而不是当前类型,f
适用于C
实例的单个类型。这可以这样做:
mapsig :: (forall a. (C a) => a -> a) -> Signal -> Signal
mapsig f (Signal sig) = Signal (map (second f) sig)
您需要使用RankNTypes
扩展程序,就像使用存在类型时一样;请注意mapsig
的实现是相同的,类型刚刚推广。
基本上,使用此类型,mapsig
可以决定调用哪个 a ;使用您之前的类型,mapsig
的调用者可以决定哪个不起作用,因为只有mapsig
知道正确的 a ,即Signal
内的那个。
但是,mapsig reduce
不起作用,原因显而易见reduce :: (C a) => a -> Int
,而您不知道 a 是Int!您需要为mapsig
提供更通用的类型(具有相同的实现):
mapsig :: (C b) => (forall a. (C a) => a -> b) -> Signal -> Signal
即,f
是一个函数,它采用任何类型作为C
的实例,并生成一个C
实例的类型(即类型在mapsig
调用时固定并由调用者选择;即,可以在任何信号上调用值mapsig f
,它将始终生成具有相同 a <的信号/ em>因此(不是你可以从外面检查)。)
Existentials和rank-N类型确实非常棘手,所以这可能需要一些时间来消化。 :)
作为附录,值得指出的是,如果C
中的所有功能对于某些r看起来都是a -> 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
到
data C = C
{ reduce :: Int
, foo :: (String, Double)
, bar :: ByteString -> Magic
}
data Signal = Signal [(Double, C)]
mapsig :: (C -> C) -> Signal -> Signal
这两种信号类型实际上是等价的!前一种解决方案的好处只有在您使用C
而存在量化它的其他数据类型时才会出现,这样您就可以拥有使用特定知识和特定实例的操作的代码。 C
它正在使用。如果您这个类的主要用例是通过存在量化,那么您可能首先不需要它。但我不知道你的程序是什么样的:)