在Haskell中创建许多类似的newtypes / typeclass实例
我是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
定义为这个实例时,我意识到我希望能够用该类型“改变”函数f
。 要清楚:我想提供一个函数f :: Num a => a -> a -> a
并获取一个新类型Foo
,它是一个Bar
实例。 说我想做这个5,10次,唯一的区别是不同的函数f
。 我当然可以复制和粘贴上面的代码,但我想知道是否有另一种方式?
在我看来,我很困惑的事情。 在Haskell中做这样的事情的最好方法是什么? 这是一个很好的设计选择,我在想什么是对/错,为什么?
编辑:我意识到一个具体的例子可能有助于使问题更清晰(注意它看起来很复杂,我不能简化代码比这更多)上面的问题包含我认为相同的信息):我是typeclass感兴趣的是来自库HaskellForMaths的Algebra kv
:
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)
然后根据我的需要改变地图structure
。 在这里,类型T
仅仅是写出抽象基本元素T 1, T 2, ...
一种便利方式。 我想这样做的原因是代数的结构常数(这里是structure
)的标准数学定义。 总结一下:我希望能够改变函数f
(最好不要在编译时?)并取回一个代数。 这可能是一个糟糕的设计决定:如果是这样,为什么?
你可以使用反射。 这是一项相当先进的技术,可能有更好的方法来解决你的问题,但你说的方式似乎就是你想要的。
{-# LANGUAGE FlexibleContexts, RankNTypes, ScopedTypeVariables, UndecidableInstances #-}
import Data.Reflection
import Data.Proxy
class Bar k where
baz :: k -> k -> k
newtype Foo f i = B i -- f is a type level representation of your function
deriving (Eq, Ord, Show)
instance (Num k, Reifies f (k -> k -> k)) => Bar (Foo f k) where
baz (B a) (B b) = B (reflect (Proxy :: Proxy f) a b)
mkFoo :: forall i r. (i -> i -> i) -> i
-> (forall f. Reifies f (i -> i -> i) => Foo f i -> r) -> r
mkFoo f x c = reify f ((p :: Proxy f) -> c (B x :: Foo f i))
main = do
mkFoo (+) 5 $ foo1 -> do
print $ foo1 `baz` B 5 -- 10
mkFoo (*) 5 $ foo2 -> do
print $ foo2 `baz` B 5 -- 25
print $ foo1 `baz` foo2 -- type error
这里有很多,所以有几个笔记。
Reifies f (k -> k -> k)
是一个约束,这意味着f
是类型k -> k -> k
的函数的类型级表示。 当我们reflect (Proxy :: Proxy f)
(一种传递类型f
以reflect
显式类型应用程序直到最近被允许的奇特方式)时,我们才会返回该函数。
现在到mkFoo
的讨厌签名
mkFoo :: forall i r. (i -> i -> i) -> i
-> (forall f. Reifies f (i -> i -> i) => Foo f i -> r) -> r
第一forall
是有ScopedTypeVariables
,所以我们可以参照类型变量的函数体中。 第二个是真正的二级类型,
(forall f. Reifies f (i -> i -> i) => Foo f i -> r) -> r
它是一种存在类型的通用编码,因为Haskell没有一流的存在。 你可以阅读这个类型
exists f. ( Reifies f (i -> i -> i) , Foo f i )
或者一些这样的f
- 它返回一个类型f
以及证明f
是一个函数i -> i -> i
和一个Foo fi
的类型级表示。 main
观察到使用这种“存在性”,我们称之为具有延续传球风格的功能,也就是说
mkFoo (+) 5 $ foo -> -- what to do with foo
在该函数中, foo
行为类似于Foo f0 Integer
类型的Foo f0 Integer
,其中f0
是为此函数创建的全新类型。
这是相当不错的,它不会让我们baz
一起Foo
从不同来源f
S,但不幸的是它不是足够聪明,让我们baz
一起Foo
s的使用不同电话的同一功能提出mkFoo
,所以:
mkFoo (+) 5 $ foo1 -> mkFoo (+) 5 $ foo2 -> foo1 `baz` foo2 -- type error
这是对我另一个答案的补充,如果你的意图是解决实际问题,而不是探索什么是可能的,我实际上会提出解决方案。 它只是将类型转换为“字典传递样式”,并没有使用任何奇特的扩展或任何东西。
data Bar k = Bar { baz :: k -> k -> k }
newtype Foo i = B i
fooBar :: (i -> i -> i) -> Bar (Foo i)
fooBar f = Bar { baz = (B x) (B y) -> B (f x y) }
然后当你有一个使用这个函数的函数时,传递一个Bar
字典:
doThingsWithFoos :: Bar (Foo Int) -> Foo Int -> Foo Int -> Foo Int
doThingsWithFoos bar a b = baz bar a (baz bar a b)
使用起来有点冗长,但这种解决方案非常灵活。 字典是完全一流的,因此,例如,您可以开始对字典本身进行更高级别的操作:
transportBar :: (a -> b) -> (b -> a) -> Bar a -> Bar b
transportBar f finv bar = Bar { baz = x y -> f (baz bar (finv x) (finv y)) }
sumBar :: (Num a) => Bar a -> Bar a -> Bar a
sumBar bar1 bar2 = Bar { baz = x y -> baz bar1 x y + baz bar2 x y }
这两种转换将是使用类型类的主要痛苦。
其实,这是没有什么不同由luqui答案,但不是从定义的幻象类型映射f
在系统运行时的具体功能reify
,我们这样做在编译时。 这使代码更简单易用。
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
class Bar k where
baz :: k -> k -> k
-- Foo has now another phantom type variable, that we use to pick the
-- desired f.
newtype Foo f k = B k
-- | GetF is used to retrieve a function for a given type label.
class GetF f k where
appF :: Foo f k -> Foo f k -> Foo f k
-- Now we can make an instance for Bar if we have an instance for GetF
instance GetF f k => Bar (Foo f k) where
baz x y = appF x y
-- = Usage example
-- | Add is just a label. We never use it at value level.
data Add
instance Num k => GetF Add k where
appF (B x) (B y) = B (x + y)
example :: Foo Add Int
example = B 1 `baz` B 2 -- = B 3
链接地址: http://www.djcxy.com/p/43463.html
上一篇: Creating many similar newtypes/typeclass instances in Haskell