Haskell: how to evaluate a String like "1+2"
Asked Answered
D

4

11

Actually I have some formula like "x + y", which is a String. I managed to replace the x/y variable with specific values like "1.2", which is still String type. Now I have expression like "1 + 2".

So the problem is how to evaluate a expression of a string type and get the result.

ps: I wanna sth like read, that can directly convert the whole string expression instead of handling the operator (+/-,etc) case by case. Is that possible?

Defiant answered 7/4, 2011 at 14:42 Comment(6)
Of course, it is possible. But I feel that you should use a better representation for expressions. One clue may be your phrase "convert the whole string expression". The question is: convert to what?Peonir
I think yoz wants to evaluate it, and is using the term convert as 'convert to a result'.Emmerich
@mdm, sure, that's why I am asking: to make him realize that conversion and evaluation are different things. Either he evaluates the string directly (which he declined already), or he converts (to what? this is the key question) and then evaluate that.Peonir
Do you want something like eval "1+2" :: Int?Harlene
@Harlene yes, I would like to see something like that. Does it exist in standard haskell (without funky packages...) ?Basis
What kind of expressions do you want to support? Text.Parsec's part of the haskell platform and lets you do: runP (do { x <- many1 digit; spaces; string "+"; spaces; y <- many1 digit; return $ read x + read y }) () "-" "1 + 2" returns Right 3. But you probably want more than just add. Do you want a calculator? A full fledged haskell interpreter?Harlene
I
24

Your question leaves a lot of room for interpretation. I'm taking a guess you aren't accustom to building a whole pipeline of lexing, parsing, maybe type checking, and evaluating. The long answer would involve you defining what language you wish to evaluate (Just integers with '+', perhaps all rationals with '+', '-' '*', '/', or even a larger language?) and perform each of the above steps for that language.

The short answer is: to evaluate Haskell expressions, which includes the basic math operators you're probably talking about, just use the "hint" package:

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

Yay!

Idellaidelle answered 7/4, 2011 at 16:47 Comment(4)
I was trying to find a way to evaluate a String expression without lexical analysis, grammar definitions etc. C# can achieve this by compiling code at runtime. So actually I was just wondering whether there's sth similar in Haskell. Your answer is exactly what I need. Thank a lot~Defiant
@Defiant Glad I could help. Obviously you can read the haddock documents yourself, but really quickly the interpret function will give you a result of polymorphic type (not a string representation), which might be even better depending on your needs. Happy hacking.Idellaidelle
Any way to do this with standard Haskell, without installing hint ?Basis
Glasgow Haskell provides an API, which is what hint uses. Otherwise (with pure Haskell as appears in the spec) you are out of luck and must write an interpreter or other custom solution..Idellaidelle
K
4

Might be worth reading the Parsec section of Real World Haskell. You could parse it into an expression tree and then substitute the values in. As you use Parsec you'd build up an expression tree using types (very roughly, I'm sure I've made some mistakes which I'll edit in fixes for as and when people point them out!) like that below.

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

Then 1 + 2 would be (Expr (Variable "x") Plus (Variable "y")) and you could apply the appropriate substitutions.

To get the result, I guess you could right a simple function evaluate :: Map String Int -> Expression -> Either ErrorMessage Int which would apply the bindings in the map and then calculate the result if possible.

Kitty answered 7/4, 2011 at 14:48 Comment(1)
I think @Defiant expected something more of an inbuilt function eval :: String -> Double. A little more haskellish would perhaps be eval :: String -> Either ErrorMessage Double. However, as most have pointed out: Expressions represented as strings is bad. Better use a syntax tree like Jeff showed.Achaea
H
3

Well I've been banging my head against hint but I give up for now. I know hint can do this but I'm not sure how. [edit] See TomMD's answer for how to set imports up for hint. [/edit]

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

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

This uninterestingly produces Right "3" as the result. I tried the following variants, only to run into baffling errors:

... eval "3 + 3" ....
-- yields --
Left (WontCompile [GhcError (errMsg = "Not in scope: `+'"])

The + operator isn't in scope??? wtf...

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'")])

The Int class isn't in scope??? ugh...

I invite those more knowledgeable than me to expound on the finer details of hint.

Higgs answered 7/4, 2011 at 16:42 Comment(1)
You need to setImports, see my answer.Idellaidelle
P
0

The accepted answer shows a minimal example of using the hint, but it lacks couple of things:

  1. How to evaluate using bindings like let x = 1 in x + 1.
  2. How to handle exceptions, specifically divide by zero.

Here is a more complete example:

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
Palpitation answered 24/1, 2023 at 10:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.