Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I parse a string to a function in Haskell?

I want a function that looks something like this

readFunc :: String -> (Float -> Float)

which operates something like this

>(readFunc "sin") (pi/2)
>1.0

>(readFunc "(+2)") 3.0
>5.0

>(readFunc "(\x -> if x > 5.0 then 5.0 else x)") 2.0
>2.0

>(readFunc "(\x -> if x > 5.0 then 5.0 else x)") 7.0
>5.0

The incredibly naive approach (note this must be compiled with {-# LANGUAGE FlexibleContexts #-})

readFunc :: (Read (Float -> Float)) => String -> (Float -> Float)
readFunc s = read s

gives

No instance for (Read (Float -> Float)) ...

Which makes sense since no such instance exists. I understand that I can parse the input string character by character by writing a map from String to Float -> Float but I want to be able to parse at least the most common functions from prelude, and even that would be way more work than I want to commit to. Is there an easy way of doing this?

Just one solution using hint

import Language.Haskell.Interpreter hiding (typeOf)
import Data.Typeable (typeOf)

data Domain = Dom Float Float Float Float Domain
            | SDom Float Float Float Float 
            deriving (Show, Read)

--gets all the points that will appear in the domain
points (SDom a b c d) m = [(x, y)|x <- [a, a+m .. b], y <- [c, c+m .. d]]
points (Dom a b c d next) m = points next m ++ [(x, y)|x <- [a, a+m .. b], y <- [c, c+m .. d]]

readFunc = do
    putStrLn "Enter a domain (as Dom x-min x-max y-min y-max subdomain, or, SDom x-min x-max y-min y-max)"
    domain' <- getLine
    let domain = (read domain') :: Domain
    --
    putStrLn "Enter a mesh size"
    meshSize' <- getLine
    let meshSize = (read meshSize') :: Float 
    --
    putStrLn "Enter an initial value function (as f(x,y))"
    func' <- getLine
    values' <- runInterpreter $ setImports["Prelude"] >>
                                eval ("map (\\(x,y) -> " ++ func' ++ ")" ++ show (points domain meshSize))
    let values = (\(Right v) -> (read v)::([Float])) values'

    --the haskell expression being evaluated
    putStrLn $ ("map (\\(x,y) -> " ++ func' ++ ")" ++ show (points domain meshSize)) 

    --prints the actual values
    putStrLn $ show values 

    --the type is indeed [float]
    putStrLn $ show $ typeOf values 
like image 920
user2407038 Avatar asked May 21 '13 20:05

user2407038


1 Answers

You can use the hint package, or plugins. I'll show you the former (partly because my Windows installation is clearly a little broken in that cabal doesn't share my belief that I have C installed, so cabal install plugins fails).

String -> Function is easy:

import Language.Haskell.Interpreter

getF :: String -> IO (Either InterpreterError (Float -> Float))
getF xs = runInterpreter $ do
   setImports ["Prelude"]
   interpret xs (as :: Float -> Float)

You may want to add additional modules to the imports list. This tests out as

ghci> getF "sin" >>= \(Right f) -> print $ f (3.1415927/2)
1.0
ghci> getF "(\\x -> if x > 5.0 then 5.0 else x)" >>= \(Right f) -> print $ f 7
5.0

(Notice the escaping of the escape character \.)

Error messages

As you may have noticed, the result is wrapped in the Either data type. Right f is correct output, whereas Left err gives an InterpreterError message, which is quite helpful:

ghci> getF "sinhh" >>= \(Left err) -> print err
WontCompile [GhcError {errMsg = "Not in scope: `sinhh'\nPerhaps you meant `sinh' (imported from Prelude)"}]

Example toy program

Of course, you can use either with your code to deal with this. Let's make a fake example respond. Your real one will contain all the maths of your program.

respond :: (Float -> Float) -> IO ()
respond f = do
   -- insert cunning numerical method instead of
   let result = f 5
   print result

A simple, one-try, unhelpful version of your program could then be

main = 
   putStrLn "Enter your function please:"
   >> getLine 
   >>= getF 
   >>= either print respond 

Example sessions

ghci> main
Enter your function please:
\x -> x^2 + 4
29.0
ghci> main
Enter your function please:
ln
WontCompile [GhcError {errMsg = "Not in scope: `ln'"}]

It does type checking for you:

ghci> main
Enter your function please:
(:"yo")
WontCompile [GhcError {errMsg = "Couldn't match expected type `GHC.Types.Float'\n            with actual type `GHC.Types.Char'"}]
like image 139
AndrewC Avatar answered Oct 10 '22 16:10

AndrewC