【问题标题】:Haskell : How do I compose a recursive function that takes an element and gives gives back its list, but having different data type?Haskell:如何编写一个递归函数,它接受一个元素并返回其列表,但具有不同的数据类型?
【发布时间】:2012-02-06 07:20:41
【问题描述】:

假设我有如下数据类型:

 data Cell = Cell (Maybe Player)
 data Board = Board [[Cell]]

现在我想生成一个这样的递归函数:

 genBoard :: [Cell] -> Board
 genBoard [] = []
 genBoard c = (take 3 c) : (genBoard $ drop 3 c) -- takes list of 9 Cells and gives 3x3 list of cells

显然,上述代码失败了,因为 (:) 不能将 [Cell] 添加到 Board,尽管从技术上讲,Board 只不过是 [[Cell]]。我需要将 Board 作为单独的数据类型来为其提供我自己的显示功能。

到目前为止,我想出的最好的是:

genBoardList :: [Cell] -> [[Cell]]
genBoardList [] = []
genBoardList c =  (take 3 c) : (genBoardList $ drop 3 c)

boardListToBoard :: [[Cell]] -> Board
boardListToBoard [] = Board []
boardListToBoard s = Board s

genBoard :: [Cell] -> Board
genBoard = boardListToBoard . genBoardList

但这似乎有点太长了,而且很难完成一件看似简单的事情。有什么想法可以改进我的代码吗?

【问题讨论】:

  • 顺便说一句,您的boardListToBoard 函数可以替换为Board(即genBoard = Board . genBoardList)。另请注意,无需单独处理 [] 案例。

标签: haskell types recursion


【解决方案1】:

您只需使用模式匹配从Board 构造函数中解开列表,然后在每个步骤中将其重新包装;例如,使用let...in:

genBoard :: [Cell] -> Board
genBoard [] = []
genBoard cs =
    let Board css = genBoard (drop 3 cs)
    in Board (take 3 cs : css)

或者,更惯用的说法是where 子句:

genBoard :: [Cell] -> Board
genBoard [] = []
genBoard cs = Board (take 3 cs : css)
  where
    Board css = genBoard (drop 3 cs)

另一个改进是使用模式匹配代替takedrop

genBoard :: [Cell] -> Board
genBoard [] = []
genBoard (c0:c1:c2:cs) = Board $ [c0, c1, c2] : css
  where
    Board css = genBoard cs

您还可以使用 split 包使其更简单:

genBoard :: [Cell] -> Board
genBoard = Board . splitEvery 3

【讨论】:

    【解决方案2】:

    您可以使用提升功能将[[Cell]] 上的功能转换为Board 上的功能:

    liftBoard :: ([[Cell]] -> [[Cell]]) -> Board -> Board
    liftBoard f (Board css) = Board (f css)
    

    有了这个,您可以将(take 3 c) : 转换为可以在递归genBoard 调用返回的Board 上使用的东西:

    genBoard :: [Cell] -> Board
    genBoard [] = Board []
    genBoard c  = liftBoard (take 3 c :) $ genBoard (drop 3 c)
    

    【讨论】:

      【解决方案3】:

      请注意,对于只是包装其他类型的类型,最好使用 newtype(没有运行时开销,newtypes 只是一种编译时工具):

      newtype Board = Board [[Cell]]
      

      您也可以使用记录语法为您制作“解构器”:

      newtype Board = Board { boardContent :: [[Cell]] }
      

      然后您可以简单地将 [[Cell]] 上的功能“提升”为 Board 上的功能:

      liftBoard f = Board . f . boardContent
      
      genBoard cs  = liftBoard (take 3 cs :) (genBoard (drop 3 cs))
      

      但是,如果您只使用类型同义词,那么所有这些都可能根本不需要。您是否真的需要为您的电路板提供数据类型,或者您是否只是为了方便和文档而给它命名?使用 newtype 或 data 的优点是您将无法混合使用 [[Cell]] 和 Board,但您真的将 [[Cell]] 用于除您的 board 之外的任何其他东西吗?如果您使用类型同义词,您也将无法在 Board 上定义新实例,这会打扰您吗?

      如果两个问题的答案都不是,请使用:

      type Board = [[Cell]]
      
      genBoard :: [Cell] -> Board
      genBoard [] = []
      genBoard cs =  take 3 cs : genBoard (drop 3 cs)
      

      【讨论】:

        猜你喜欢
        • 2017-02-03
        • 2011-05-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-05-29
        • 2016-07-07
        • 2016-06-21
        • 2010-10-20
        相关资源
        最近更新 更多