说我正在尝试在Haskell中定义一个新的类型类,除其他外,它必须具有+操作:
class Foo a where
(+) :: a -> a -> a
(实际上,Foo类型类有更多的东西,但让我停在这里以保持最小。)
现在,我要使基本的“ Integer”类型成为Foo的实例;就+操作而言,我只想保留已经定义的常规加法。我该怎么办?
不用说,以下内容毫无意义:
instance Foo Integer where
(+) x y = x+y
当我要求Haskell计算2 + 3时,它将永远循环,但是我想这是可以预期的!我还尝试了一点都不做:
instance Foo Integer
这可以编译,但是当我要求2 + 3时,我得到“没有实例,也没有用于类操作+的默认方法”。同样,这很有意义...
那我们该怎么办呢?
我猜这是一个关于“名称空间”的普遍问题,我的意思是,当两个类型类使用相同的名称时,会发生什么?就我而言,在尝试使一个类型(整数)成为两个具有此类名称冲突(Num和Foo)的两个类型类的实例时遇到问题。
通过阅读this question,我现在担心自己所要求的只是被禁止...
答案 0 :(得分:8)
要提供特定的解决方案,类似这样的方法将无需处理(+)冲突就可以工作:
class Foo a where
(<+>) :: a -> a -> a
infixl 6 <+>
instance Foo Integer where
(<+>) = (+)
现在,您的Foo类具有自己的运算符,并且固定性声明意味着(<+>)的解析方式与(+)相同。
答案 1 :(得分:4)
如果您确实要调用操作+
,并且要根据Prelude中的+
进行定义,则可以这样操作:
import Prelude (Integer, print)
import qualified Prelude ((+))
class Foo a where
(+) :: a -> a -> a
instance Foo Integer where
(+) x y = x Prelude.+ y
main = print (2 + 3 :: Integer)
这将输出5。
要证明main
的定义确实是在使用我们新的+
运算符,而不是原始的运算符,我们可以更改+
的定义:
import Prelude (Integer, print)
import qualified Prelude ((+))
class Foo a where
(+) :: a -> a -> a
instance Foo Integer where
(+) x y = x Prelude.+ y Prelude.+ 1
main = print (2 + 3 :: Integer)
这将输出6。
答案 2 :(得分:2)
我认为您在Haskell中不会像在纯数学设置中那样做。您正在尝试建立一个Abelian小组(从您的评论中看来),据我所知(作为自高中以来没有参加过数学课程的人),他们是一个a
类型的小组,函数f :: a -> a -> a
这样f x y = f y x
。
将其与Haskells内置的(并且经常使用的)Monoid类进行对比,您会明白为什么我说Haskellers可能会对此有所不同。 Monoid是一组a
类型的组,其中存在一个函数f :: a -> a -> a
使得f x (f y z) = f (f x y) z
(另外,但不相关的值k
使得f x k = x
) 。它表示关联性而不是可交换性,但在其他方面相同。
Monoid表示为:
class Monoid a where
mempty :: a
mappend :: a -> a -> a
mconcat :: [a] -> a
并定义了几个,例如Sum
newtype Sum a = Sum { getSum :: a }
instance Num a => Monoid (Sum a) where
mempty = 0
mappend (Sum x) (Sum y) = Sum (x+y)
-- mconcat is defined as `foldr mappend mempty`
请注意,它不会尝试在此处重新定义(+)
。实际上,它将自己的运算符定义为mappend
:(<>)
的同义词。我建议对您的Abelian小组使用类似的语法。
class Abelian a where
(|<>|) :: a -> a -> a -- or similar
答案 3 :(得分:1)
正如其他人回答的那样,最简单的选择是为操作员选择一个新名称。大多数现有软件包都采用这种方法,以便库的用户可以照常继续使用Num
。
正如OP在注释中所指出的,也可以导入Prelude
合格,并根据需要定义+
。想要使用新的+
的其他任何模块都需要执行相同的操作。他们不能导入前奏(通过import Prelude ()
或NoImplicitPrelude
),也不能导入合格的前奏。在大型代码库中,这是非常合理的,每个人都知道本地约定,您可以为所使用的所有类型提供实例。
答案 4 :(得分:1)
在Haskell中,您可以使用自己的Prelude
代替标准的clone git / 10.0.0.100/project --branch Devel
,并且现有多个alternative preludes。例如。 numeric-prelude定义了(+)
in Algebra.Additive.C
。
答案 5 :(得分:1)
程序员的方法 正如其他人所说,最好是放弃在数学界广泛使用的加法和乘法半式半数的方便表示法,而在一个更复杂的环境中考虑monoid。抽象形式-一种具有不同运算的独特代数结构,该运算通常与数环的加法和乘法无关,其中Num
类型类可以视为近似形式化。 Haskell类型系统依靠您来确保相同的名称指的是相同的事物,而不同的名称指的是不同的事物-这种简单且合乎逻辑的规则有助于避免混淆。这就是为什么通常使用菱形<>
符号来指代任何monoid的monoidal操作的原因。 (实际上,由于传统,对初始 monoids的操作有时称为++
。)
首先为通常的数字定义Num
(甚至在硬件级别),这在一定程度上是不合逻辑的,但被认为是实用的,然后提取构成的半定式: / p>
base-4.11.1.0:Data.Semigroup.Internal
...
198 instance Num a => Semigroup (Sum a) where
199 (<>) = coerce ((+) :: a -> a -> a)
...
227 instance Num a => Semigroup (Product a) where
228 (<>) = coerce ((*) :: a -> a -> a)
...
—碰巧+
在任何抽象代数开始发挥作用之前就被占据了。
代数论者的方法
不过,很有可能用两个类半体定义一个类型之外的环:{-# language FlexibleInstances #-}
{-# language FlexibleContexts #-}
{-# language UndecidableInstances #-}
{-# language TypeApplications #-}
module MonoidsField where
import Prelude (Integer, Semigroup, (<>), Monoid, mempty, Show, show)
import Data.Coerce
newtype Sum a = Sum { getSum :: a }
newtype Product a = Product { getProduct :: a }
data N = Z | S { predecessor :: N}
-- | Substract one; decrease.
dec :: Coercible a (N -> N) => a
dec = coerce predecessor
instance Show N
where
show Z = ""
show x = '|' : show (predecessor x)
instance Semigroup (Sum N)
where
u <> (Sum Z) = u
u <> v = coerce S (u <> dec v)
instance Monoid (Sum N)
where
mempty = Sum Z
instance Semigroup (Product N)
where
u <> (Product Z) = coerce (mempty @(Sum N))
u <> v = let (*) = (<>) @(Product N)
(+) = coerce ((<>) @(Sum N))
in u + (u * dec v)
instance Monoid (Product N)
where
mempty = Product (S Z)
(+) :: Monoid (Sum a) => a -> a -> a
x + y = getSum (Sum x <> Sum y)
(*) :: Monoid (Product a) => a -> a -> a
x * y = getProduct (Product x <> Product y)
class PseudoRing a
instance (Monoid (Sum a), Monoid (Product a)) => PseudoRing a
where
-- You may add some functions that make use of distributivity between the additive and
-- multiplicative monoid.
-- ^
-- λ (S (S (S Z))) + (S (S Z))
-- |||||
-- λ (S (S (S Z))) * (S (S Z))
-- ||||||
—如您所见,PseudoRing
类本身并没有添加任何操作,但是可以确保定义了它所需的两个monoid。如果实例化它,则意味着您已确保分布公理成立。
如该示例所示,可以将子类视为本身包括为其超类定义的所有操作。因此,您有可能宣布instance Num a => Foo a
并重新使用+
的定义。然后,您可能会为自己的类型定义Num
的“ partial” 实例,以使优先级无法为所有必需的方法定义。这种方法显然是不安全的,令人困惑的,并且总体上不建议这样做,但这可能正是您所需要的,尤其是如果装饰有适当的scientific license。因此,您的示例变为:
class Foo a
instance Num a => Foo a
让我知道以上任何一项是否需要澄清!