我是Haskell的初学者,并试图了解类型类和类型 . 我有以下示例(代表我正在研究的代数中的一个真正问题),其中我定义了一个只包装Num实例的类型,以及一个定义二进制操作 baz
的类型类 .
newtype Foo i = B i deriving (Eq, Ord, Show)
class Bar k where
baz :: k -> k -> k
instance Num k => Bar (Foo k) where
baz (B a) (B b) = B (f a b)
f :: Num a => a -> a -> a
f a b = a + b
当定义 Bar
是这个的一个实例时,我意识到我希望能够"vary"与该类型的函数 f
. 要明确:我想提供一个函数 f :: Num a => a -> a -> a
并返回一个新类型 Foo
,它是 Bar
的一个实例 . 说我想做5次,10次,唯一不同的是不同的功能 f
. 我当然可以复制并粘贴上面的代码,但我想知道是否还有其他方法?
在我看来,我很困惑 . 在Haskell中做这样的事情的最佳方法是什么?这是一个很好的设计选择,我在想什么是对/错?为什么?
编辑:我意识到一个具体的例子可能有助于使问题更清楚(请注意,它可能看起来很复杂,我不能简化代码而不是这个 . 上面的问题包含我认为的相同信息):我是类型类感兴趣的是 Algebra k v
来自图书馆HaskellForMaths:
class Algebra k b where
unit :: k -> Vect k b
mult :: Vect k (Tensor b b) -> Vect k b
这里 k
是一个字段(一个数学结构,如实数或复数),而 v
是矢量空间中的基础选择 . 我想用它这样的东西
newtype Basis i = T i deriving (Eq, Ord, Show)
type Expression k = Vect k (Basis Int)
instance Algebra k Basis where
unit x = x *> return I
mult = linear mult'
where mult' (T x ,T y) = comm x y
where comm a b = sum $ map (\c -> structure a b c *> t c) [0..n]
t :: Int -> Expression k
t a = return (T a)
然后根据我的喜好改变 Map structure
. 这里 T
类型只是编写抽象基础元素 T 1, T 2, ...
的便捷方式 . 我想要这样做的原因是代数的结构常数的标准数学定义(这里: structure
) . 总结一下:我希望能够改变函数 f
(最好不要在编译时?)并取回代数 . 这可能是一个糟糕的设计决定:如果是这样,为什么?
4 回答
你可以使用reflection . 这是一种相当先进的技术,可能有更好的方法来解决您的问题,但您已经说明了它的方式,这似乎是您正在寻找的 .
这里有很多事情,所以有几点说明 .
是一个约束,表示
f
是k -> k -> k
类型函数的类型级表示 . 当我们reflect (Proxy :: Proxy f)
(一种将f
类型传递给reflect
的奇特方式,因为直到最近才允许使用显式类型的应用程序),我们将函数本身退出 .现在到
mkFoo
的令人讨厌的签名第一个
forall
用于ScopedTypeVariables,因此我们可以引用函数体内的类型变量 . 第二个是真正的rank-2 type,它是存在类型的常见编码,因为Haskell没有第一类存在性 . 您可以将此类型读作
或者某些类型 - 它返回一个类型
f
以及f
是函数i -> i -> i
的类型级表示和Foo f i
的证据 . 在main
中观察要使用这个"existential",我们用连续传递样式调用该函数,即在函数中,
foo
的行为类似于Foo f0 Integer
类型,其中f0
是专为此函数制作的全新类型 .它让我们
baz
来自不同的f
s,但遗憾的是它不够聪明,不能让我们baz
一起Foo
用相同的函数使用不同的mkFoo
调用,所以:这是对我的另一个答案的补充,如果你的意图是解决实际问题而不是探索可能的问题,我实际建议的解决方案 . 它只是将类型类转换为“字典传递样式”,并且不使用任何花哨的扩展或任何东西 .
然后当你有一个使用它的函数时,传递一个
Bar
字典:它使用起来有点冗长,但这种解决方案非常灵活 . 字典完全是一流的,因此,例如,您可以开始对字典本身进行更高级别的操作:
这两种转换都是使用类型类的主要痛苦 .
这实际上与luqui的答案没有什么不同,而是使用
reify
而不是在运行时定义从幻像类型f
到具体函数的映射我们在编译时这样做 . 这使代码更简单,更容易使用 .鉴于
f
仅取决于k
,您可以定义另一个类,它将打包这样的函数,如下所示: