首页 > 解决方案 > 如何在 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生成一个 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)]]
  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。我在网上发现这被称为 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.

我倾向于相信我对类的理解仍然存在错误。

标签: haskellpolymorphism

解决方案


你想这样写:

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.

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


推荐阅读