我怎么能把它写成隐式递归?

时间:2014-11-12 17:39:53

标签: haskell

我正在尝试编写一个模块,该模块可以采用简单方程的字符串表示,并对其进行评估。作为第一步,我需要将等式划分为单个令牌;基于takePiece的“规则”。我试着写cutEqu作为折叠,但遇到了我不是真的折叠在字符串上的问题;我正在服用不同大小的碎片,直到它耗尽。我可以作弊,让它折叠在一个等于字符串长度的数字列表上,但这看起来很笨拙。

在我读过的大多数指南中,他们注意到一旦你理解了隐式递归模式(比如折叠和贴图),显式递归是罕见的,这似乎是一个潜在的常见场景,但找不到适当的标准功能来处理它。如何使用隐式递归来编写像cutEqu这样简单的东西?我确信我可以找出一个简单的函数来封装行为,但是标准库中尚未存在它表明我可能会认为这个场景是错误的。

import Data.Char

isLit :: String -> Bool
isLit = isDigit . head

takePiece :: String -> (String,String)
takePiece str
    | isLit str = break (not . isDigit) str
    | otherwise = splitAt 1 str

cutEqu :: String -> [String]
cutEqu [] = []
cutEqu xs = let (p,rest) = takePiece xs in
            p : cutEqu rest

编辑: 这是我试图隐含地写它:

consumeWith :: ([a] -> ([b],[a])) -> [a] -> [[b]]
consumeWith _ [] = []
consumeWith f xs = let (t, rest) = f xs in
    t : consumeWith f rest

cutEqu' :: String -> [String]
cutEqu' = consumeWith (takePiece)

但同样,我担心这样的事情不是标准功能。有没有更好的方法来解决这个问题?

2 个答案:

答案 0 :(得分:5)

该模式称为unfoldr,其类型为签名:

unfoldr :: (b -> Maybe (a, b)) -> b -> [a]

您的cutEq函数可以按unfoldr这样的方式编写:

import Data.List

cutEq' str = unfoldr go str
  where go [] = Nothing
        go xs = Just $ takePiece xs

答案 1 :(得分:1)

我不认为隐式与显式递归是你应该关注的问题。我建议你做的第一件事就是将这个问题分成两部分:

  1. 解析:获取字符串并生成表示公式的抽象语法树(“AST”)。
  2. 评估:获取AST并生成结果。
  3. 我无法从你的代码中看出你试图在这里实现的等式语言的结构是什么。这是我开始的地方,而不是解析:定义AST的数据类型。例如,这是一个适用于简单表达语言的AST,包含变量,数字文字,加法,减法和乘法:

    import Control.Applicative (liftA2)
    import Data.Map (Map)
    import qualified Data.Map as Map
    
    -- | The abstract syntax tree for the expression language.
    data Expr a = Var String
                | Lit a
                | Add (Expr a) (Expr a)
                | Sub (Expr a) (Expr a)
                | Mul (Expr a) (Expr a)
        deriving Show
    
    
    -- | Evaluate an 'Expr', using a 'Map' of variable names to values to represent the
    -- evaluation environment.
    eval :: Num a => Map String a -> Expr a -> Maybe a
    eval env (Var v) = Map.lookup v env
    eval env (Lit a) = Just a
    eval env (Add expr1 expr2) = liftA2 (+) (eval env expr1) (eval env expr2)
    eval env (Sub expr1 expr2) = liftA2 (-) (eval env expr1) (eval env expr2)
    eval env (Mul expr1 expr2) = liftA2 (*) (eval env expr1) (eval env expr2)
    

    然后,为了完成实现,您可以使用此签名编写一个函数:

    parse :: String -> Maybe Expr
    

    我会说,事实上,像Expr这样的AST类型的显式递归对于像这样的简单解释器来说是一个很好的解决方案。 “prefer implicit recursion”建议更适用于列表等集合类型,因为它使代码更易于编写和读取。但是在简单的基于AST的解释器的情况下,你想要做的是定义AST,以便它作为编写简单评估规则的脚手架,说明表达式的值如何与其子表达式的值相关(称为compositionality)。