在Haskell中为状态传递样式idiomatic编写辅助函数?

时间:2014-11-01 02:02:19

标签: haskell idiomatic

我正在为纸牌游戏制作一些代码:

splitCards_ (x:xs) (a,b,c) | isGold x   = splitCards xs (a:x,b,c) 
                           | isAction x = splitCards xs (a,b:x,c)
                           | isVP x     = splitCards xs (a,b,c:x)
splitCards_ [] (a,b,c) = (a,b,c)

splitCards xs = splitCards_ xs ([],[],[]) 

基本上,取一张牌列表,并根据牌的类型将其分成三个不同的列表。 splitCards_通过递归更新它的参数来表示状态更新,然后splitCards(实际函数)用于始终以三个特定类型的卡列表为空来开始计算。

我相信这被称为状态传递风格,我很确定这是完全惯用的,但我更关心的是我必须定义辅助函数splitCards_才能得到这个以我想要的方式工作。正在创建像这个惯用的Haskell一样的辅助函数吗?是否有更清晰的方式来写这个?有没有命名约定只是在辅助函数名称的末尾加上下划线?

3 个答案:

答案 0 :(得分:8)

是的,这是完全惯用的风格。这是使函数尾递归的经典方法。 (虽然在Haskell中这不那么重要,但稍微有些细微差别)。

此外,制作辅助函数绝对是好的,并且是许多常见模式的关键部分。如果你觉得某些东西更自然,那就去吧!除非它被带到极端,否则它有助于提高可读性。

我的主要建议是将辅助函数放入where子句中。这样,它只会在main函数的范围内可见,这使得它只是一个帮助器。你给辅助函数的名字不太重要; splitCards_很好,但splitCards'(发音为“splitCards prime”)和go(辅助函数的通用名称)会更常见。所以我会重写你的代码:

splitCards xs = go xs ([],[],[]) 
  where go (x:xs) (a, b, c) | isGold x   = go xs (a:x,b,c) 
                            | isAction x = go xs (a,b:x,c)
                            | isVP x     = go xs (a,b,c:x)
        go [] (a, b, c) = (a, b, c)

请注意,这些只是化妆品的变化 - 您正在做的是从根本上声音。

两个命名选项(go vs splitCards')只是一个偏好问题。

我个人喜欢go,因为一旦你习惯了惯例,它就会清楚地表明某些东西只是一个辅助函数。从某种意义上说,go几乎更像语法而不是它自己的函数;它的意思纯粹是从属于splitCards函数。

其他人不喜欢go,因为它有点神秘,有点武断。它也可能使代码的递归结构不那么清晰,因为你在go而不是函数本身进行递归。

去寻找你认为最好的东西。

答案 1 :(得分:3)

除了在这种特定情况下您可以使用partition中的Data.List函数之外,我没有太多要添加到@ Tikhon的答案中:

splitCards xs = 
  let (as,bs) = partition isGold xs
      (cs,ds) = partition isAction bs
  in (as,cs,ds) -- (golds, actions, others)

由于懒惰的评估,它应该与您手工制作的版本具有基本相同的性能。

答案 2 :(得分:1)

在一般情况下,代码与您的代码是惯用的,尤其是如@Tikhon建议的那样清理一下。在特定情况下,您的递归方案似乎是左侧折叠,因此通常在代码中显式。以@ Tikhon的代码为基础:

splitCards xs = foldl' separate ([],[],[]) xs
  where separate (a, b, c) x | isGold x   = (a:x,b,c) 
                             | isAction x = (a,b:x,c)
                             | isVP x     = (a,b,c:x)

第一行甚至可以签约(在这种情况下不太需要,恕我直言,但......)

splitCards = foldl' separate ([],[],[])
  where ...