我正在尝试将2x2矩阵乘以学习练习。我正在尝试将Matrix
类型设为Num
的实例,以便我可以与*
相乘。当我尝试在GHCI中执行(Matrix 1 2 3 4) *(Matrix 1 1 1 1)
时,即使我使用灵活上下文,我也会收到错误,即使用FlexibleContexts to permit this
:
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
import Debug.Trace
data Matrix a b c d= Matrix a b c d
deriving (Show)
instance Num a=>Num (Matrix a a a a) where
(*) (Matrix a1 b1 c1 d1) (Matrix a2 b2 c2 d2) = (trace "* ran")(Matrix (a1*a2 + b1*c2) (a1*b2 + b1*d2) (c1*a2 + d1*c2) (c1*b2+d2*d1))
multMat (Matrix a1 b1 c1 d1) (Matrix a2 b2 c2 d2) = (trace "Multmat ran")(Matrix (a1*a2 + b1*c2) (a1*b2 + b1*d2) (c1*a2 + d1*c2) (c1*b2 + d1*d2))
我不认为我的*
方法被调用,因为"* ran"
从未打印出来。另一方面,如果我运行multMat(代码是相同的,除了函数名),调试语句就打印出来,我得到了预期的答案。
*我认为问题是当在Matrix数据类型上使用*
时,会调用其他一些方法。我如何让它调用我的方法? *
在编译此代码时,它会在尝试使用*
类型Matrix
时出现运行时错误。
答案 0 :(得分:3)
矩阵乘法的定义很好(虽然在语义上它是不正确的)。您的代码编译得很好。但问题是,如果您执行查询:
*Main> (Matrix 1 2 3 4) * (Matrix 7 5 3 1)
Haskell感到困惑的是它应该附加到1
,2
等哪种类型。它可以是Int
,Integer
,Float
,{{1所有这些都有不同的实现(如果它选择Double
,它将执行浮点乘法,这与整数乘法不同)。
解决方案是你给Haskell一个提示你正在使用的类型:
Float
毕竟,*Main> (Matrix (1 :: Int) (2 :: Int) (3 :: Int) (4 :: Int)) * (Matrix (7 :: Int) (5 :: Int) (3 :: Int) (1 :: Int))
Matrix 7 6 15 4
可以是任何类型的1
类型(甚至是矩阵),内部Haskell会调用Num
将其转换为正确的fromInteger
类型。
这也是为什么从Num
实例化Matrix
并不简单的原因:您需要将整数转换为矩阵。由于您的Num
数据类型只允许2 x 2矩阵,因此没有直接的方法。
最后要注意的是你的矩阵乘法是错误的。你应该使用:
Matrix
虽然我能理解这只是一个简单的测试。
In the comments,@ stites问了一个有趣的问题,即仅仅实例化一种Matrix类型就足够了。类似的东西:
instance Num a=>Num (Matrix a a a a) where
(*) (Matrix a1 b1 c1 d1) (Matrix a2 b2 c2 d2) = Matrix (a1*a2+b1*c2) (a1*b2+b1*d2) (c1*a2+d1*c2) (b2*c1+d1*d2)
然而,Haskell对此有误,这是合理的。毕竟,一个有效的推理方法是,您仍然可以使用:
(Matrix (1 :: Int) 2 3 4) * (Matrix 7 5 3 1)
现在您只为Matrix Int Float Float Int
定义了instance
,但可能会定义一个,例如:
Matrix a a a a
由于编译器应该保守,它不能简单地假设选择了最受欢迎的instance Num (Matrix Int Float Float Int) where
(*) ...
:也许你的印象是你定义了另一个实例某处。
此外,当您完全指定左矩阵的类型时,它可以工作:
instance
这是因为现在您已完全指定左操作数属于*Main> (Matrix (1 :: Int) (2 :: Int) (3 :: Int) (4 :: Int)) * (Matrix 7 5 3 1)
Matrix 7 6 15 4
类型。由于Matrix Int Int Int Int
具有签名:
(*)
(使用(*) :: Num e => e -> e -> e
来减少混淆),它知道e
所以它可以"崩溃"右操作数:它因此可以导出右操作数,类型为e = Matrix Int Int Int Int
。
您甚至可以在任意一侧指定类型,例如:
Matrix Int Int Int Int
也可以。因为第一个Haskell派生出*Main> (Matrix (1 :: Int) 2 (3 :: Int) 4) * (Matrix 7 (5 :: Int) 3 (1 :: Int))
Matrix 7 6 15 4
。现在第一个操作数填入e = Matrix a b c d
,第二个a = Int
等等。
@ user2407038还提供了有用的comment。您也可以将您的定义写为:
b = Int
由于签名显示{-# LANGUAGE GADTs #-}
-- ...
instance (Num a, a ~ b, b ~ c, c ~ d) => Num (Matrix a b c d) where
(*) (Matrix a1 b1 c1 d1) (Matrix a2 b2 c2 d2) = Matrix (a1*a2+b1*c2) (a1*b2+b1*d2) (c1*a2+d1*c2) (b2*c1+d1*d2)
,Haskell将尝试将乘法与此实例匹配。如果Matrix a b c d
,它可以这样做。这是真的。