当 boolean/tagged 联合确定递归函数的当前执行是否在其第一次迭代时,术语是什么?

What's the terminology for when a boolean/tagged union determines whether the current execution of a recursive function is at its first iteration?

我记得在 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 发布了一个答案,后来被删除了。但其实我觉得这个回答还是有道理的。

This is cleaner to define as a set of separate functions that each behave some specific way, and a function head match that determines which of those functions to dispatch (Haskell provides a broader array of type-based function matching tools here). In other words, a certain style of "finite state machine" is what you are looking for.

The states can be styled as function names or as a state argument; which to use depends on the context and language, and this can extend to the state argument being a function name and that itself being a sort of match.

What is best for Haskell is usually not what works best for Erlang. Many one-off tasks are delegated to separate processes in Erlang, and even process instantiation in Erlang goes through an "init state" when it calls init, which is essentially the same thing as when you say "when a recursive function needs to change its behaviour after the first iteration". OTOH, Haskell provides more ways to match on a function head. In either case taking an approach where a named function defines an operating state is cleaner. The result will be code that is not nested, doesn't require procedural conditionals, and can be called from anywhere more easily (more flexibly dealt with when you re-write your program later...).

FSMs are a general way of determining what code to execute based on state, and initialization of a function (or process) is a special case of that. I've heard this called "pass-through initialization" as in, the entry function defines the interface, does one-time processing to set up the main procedure and passes execution through to it:

init(Args) ->
      {ok, NewArgs} = one_time_processing(Args),
      loop(NewArgs).

loop(Args) ->
      {ok, NewArgs} = do_stuff(Args),
      loop(NewArgs).

Of course, the above is an infinite loop, so its more common to either have a check for exit at the end of the loop/1 function, or (more often) a match in the function head of loop:

loop(done, Args) ->
      Args;
loop(cont, Args) ->
      {Cond, NewArgs} = do_stuff(Args),
      loop(Cond, NewArgs).

But in either case it is better to have the initialization of a process be its own procedure, separately defined from whatever the body of the loop is. Other languages with looping constructs handle this differently with some combination of conditional checks applied a special way based on which style of loop the programmer chooses, but the effect is the same. Very often the most obvious way to implement this procedurally is to do the same: wrap the whole loop behind a function call, and the steps that precede the loop are the "one time" initialization parts. In this case its not that the loop is "wrapped" in a function call, but that you write an interface function to access it which does some one-time initialization on the way to calling it.

为了扩展我对 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

还想指出,我发现了一篇讨论这种确切技术的学术论文。

它被 Andy Gill 和 Graham Hutton (2009) 称为 "The worker/wrapper transformation"

Link: http://www.cs.nott.ac.uk/~gmh/wrapper.pdf