How can I parse a string to a function in Haskell?
Asked Answered
S

1

12

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 
Subjacent answered 21/5, 2013 at 20:39 Comment(3)
What do you need it for? Maybe there are easier ways to solve the underlying problem.Angstrom
I am writing a program which implements several numerical techniques for solving differential equations in two dimensions. I would like a command line program which will take input from the user in the form of an anonymous Haskell function (in string format of course) in order to define the initial conditions of a problem.Subjacent
Is it enough to interpret expressions? See #5583426Stair
A
15

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'"}]
Angstrom answered 21/5, 2013 at 22:40 Comment(1)
This is excellent, I have been playing with hint since Thomas posted his comment; my program eventually converts the function to a list of values so my implementation skips converting to a function completely and just generates a list.Subjacent

© 2022 - 2024 — McMap. All rights reserved.