如何在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)]]
?> 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]]

从这里开始,我创建了一个函数来创建MapBoard,可以在其中查找特定 Square 的值给定其索引。

?> defineIndices 2 3
[[(1,1),(1,2),(1,3)],[(2,1),(2,2),(2,3)]]

但是,对我来说,我还应该编写一个方法,在该方法中我可以直接从一对 Int(s) 创建 MapBoard,实现我之前的功能,这似乎是合理的。这可能看起来像:

type MapBoard = Map (Int, Int) Square

createMapBoard :: [[(Int,Int)]] -> [[Square]] -> MapBoard
createMapBoard indices squares = M.fromList $ zip (concat indices) (concat squares)

但是,我查看了在这种情况下是否可以使用createMapBoard实现多态性,并使用createMapBoard2代替createMapBoard。我在网上发现这叫做 Ad-Hoc 多态性,可以做例如

createMapBoard2 :: Int -> Int -> MapBoard
createMapBoard2 n m = createMapBoard indices squares where
  indices = defineIndices n m
  squares = createBoard n m

试图自己写一些类似的东西,我能想到的最好的是以下内容:

class Square a where
    square :: a -> a
    
instance Square Int where
    square x = x * x

instance Square Float where
    square x = x * x 

尝试编译它会导致编译错误:

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.
?> 
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
  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

用另一种代数类型 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?

编辑:

纠正了语法错误后,我的代码现在是:

这导致了另一个编译错误:

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.

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

  • @EoinDowling Nah, two functions merely producing the same thing isn't a good enough reason to name them the same thing. We don't try to make `abs` and `length` have the same name, even though they both produce an `Int`.

以上是如何在Haskell中以多态方式为多种类型编写多个函数定义?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>