haskell - 如何在 Haskell 中以多态方式为多种类型编写多个函数定义?
问题描述
鉴于我的类型定义:
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生成一个 2D 值元组列表 - 或'Board'。它初始化所有相同值的维度 n*m 的列表。
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的目的是为了创建由 createBoard 生成的 Board(s) 的索引的有序列表。
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 的值给定它的索引。
type MapBoard = Map (Int, Int) Square
createMapBoard :: [[(Int,Int)]] -> [[Square]] -> MapBoard
createMapBoard indices squares = M.fromList $ zip (concat indices) (concat squares)
但是,对我来说,我还应该编写一个方法,在该方法中我可以直接从一对 Int(s) 创建 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
.
我个人发现“反向多态”通常比“正向多态”更好用:不要让函数的参数多态,而要让结果多态。这样,由环境固定表达式的最外层类型就足够了,并且类型检查器会自动推断出所有子表达式。反过来,您必须修复所有单个表达式的类型,并且编译器可以推断最终结果类型......这几乎没有用,因为您可能无论如何都想通过签名来修复它。
推荐阅读
- ios - 当系统主题改变时,如何改变UIToolBar的颜色?
- android - Android资源链接失败问题
- fortran - 在 fortran 中分配矩阵 - 无效的内存引用
- sql-server - Dmax 搜索最大日期返回表中的最终日期并且不考虑条件
- vala - 无法创建 SinkInputInfo 数组
- python - Scipy linregress 线没有用散点图准确绘制
- css - 如何在页脚中垂直对齐这些 div?
- python - PoolProcessExecutor 和子进程 Python
- javascript - Javascript toFixed, Math.round
- javascript - 未从异步 forEach 外部接收数据