gpt4 book ai didi

haskell - 在 Haskell 中创建许多类似的新类型/类型类实例

转载 作者:行者123 更新时间:2023-12-02 17:00:29 25 4
gpt4 key购买 nike

我是 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 ,它是酒吧。假设我想执行 5、10 次,唯一的区别是不同的函数 f。我当然可以复制并粘贴上面的代码,但我想知道是否还有其他方法?

在我看来,我把事情搞混了。在 Haskell 中做这样的事情的最佳方法是什么?这是一个好的设计选择吗?我的想法对/错是什么?为什么?

编辑:我意识到一个具体的例子可能有助于使问题更清晰(请注意,它可能看起来很复杂,我无法比这更简化代码。上面的问题包含我认为相同的信息):我感兴趣的类型类是来自库 HaskellForMathsAlgebra k v :

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 结构。这里的类型 T 只是编写抽象基本元素 T 1, T 2, ... 的一种便捷方式。我想要这样做的原因是代数的标准数学定义,即其结构常量(此处:结构)。总结一下:我希望能够改变函数 f (最好不要在编译时?)并返回代数。这可能是一个糟糕的设计决策:如果是这样,为什么?

最佳答案

您可以使用reflection 。这是一项相当先进的技术,可能有更好的方法来解决您的问题,但从您所说的方式来看,这似乎就是您正在寻找的。

{-# 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)

是一个约束,意味着 fk -> 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 ,因此我们可以在函数体内引用类型变量。第二个是正品rank-2 type ,

(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是函数 i -> i -> i 的类型级表示,以及 Foo f i 。观察main为了使用这个“存在”,我们用连续传递风格调用该函数,即

mkFoo (+) 5 $ \foo -> -- what to do with foo

在函数中,foo其行为就像类型 Foo f0 Integer哪里f0是专门为此功能而设计的全新类型。

很好,它不让我们 baz一起Foo来自不同的f s,但不幸的是它不够智能,无法让我们 baz一起Foo使用不同的调用 mkFoo 使用相同的函数创建,所以:

mkFoo (+) 5 $ \foo1 -> mkFoo (+) 5 $ \foo2 -> foo1 `baz` foo2  -- type error

关于haskell - 在 Haskell 中创建许多类似的新类型/类型类实例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46348426/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com