我们可以抽象出类型类吗?

时间:2015-12-01 20:34:19

标签: haskell

我想知道是否有更深层次的原因我们无法抽象出类型类(或者我们可以吗?)。

例如,当我们有

fzip :: (forall a.[a] -> [a]) -> [b] -> [c] -> [(b,c)]
fzip f xs ys = zip (f xs) (f ys)

然后我们可以说

fzip (drop 42) [1..100] ['a'..'z']
fzip reverse   [1..100] ['a'..'z']

等等。但我们不能

fzip (map succ) [1..100] ['a'..'z']

我们可以解决:

ezip :: (Enum b, Enum c) => (forall a.Enum a => [a] -> [a]) -> [b] -> [c] -> [(b,c)]
ezip f xs ys = zip (f xs) (f ys)

同样我们可以修复

fzip (map (5*)) [1..100] [1.5, 2.3, 4.7]

nzip :: (Num b, Num c) => (forall a.Num a => [a] -> [a]) -> [b] -> [c] -> [(b,c)]
nzip f xs ys = zip (f xs) (f ys)

但是,我们不能将ezipnzip包含在以下内容中,这是不是很令人尴尬:

gzip :: (g b, g c) => (forall a. g a => [a] -> [a]) -> [b] -> [c] -> [(b,c)]

尽管代码是绝对相同的,但是这个类的名称是什么? 或者我们能以某种方式吗?

有趣的是,当实例只是包含函数的记录时,这很容易实现。

2 个答案:

答案 0 :(得分:11)

您可以几乎使用ConstraintKinds执行此操作:

{-# LANGUAGE ConstraintKinds, RankNTypes #-}

import Data.Proxy

gzip :: (g b, g c) => Proxy g -> (forall a . g a => [a] -> [a]) -> [b] -> [c] -> [(b,c)]
gzip _ f xs ys = zip (f xs) (f ys)

test1 = gzip (Proxy :: Proxy Enum) (map succ) [1 .. 100] ['a' .. 'z']
test2 = gzip (Proxy :: Proxy Num) (map (5*)) [1 .. 100] [1.5, 2.3, 4.7]

主要区别在于您需要Proxy参数,因为GHC无法在没有帮助的情况下推断g的正确实例化。

答案 1 :(得分:8)

为约束添加Proxy参数:

{-# LANGUAGE PartialTypeSignatures #-}

import Data.Proxy

gzip :: (g b, g c) => Proxy g -> (forall a. g a => [a] -> [a]) -> [b] -> [c] -> [(b,c)]
gzip _ f bs cs = zip (f bs) (f cs)

> gzip (Proxy :: _ Enum) [0, 1] "ab"
[(1,'b'),(2,'c')]

GHC认为约束参数只出现在不明确的约束中,因此我们需要在代理中明确记录它们。