给定我的类型定义:
data Tile = Revealed | Covered deriving (Eq, Show)
data MinePit = Clean | Unsafe deriving (Eq, Show)
data Flag = Flagged | Unflagged deriving (Eq, Show)
type Square = (Tile, MinePit, Flag)
type Board = [[Square]]
我创建了两个函数:
createBoard :: Int -> Int -> Board
createBoard 0 _ = [[]]
createBoard _ 0 = [[]]
createBoard 1 1 = [[(Covered, Clean, Unflagged)]]
createBoard n m = take n (repeat (take m (repeat (Covered, Clean, Unflagged))))
一个例子:
λ> createBoard 2 3
[[(Covered,Clean,Unflagged),(Covered,Clean,Unflagged),(Covered,Clean,Unflagged)],[(Covered,Clean,Unflagged),(Covered,Clean,Unflagged),(Covered,Clean,Unflagged)]]
defineIndices :: Int -> Int -> [[(Int,Int)]]
defineIndices n m = [[(i,j) | j <- [1..m]] | i <- [1..n]]
它的行为类似于:
λ> defineIndices 2 3
[[(1,1),(1,2),(1,3)],[(2,1),(2,2),(2,3)]]
从这里开始,我创建了一个函数来创建MapBoard,在该函数中可以根据给定的Square值查找特定Square的值。
type MapBoard = Map (Int, Int) Square
createMapBoard :: [[(Int,Int)]] -> [[Square]] -> MapBoard
createMapBoard indices squares = M.fromList $ zip (concat indices) (concat squares)
但是,对我来说似乎合理的是,我还应该编写一种方法,在该方法中,我可以直接从一对Int(一个或多个)中创建MapBoard,以实现我以前的功能。可能看起来像:
createMapBoard2 :: Int -> Int -> MapBoard
createMapBoard2 n m = createMapBoard indices squares where
indices = defineIndices n m
squares = createBoard n m
但是,我检查了是否可以使用createMapBoard在这种情况下实现多态,然后将createMapBoard2改为createMapBoard。我在网上发现这就是所谓的Ad-Hoc多态现象,例如
class Square a where
square :: a -> a
instance Square Int where
square x = x * x
instance Square Float where
square x = x * x
尝试自己写类似的东西,我能想到的最好的方法如下:
class MyClass a b MapBoard where
createMapBoard :: a -> b -> MapBoard
instance createMapBoard [[(Int,Int)]] -> [[Square]] -> MapBoard where
createMapBoard indices squares = M.fromList $ zip (concat indices) (concat squares)
instance createMapBoard Int -> Int -> MapBoard where
createMapBoard n m = createMapBoard indices squares where
indices = defineIndices n m
squares = createBoard n m
尝试编译它会导致编译错误:
src/minesweeper.hs:35:19-26: error: …
Unexpected type ‘MapBoard’
In the class declaration for ‘MyClass’
A class declaration should have form
class MyClass a b c where ...
|
Compilation failed.
λ>
对于为什么不允许在类定义中使用非代数类型(例如MapBoard),我感到困惑。
class MyClass a b MapBoard where
用另一个代数类型c替换MapBoard会导致另一个编译错误,这对我来说是丢失的。
src/minesweeper.hs:37:10-63: error: …
Illegal class instance: ‘createMapBoard [[(Int, Int)]]
-> [[Square]] -> MapBoard’
Class instances must be of the form
context => C ty_1 ... ty_n
where ‘C’ is a class
|
src/minesweeper.hs:39:10-46: error: …
Illegal class instance: ‘createMapBoard Int -> Int -> MapBoard’
Class instances must be of the form
context => C ty_1 ... ty_n
where ‘C’ is a class
|
Compilation failed.
我是否可以实现createMapBoard的临时多态性?我是否可以创建一个类定义,该类定义对所有实例的返回类型必须为MapBoard进行严格限制?
编辑:
纠正了语法错误后,我的代码现在是:
class MyClass a b where
createMapBoard :: a -> b
instance createMapBoard [[(Int,Int)]] [[Square]] where
createMapBoard indices squares = M.fromList $ zip (concat indices) (concat squares)
instance createMapBoard Int Int where
createMapBoard n m = createMapBoard indices squares where
indices = defineIndices n m
squares = createBoard n m
这会导致另一个编译错误:
src/minesweeper.hs:37:10-23: error: …
Not in scope: type variable ‘createMapBoard’
|
src/minesweeper.hs:39:10-23: error: …
Not in scope: type variable ‘createMapBoard’
|
Compilation failed.
我倾向于认为,我对类的理解仍然存在错误。
您想这样写:
class MyClass a b where createMapBoard :: a -> b -> MapBoard
instance MyClass [[(Int,Int)]] [[Square]] where
createMapBoard indices squares = M.fromList $ zip ...
instance MyClass Int Int where
createMapBoard n m = createMapBoard indices squares where
...
在... -> ... -> MapBoard
已经在createMapBoard
方法的签名,这并不在类/实例头属于。
顺便说一句,我不认为在这里开设一门课真的很有意义。拥有两个单独命名的createMapBoard
函数没有错。如果您可以实际在其上编写多态函数,则只有走一类的路要走,但是在这种情况下,我对此表示怀疑–您宁愿遇到需要一个版本或另一个版本的具体情况。然后就不需要一个类了,只需硬编写所需的版本即可。
宁愿使用单独的函数而不使用类方法的一个原因是,它使类型检查器的工作更加容易。一旦参数createMapBoard
是多态的,它们就可能具有任何类型(至少就类型检查器而言)。因此,只能使用其类型在其他位置完全确定的参数来调用它。现在,在其他编程语言中,您可能想要传递的值的类型通常无论如何都是固定的,但是在Haskell中,实际上也很常见也具有多态值。最简单的例子是数字文字–它们没有类型Int
,而是Num a => a
。
我个人发现“反向多态性”通常比“正向多态性”更适合使用:不要使函数的参数成为多态性,而要使结果多变。这样,由环境固定表达式的最外面的类型就足够了,并且类型检查器会自动推断所有子表达式。反之,则必须固定所有单个表达式的类型,并且编译器可以推断最终结果类型...这几乎没有用,因为您可能仍想通过签名对其进行修复。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句