我正在为纸牌游戏制作一些代码:
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一样的辅助函数吗?是否有更清晰的方式来写这个?有没有命名约定只是在辅助函数名称的末尾加上下划线?
答案 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 ...