在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) (一种传递类型freflect显式类型应用程序直到最近被允许的奇特方式)时,我们才会返回该函数。

现在到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

下一篇: A typecheck errors in deriving wrapper for Linear.V