这是我第一次理解Haskell中的类。我的问题是如何定义我在类中声明的函数以及如何通过终端ghci测试它们。
我逐步解释我的所作所为:
type Point2d = (Int, Int) -- point
type Vector2d = (Int, Int) -- vector
data Shape =
Line Point2d Point2d
| Triangle Point2d Point2d Point2d
deriving (Eq, Show)
class ClassShape s where
name :: s -> String
perim :: s -> Int -- given a CShape calculates the perimeter
move :: s -> Vector2d -> s
现在,我通过实现相应的函数将s
声明为ClassShape实例。
nameShape :: Shape s -> String
nameShape Line = "Line"
nameShape Triangle = "Triangle"
perimShape :: Shape s -> Int
perimShape Line a b = 999 -- ...
perimShape Triangle a b c = 999 -- ...
这是我的问题:我该如何申报这些功能?我只需要看一个“例子”来理解这个概念。
Haskell返回的错误是:
`Shape' is applied to too many type arguments
In the type signature for `nameShape':
nameShape :: Shape s -> String
`Shape' is applied to too many type arguments
In the type signature for `perimShape':
perimShape :: Shape s -> Int
然后,我如何在Haskell上测试程序?
感谢所有人。
答案 0 :(得分:9)
请注意nameShape
函数不起作用,因为没有定义Shape s
类型。请记住,s
是一个类型变量。只有定义了Shape s
类型构造函数,才能使用它们。您已在定义中定义了Shape
类型,但未定义Shape s
。要定义类型类的实例,您必须执行以下操作:
instance ClassShape Shape where
name (Line _ _) = "Line"
name (Triangle _ _ _) = "Triangle"
perim (Line x y) = undefined -- Calculate perimiter using x and y
perim (Triangle x y z) = undefined
move (Line x y) = undefined
move (Triangle x y z) = undefined
您必须在undefined
填写工作部分。
答案 1 :(得分:1)
你对早期的Haskell程序员产生了一种常见的困惑:使用两种不同的东西,它们以相关的方式(总和类型和类)以两种不同的方式做同样的事情。因此有两个问题:"小"问题(这个错误是什么意思?)和"大"问题(为什么你的代码形如此?)。
当你打算写Shape s
时,你写了Shape
。你定义Shape
的方式,它有种类*
(也就是说,它是一种具体的类型),而不是种类* -> *
,这是形容词 - ""列表"或者"一对"这是抽象的,直到你给他们一个具体类型来修改("一个字符串列表"具体,""列表是抽象的)。当您撰写Shape s
时,您将Shape
作为形容词s
的形容词,但它不是形容词;它是一个名词。
这就是你得到错误的原因:
`Shape' is applied to too many type arguments
旁注:您可能习惯于错误消息通常与实际问题不太相关的语言。在Haskell中,编译器通常会告诉您确切的错误,就像在这种情况下一样。
类型类是不相关类型的集合,可以执行相同的操作。类型类语法将隐式上下文作为"约束"传递,该上下文可以是隐式的,因为它属于该类型。
你可能需要在一个安静的角落里读几遍最后一段。基本上我的意思是说你可以做一个类型类与上下文的数据构造函数相同的事情:
data EqOrd s = EqOrdLib {getEq :: s -> s -> Bool, getCmp :: s -> s -> Ordering}
-- this is just provided to us as a primitive by Haskell
intEOL :: EqOrd Int
intEOL = EqOrdLib (==) compare
-- but we can then define things like this:
listEOL :: EqOrd x -> EqOrd [x]
listEOL (EqOrdLib base_eq base_cmp) = EqOrdLib list_eq list_cmp where
list_cmp [] [] = EQ
list_cmp (_:_) [] = GT
list_cmp [] (_:_) = LT
list_cmp (x:xs) (y:ys) = case base_cmp x y of
LT -> LT
GT -> GT
EQ -> list_cmp xs ys
list_eq xs ys = list_cmp xs ys == EQ
现在要使用那种上下文,你必须明确写出:
quicksort :: EqOrd x -> [x] -> [x]
quicksort _ [] = []
quicksort lib (p:els) = quicksort lib lesser ++ [p] ++ quicksort lib greater
where cmp = getCmp lib
p_less_than x = cmp x p == LT
p_gte x = not . p_less_than
greater = filter p_less_than els
lesser = filter p_gte els
请参阅,我们明确传入此函数库lib
并明确提取比较函数cmp = getCmp lib
。
类型类允许我们隐式地传递函数库,方法是预先声明类型本身只有一个这样的库。我们将库作为"约束"传递,因此您使用"胖箭头"而不是EqOrd x -> [x] -> [x]
而不是Ord x => [x] -> [x]
。约束。但是暗示这意味着当你要我在类型为<
的两个值上使用x
函数时,我会隐含地知道从哪个库获取该函数并将为您获取该函数。 &#34;
现在:您有一种,Shape
,因此您不需要类型。 (回到上面的第一段:类型类是不相关类型的集合,可以做同样的事情。
如果你想做类型类而不是Shape的sum-type,让我们定义不同类型的n维向量:
class Vector v where
(*.) :: (Num r) => r -> v r -> v r
(.+.) :: (Num r) => v r -> v r -> v r
norm :: (Num r, Floating r) => v r -> r
-- another advantage of type classes is *default declarations* like:
(.-.) :: (Num r) => v r -> v r -> v r
v1 .-. v2 = v1 .+. (-1 *. v2)
data V2D r = V2D r r deriving (Eq, Show)
instance Vector V2D where
s *. V2D x y = V2D (s * x) (s * y)
V2D x1 y1 .+. V2D x2 y2 = V2D (x1 + x2) (y1 + y2)
norm (V2D x y) = sqrt (x^2 + y^2)
data V3D r = V3D r r r deriving (Eq, Show)
instance Vector V3D where
s *. V3D x y z = V3D (s * x) (s * y) (s * z)
V3D x1 y1 z1 .+. V3D x2 y2 z2 = V3D (x1 + x2) (y1 + y2) (z1 + z2)
norm (V3D x y z) = sqrt (x^2 + y^2 + z^2)
然后我们可以写下这样的内容:
newtype GeneralPolygon v r = Poly [v r]
perimeter :: (Num r, Floating r, Vector v) -> GeneralPolygon v r -> r
perimeter (Poly []) = 0
perimeter (Poly (x : xs)) = foldr (+) 0 (map norm (zipWith (.-.) (x : xs) (xs ++ [x])))
translate :: (Vector v, Num r) => GeneralPolygon v r -> v r -> GeneralPolygon v r
translate (Poly xs) v = Poly (map (v .+.) xs)
现在,如果你真的想要,你也可以 将你的sum-type数据声明解包到一堆数据声明中:
data Line = Line Point2d Point2d deriving (Eq, Show)
data Square = Square Point2d Point2d deriving (Eq, Show)
data Triangle = Triangle Point2d Point2d Point2d deriving (Eq, Show)
现在你可以做一些简单的事情:
class Shape s where
perim :: s -> Int
move :: s -> Vector2d -> s
虽然我应该说,当你想为perimeters做平方根时会遇到问题(sqrt
类型为Floating
,Int
没有有功能,你想要将Int改为Double或其他东西。