Haskell:如何评估类似于“1 + 2”的字符串

11

其实我有一些类似于"x + y"这样的公式,它是一个String类型的。

我已经使用具体数值(例如"1.2")替换了x/y变量,但它们仍然是String类型。现在我有了一个类似"1 + 2"的表达式。

所以问题是如何计算字符串类型的表达式并得到结果。

附:我想要像read这样的东西,可以直接转换整个字符串表达式,而不是逐个处理运算符(+/-等)情况。这可能吗?


1
当然可以。但我认为你应该使用更好的表达方式。一个线索可能是你的短语“将整个字符串表达式转换”。问题是:转换成什么? - Ingo
我认为yoz想要评估它,并将“转换”用作“转换为结果”的术语。 - mdm
@mdm,没错,这就是我问的原因:让他意识到转换和评估是不同的事情。他可以直接评估字符串(他已经拒绝了),或者将其转换(转换成什么?这是关键问题),然后再进行评估。 - Ingo
你想要类似于 eval "1+2" :: Int 这样的东西吗? - rampion
@rampion 是的,我想看到这样的东西。它是否存在于标准的Haskell中(没有奇怪的包)? - vikingsteve
你想支持哪种表达式?Text.Parsec是Haskell平台的一部分,它可以让你做到:runP (do { x <- many1 digit; spaces; string "+"; spaces; y <- many1 digit; return $ read x + read y }) () "-" "1 + 2"返回Right 3。但你可能不仅仅想要加法。你想要一个计算器吗?一个完整的Haskell解释器? - rampion
4个回答

24

您的问题存在很大的理解空间。我猜测您可能不习惯构建整个词法分析、语法分析、类型检查和求值管道。长答案需要您定义要评估的语言(仅带'+'的整数,也许包括所有有'+', '-', '*', '/'运算符的有理数,甚至更大的语言?),并对该语言执行上述每个步骤。

简短的答案是:要评估Haskell表达式,其中包括您可能在谈论的基本数学运算符,请使用"hint"包:

$ cabal install hint
...
$ ghci
> import Language.Haskell.Interpreter
> runInterpreter $ setImports ["Prelude"] >> eval "3 + 5"
Right "8"

太棒了!


我正在尝试找到一种无需词法分析、语法定义等方式来评估字符串表达式的方法。在C#中,可以通过在运行时编译代码来实现这一点。因此,我只是想知道Haskell中是否有类似的东西。你的答案正是我需要的。非常感谢~ - yoz
@yoz 很高兴我能帮上忙。显然,你可以自己阅读 haddock文档,但 interpret 函数将快速为您提供多态类型的结果(而不是字符串表示),根据您的需要,这可能会更好。祝编程愉快。 - Thomas M. DuBuisson
有没有使用标准的 Haskell 实现这个功能的方法,而不需要安装 hint - vikingsteve
Glasgow Haskell提供了一个API,这就是hint使用的内容。否则(仅使用规范中出现的纯Haskell),你就需要编写解释器或其他自定义解决方案。 - Thomas M. DuBuisson

4

阅读Real World HaskellParsec部分可能会很有价值。您可以将其解析为表达式树,然后替换其中的值。使用Parsec时,您将使用以下类型(非常粗略,我肯定犯了一些错误,我将在人们指出错误时进行修正!)构建表达式树。

 data Op = Plus | Minus
 data Term = Variable String
           | Value Int
 data Expression = Expr Expression Op Expression
                 | Term

然后 1 + 2 就会变成 (Expr (Variable "x") Plus (Variable "y")),你可以应用相应的替换。为了得到结果,我猜你可以编写一个简单的函数evaluate :: Map String Int -> Expression -> Either ErrorMessage Int,它将应用映射中的绑定,然后在可能的情况下计算结果。

1
我认为@yoz期望有一个更内置的函数eval :: String -> Double。更具Haskell风格的可能是eval :: String -> Either ErrorMessage Double。然而,正如大多数人指出的那样:用字符串表示表达式是不好的。最好使用像Jeff展示的语法树。 - Tarrasch

3

我一直在苦苦思索如何使用hint,但现在我放弃了。我知道hint可以做到这一点,但我不确定如何操作。

[编辑]请参考TomMD的答案,了解如何为hint设置导入。

import Language.Haskell.Interpreter (eval, runInterpreter, Interpreter, InterpreterError)

main = do let resIO = eval "3" :: Interpreter String
          res <- runInterpreter resIO
          print res

这样做会无聊地产生 Right "3" 作为结果。我尝试了以下变体,但遇到了令人困惑的错误:

... eval "3 + 3" ....
-- yields --
Left (WontCompile [GhcError (errMsg = "Not in scope: `+'"])
+ 运算符不在范围内???什么鬼...
import Language.Haskell.Interpreter (interpret, as, runInterpreter, Interpreter)

main = do let resIO = interpret "3" (as :: Int) :: Interpreter Int
          res <- runInterpreter resIO
          print res
-- yields --
Left (WontCompile [GhcError (errMsg = "Not in scope: type constructor or class 'Int'")])

Int类不在作用域内???烦人...

我邀请比我更有知识的人来详细阐述提示的细节。


4
你需要使用setImports,请参考我的回答。 - Thomas M. DuBuisson

0

被接受的答案展示了使用提示的最小示例,但它缺少一些东西:

  1. 如何使用绑定进行求值,例如let x = 1 in x + 1
  2. 如何处理异常,特别是除以零。

这里是一个更完整的示例:

import qualified Control.DeepSeq as DS
import Control.Exception (ArithException (..))
import qualified Control.Exception as Ex
import qualified Control.Monad as M
import qualified Data.Either as E
import qualified Language.Haskell.Interpreter as I

evalExpr :: String -> [(String, Integer)] -> IO (Maybe Integer)
evalExpr expr a = Ex.handle handler $ do
  i <- I.runInterpreter $ do
    I.setImports ["Prelude"]
    -- let var = value works too
    let stmts = map (\(var, val) -> var ++ " <- return " ++ show val) a
    M.forM_ stmts $ \s -> do
      I.runStmt s

    I.interpret expr (I.as :: Integer)

  -- without force, exception is not caught
  (Ex.evaluate . DS.force) (E.either (const Nothing) Just i)
  where
    handler :: ArithException -> IO (Maybe Integer)
    handler DivideByZero = return Nothing
    handler ex = error $ show ex

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接