我如何才能在 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]]

我创建了两个函数:

  1. 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)]]
  1. 函数 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.

我个人认为“反向多态性”通常比“正向多态性”更好用:不要使函数的 参数 多态,而是 结果。这样,环境固定表达式的最外层类型就足够了,类型检查器会自动推断出所有子表达式。相反,您必须固定所有单个表达式的类型,并且编译器可以推断出最终结果类型……这几乎没有用,因为您可能想通过签名来修复它。