我如何才能在 Haskell 中以多态方式为多种类型编写多个函数定义?
How might I be able to write multiple function definitions for multiple types in a polymorphic manner in 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 生成值元组的二维列表——或 '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。我发现 online 这称为 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.
λ>
我很困惑为什么不允许我在 class 定义中使用非代数类型,例如 MapBoard。
class MyClass a b MapBoard where
将 MapBoard 替换为另一种代数类型 c 会导致另一个编译错误,这让我迷失了。
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的ad-hoc多态吗?我是否可以创建一个 class 定义,它严格限制所有实例的 return 类型必须是 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.
我倾向于认为我对 classes 的理解错误仍然存在。
你想这样写:
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
方法的签名中,这不属于 class / 实例头。
顺便说一下,我不相信在这里设置 class 真的有意义。有两个单独命名的 createMapBoard
函数没有错。如果您实际上可以在其上编写多态函数,则 class 才是可行的方法,但在这种情况下我对此表示怀疑 - 您宁愿在需要一个版本或另一个版本的具体情况下。那么就不需要 class,只需硬写你想要的版本即可。
宁愿使用单独的函数而不是 class 方法的一个原因是它使类型检查器的工作更容易。一旦 createMapBoard
的参数是多态的,它们就可能具有 any 类型(至少就类型检查器而言)。所以你只能用类型在别处完全确定的参数来调用它。现在,在其他编程语言中,您可能想要传递的值的类型通常是固定的,但在 Haskell 中,实际上也非常常见 多态值 。最简单的例子是数字文字——它们没有 Int
左右的类型,但是 Num a => a
.
我个人认为“反向多态性”通常比“正向多态性”更好用:不要使函数的 参数 多态,而是 结果。这样,环境固定表达式的最外层类型就足够了,类型检查器会自动推断出所有子表达式。相反,您必须固定所有单个表达式的类型,并且编译器可以推断出最终结果类型……这几乎没有用,因为您可能想通过签名来修复它。
根据我的类型定义:
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 生成值元组的二维列表——或 '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。我发现 online 这称为 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.
λ>
我很困惑为什么不允许我在 class 定义中使用非代数类型,例如 MapBoard。
class MyClass a b MapBoard where
将 MapBoard 替换为另一种代数类型 c 会导致另一个编译错误,这让我迷失了。
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的ad-hoc多态吗?我是否可以创建一个 class 定义,它严格限制所有实例的 return 类型必须是 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.
我倾向于认为我对 classes 的理解错误仍然存在。
你想这样写:
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
方法的签名中,这不属于 class / 实例头。
顺便说一下,我不相信在这里设置 class 真的有意义。有两个单独命名的 createMapBoard
函数没有错。如果您实际上可以在其上编写多态函数,则 class 才是可行的方法,但在这种情况下我对此表示怀疑 - 您宁愿在需要一个版本或另一个版本的具体情况下。那么就不需要 class,只需硬写你想要的版本即可。
宁愿使用单独的函数而不是 class 方法的一个原因是它使类型检查器的工作更容易。一旦 createMapBoard
的参数是多态的,它们就可能具有 any 类型(至少就类型检查器而言)。所以你只能用类型在别处完全确定的参数来调用它。现在,在其他编程语言中,您可能想要传递的值的类型通常是固定的,但在 Haskell 中,实际上也非常常见 多态值 。最简单的例子是数字文字——它们没有 Int
左右的类型,但是 Num a => a
.
我个人认为“反向多态性”通常比“正向多态性”更好用:不要使函数的 参数 多态,而是 结果。这样,环境固定表达式的最外层类型就足够了,类型检查器会自动推断出所有子表达式。相反,您必须固定所有单个表达式的类型,并且编译器可以推断出最终结果类型……这几乎没有用,因为您可能想通过签名来修复它。