我记得在Erlang中看到,递归函数的包装函数有时会传递一个原子,该原子确定递归是在第一次迭代(n = 1)还是一些连续迭代(n> 1)。当递归函数在第一次迭代后需要更改其行为时,这很有用。这种模式叫什么?
此外,这种模式在Haskell中也适用吗?我用它编写了一个小片段,查看first
布尔值:
import Data.Char (digitToInt, isDigit)
data Token = Num Integer deriving (Show)
tokeniseNumber :: String -> (String, Maybe Token)
tokeniseNumber input = accumulateNumber input 0 True
where
accumulateNumber :: String -> Integer -> Bool -> (String, Maybe Token)
accumulateNumber [] value True = ([], Nothing)
accumulateNumber [] value False = ([], Just (Num value))
accumulateNumber input@(peek:tail) value first =
case isDigit peek of
False | first -> (input, Nothing)
False | not first -> (input, Just (Num value))
True -> accumulateNumber tail (value * 10 + (toInteger . digitToInt) peek) False
- 编辑 -
zxq9发布了一个答案,后来被删除了。但我认为答案是有道理的。
这可以更清晰地定义为一组单独的函数,每个函数都以某种特定的方式运行,并且函数头匹配确定要调度哪些函数(Haskell在此提供了更广泛的基于类型的函数匹配工具)。换句话说,某种风格的“有限状态机”就是你要找的东西。
状态可以设置为函数名称或状态参数;使用哪个取决于上下文和语言,这可以扩展到作为函数名称的state参数,并且它本身就是一种匹配。
Haskell的最佳选择通常不是最适合Erlang的。许多一次性任务被委托给Erlang中的单独进程,甚至Erlang中的进程实例化在调用init时也会经历一个“初始状态”,这与当你说“递归函数需要更改它时的情况基本相同”第一次迭代后的行为“。 OTOH,Haskell提供了更多匹配函数头的方法。在任何一种情况下,采用命名函数定义操作状态的方法更清晰。结果将是不嵌套的代码,不需要程序条件,并且可以更容易地从任何地方调用(稍后当您重新编写程序时更灵活地处理......)。
FSM是确定基于状态执行什么代码的一般方法,并且函数(或进程)的初始化是一种特殊情况。我听说这个称为“传递初始化”,如入口函数定义接口,进行一次性处理以设置主程序并将执行传递给它:
init(Args) -> {ok, NewArgs} = one_time_processing(Args), loop(NewArgs). loop(Args) -> {ok, NewArgs} = do_stuff(Args), loop(NewArgs).
当然,上面是一个无限循环,所以它更常见于在循环/ 1函数结束时检查退出,或者(更常见的)在循环的函数头中匹配:
loop(done, Args) -> Args; loop(cont, Args) -> {Cond, NewArgs} = do_stuff(Args), loop(Cond, NewArgs).
但是在任何一种情况下,最好将进程的初始化作为自己的过程,从循环体中分别定义。具有循环结构的其他语言处理这种情况的方式不同,条件检查的某些组合基于程序员选择的循环样式以特殊方式应用,但效果是相同的。通常,在程序上实现此方法最明显的方法是执行相同的操作:将整个循环包装在函数调用之后,循环之前的步骤是“一次”初始化部分。在这种情况下,它不是循环在函数调用中“包装”,而是你编写一个接口函数来访问它,它在调用它的过程中进行一些一次性初始化。
答案 0 :(得分:4)
要扩展我对boolean blindness的评论,我不仅仅意味着使用其他类型同构2
,而是使用正确的类型来编码原因你的递归函数关心它是哪个迭代。
将您的代码与以下版本进行比较,我会说更干净,更有吸引力。它取决于将Maybe Integer
而不是(Integer, Bool)
传递给accumulateNumber
。
import Data.Char (digitToInt, isDigit)
import Data.Maybe
import Control.Applicative
data Token = Num Integer deriving (Show)
tokeniseNumber :: String -> (String, Maybe Token)
tokeniseNumber input = accumulateNumber input Nothing
where
accumulateNumber :: String -> Maybe Integer -> (String, Maybe Token)
accumulateNumber input@(peek:tail) value
| isDigit peek = accumulateNumber tail (Just $ toNum (fromMaybe 0 value) peek)
accumulateNumber input value = (input, Num <$> value)
toNum value peek = value * 10 + (toInteger . digitToInt) peek
答案 1 :(得分:0)
还想指出我发现了一篇讨论这种确切技术的学术论文。
它被Andy Gill&amp; amp;所谓的“工人/包装变革”称为Graham Hutton(2009)