Equivalent of python eval in Haskell
Asked Answered
D

7

13

There is function in python called eval that takes string input and evaluates it.

>>> x = 1
>>> print eval('x+1')
2
>>> print eval('12 + 32')
44
>>>  

What is Haskell equivalent of eval function?

Dekow answered 18/3, 2010 at 10:49 Comment(0)
F
26

It is true that in Haskell, as in Java or C++ or similar languages, you can call out to the compiler, then dynamically load the code and execute it. However, this is generally heavy weight and almost never why people use eval() in other languages.

People tend to use eval() in a language because given that language's facilities, for certain classes of problem, it is easier to construct a string from the program input that resembles the language itself, rather than parse and evaluate the input directly.

For example, if you want to allow users to enter not just numbers in an input field, but simple arithmetic expressions, in Perl or Python it is going to be much easier to just call eval() on the input than writing a parser for the expression language you want to allow. Unfortunately, this kind of approach, almost always results in a poor user experience overall (compiler error messages weren't meant for non-programmers) and opens security holes. Solving these problems without using eval() generally involves a fair bit of code.

In Haskell, thanks to things like Parsec, it is actually very easy to write a parser and evaluator for these kinds of input problems, and considerably removes the yearning for eval.

Fez answered 18/3, 2010 at 14:9 Comment(1)
That may all be true, but it does not address the question.Bluegrass
G
12

It doesn't have an inbuilt eval function. However are some packages on hackage that can do the same sort of thing. (docs). Thanks to @luqui there is also hint.

Gary answered 18/3, 2010 at 10:54 Comment(1)
hint is an easier version of the same thing: hackage.haskell.org/package/hintSingleaction
M
11

There is no 'eval' built into the language, though Template Haskell allows compile time evaluation.

For runtime 'eval' -- i.e. runtime metaprogramming -- there are a number of packages on Hackage that essentially import GHC or GHCi, including the old hs-plugins package, and the hint package.

Methodical answered 18/3, 2010 at 17:23 Comment(6)
This has nothing in common with a Python-style eval. You won't have an access to your code metadata. What you're talking about is just constructing Haskell code from within Haskell, it remains detached. The only way to have a proper eval for a statically compiled language (especially if there's a separate compilation) is to preserve all the metadata (types, name tables, etc.) and link the compiler or interpreter into any binary.Musician
Btw., see the first example in the question. You can't ever do this in Haskell. It's not possible to pass the information about a local binding 'x' into your 'eval' function.Musician
See the hs-plugins paper for how to preserve the metadata -- it is done via Haskell's compile-time reflection capability (TH), combined with the Data.Dynamic dynamic type mechanism for splice points. cse.unsw.edu.au/~dons/hs-plugins Reflection + runtime code generation + dynamics for splice points gives you a full multistage metaprogramming model.Methodical
SK, that is simply not true. What Don says, and also take a look at the hint package, specifically at function 'interpret' which brings an evaluated value into the current environment. As for bringing a local 'x' into scope, simply eval a function and then apply it to x.Lucier
While it is true, that there are ways to achieve the same ends, and indeed they are safer and more controlled ways, @Musician does have a valid point: eval() in Python offers program source authored at run-time direct, full access to the environment containing the eval() even without the programmer intending it. In Haskell you'd have to be very explicit about what you wanted to provide the run-time code access to, which is a good thing, but different than eval() nonetheless.Fez
the challenging part is that how to bring the surrounding environment into eval, it's not hard to bring the output of eval into the host.Vane
F
4

hs-plugins has System.Eval.Haskell.

Flagellum answered 19/3, 2010 at 16:8 Comment(0)
B
2

This question is asked 11 years ago, and now use the package hint we can define eval very easily, following is an example as self-contained script (you still need nix to run it)

#!/usr/bin/env nix-shell
#! nix-shell -p "haskellPackages.ghcWithPackages (p: with p; [hint])"
#! nix-shell -i "ghci -ignore-dot-ghci -fdefer-type-errors -XTypeApplications"

{-# LANGUAGE ScopedTypeVariables, TypeApplications, PartialTypeSignatures #-}

import Data.Typeable (Typeable)
import qualified Language.Haskell.Interpreter as Hint

-- DOC: https://www.stackage.org/lts-18.18/package/hint-0.9.0.4

eval :: forall t. Typeable t => String -> IO t
eval s = do
    mr <- Hint.runInterpreter $ do
        Hint.setImports ["Prelude"]
        Hint.interpret s (Hint.as :: t)
    case mr of
        Left err -> error (show err)
        Right r -> pure r

-- * Interpret expressions into values:

e1 = eval @Int "1 + 1 :: Int"
e2 = eval @String "\"hello eval\""

-- * Send values from your compiled program to your interpreted program by interpreting a function:

e3 = do
    f <- eval @(Int -> [Int]) "\\x -> [1..x]"
    pure (f 5)
Briefcase answered 27/11, 2021 at 12:5 Comment(2)
+1 for a concrete implementation, but “now we have new choice” is a bit odd here. hint isn't exactly new.Homecoming
@Homecoming Thanks for point out that, I will edit the answer :)Briefcase
M
1

There is no eval equivalent, Haskell is a statically compiled language, same as C or C++ which does not have eval neither.

Musician answered 18/3, 2010 at 10:52 Comment(7)
It isn't always statically compiled. Take a look at ghciGary
Yep, but you can't embed easily the whole ghci repl into your standalone application. Of course you can, say, use TCC from C as well, but it's not the same as 'eval' in decent dynamic languages, it's always a hack.Musician
Being statically typed isn't a barrier to runtime metaprogramming. It just means your 'eval' function also needs to do type checking. Which the Haskell plugins and hint packages do. Typed runtime metaprogramming.Methodical
I said 'statically compiled', not 'statically typed'. And yes, there is no eval in Haskell. It is not a dynamic language with a built-in interpreter. All the available options are invoking a detached compiler in one form or another, or implementation-specific.Musician
And, one other important point: Haskell does not preserve any metadata in a compiled binary (yes, yes, that's exactly what I meant by a static compilation, thanks for a downvote). Your mock 'eval' won't ever be able to interact with a surrounding environment. You won't pass a variable to it, won't refer to an existing datatype, etc.Musician
Being statically typed with Haskell's sophistication is actually an advantage in runtime metaprogramming. Using typeclasses, the evaluation library could be given very precise type information without the programmer explicitly stating them, like it's done in the Posix.Regex library.Babirusa
"you can't embed easily the whole ghci repl into your standalone application." -- is also wrong, that's what the hint library does -- export GHCi as a Haskell module. Lambdabot uses it, Yi uses it, riot has used it. Embedding the Haskell interpreter in your Haskell app is not entirely rare, and there's decent support to do so.Methodical
S
0

This answer shows a minimal example of using the hint package, 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
Stanfill answered 24/1, 2023 at 10:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.