patternMinor
Data structure for expression evaluation in Haskell
Viewed 0 times
expressionevaluationforstructurehaskelldata
Problem
I've made a data structure for mathematical expressions. I want to parse mathematical expressions like:
into an evaluated document like:
where \$=\$ is assignment and \$:\$ is evaluation.
The data structure must handle errors:
Smells and comments
Haskell is fantastic for this, but I'm still struggling with algebraic data structures; I'm used to object-oriented design. Because of this, I'd like some feedback!
Smells:
Data structure
The data structure itself is most important. I've included code for serialization and evaluation for reference. These are less important, but I'm thankful for comments on those as well!
``
-- Result data
type EvalError = String
type EvalRes = Either EvalError Double
data StatementResult = StatementResult Statement EvalRes | JustInformative
- \$x = 3\$
- \$y = 4\$
- \$z = x + y :\$
into an evaluated document like:
- \$x = 3\$
- \$y = 4\$
- \$z = x + y : 7\$
where \$=\$ is assignment and \$:\$ is evaluation.
The data structure must handle errors:
- Invalid input like multiple equals signs
- Invalid expressions like referencing an undefined variable
Smells and comments
Haskell is fantastic for this, but I'm still struggling with algebraic data structures; I'm used to object-oriented design. Because of this, I'd like some feedback!
Smells:
- Structure for output document relates arbitrarily on structure for input documents
- Picking
typevsdataseems random
- Should I be using records?
- I struggle with finding good names
evalExpcontains much repetition
- Comments on my style in general?
- I suspect that I'm evaluating nested expressions multiple times. Thoughts on how to fix this?
Data structure
The data structure itself is most important. I've included code for serialization and evaluation for reference. These are less important, but I'm thankful for comments on those as well!
``
module Document where
import Text.Printf(printf)
import Data.List(intercalate)
import qualified Data.Map.Strict as M
-- Source data
data Exp = Num Double
| Add Exp Exp
| Sub Exp Exp
| Mult Exp Exp
| Div Exp Exp
| Neg Exp
| Ref Name
| Call Name [Exp]
deriving (Show)
type Name = String
type Evaluation = Bool
data Statement = Statement (Maybe Name) Exp Evaluation | Informative String
deriving (Show)
data Document = Document [Statement]
deriving (Show)
instance Monoid Document where
mempty = Document []
(Document a) mappend (Document b) = Document (a mappend` b)-- Result data
type EvalError = String
type EvalRes = Either EvalError Double
data StatementResult = StatementResult Statement EvalRes | JustInformative
Solution
Data.Functor.Foldable can take some boilerplate out of your code. Also I tried to make the recursion in evalExp's references as tight-looped as possible.
import Data.Functor.Foldable
-- Source data
data ExpF t
= Num Double
| Add t t
| Sub t t
| Mult t t
| Div t t
| Neg t
| Ref Name
| Call Name [t]
deriving (Show, Functor, Foldable, Traversable)
type Exp = Fix ExpF
instance Serialize Exp where
serialize = cata $ \case
Num d -> show d
Add x y -> printf "(%s + %s)" x y
Sub x y -> printf "(%s - %s)" x y
Neg x -> "-" ++ x
Mult x y -> printf "%s * %s" x y
Div x y -> printf "%s / %s" x y
Ref name -> name
Call name exps -> printf "%s(%s)" name (intercalate ", " exps)
evalExp :: NameExpressions -> Exp -> EvalRes
evalExp d = evalExp' d' where
d' = evalExp' d' d
evalExp' d' = cata $ sequenceA >=> \case
Ref name -> fromMaybe
(Left $ "No match for name: " ++ name)
(M.lookup name d')
x -> first (const "Not implemented") $ do Right $ case x of
Num n -> n
Add x y -> x + y
Sub x y -> x - y
Mult x y -> x * y
Div x y -> x / y
Call "sin" (arg1:_) -> sin arg1
Call "cos" (arg1:_) -> cos arg1
Call "tan" (arg1:_) -> tan arg1
Call "asin" (arg1:_) -> asin arg1
Call "acos" (arg1:_) -> acos arg1
Call "atan" (arg1:_) -> atan arg1
Call "sinh" (arg1:_) -> sinh arg1
Call "cosh" (arg1:_) -> cosh arg1
Call "tanh" (arg1:_) -> tanh arg1
Call "asinh" (arg1:_) -> asinh arg1
Call "acosh" (arg1:_) -> acosh arg1
Call "atanh" (arg1:_) -> atanh arg1
Call "log" (arg1:_) -> log arg1
Call "exp" (arg1:_) -> exp arg1
Call "abs" (arg1:_) -> abs arg1
Call "sqrt" (arg1:_) -> sqrt arg1
Call "pow" (arg1:arg2:_)) -> arg1 ** arg2
Neg x -> negate xCode Snippets
import Data.Functor.Foldable
-- Source data
data ExpF t
= Num Double
| Add t t
| Sub t t
| Mult t t
| Div t t
| Neg t
| Ref Name
| Call Name [t]
deriving (Show, Functor, Foldable, Traversable)
type Exp = Fix ExpF
instance Serialize Exp where
serialize = cata $ \case
Num d -> show d
Add x y -> printf "(%s + %s)" x y
Sub x y -> printf "(%s - %s)" x y
Neg x -> "-" ++ x
Mult x y -> printf "%s * %s" x y
Div x y -> printf "%s / %s" x y
Ref name -> name
Call name exps -> printf "%s(%s)" name (intercalate ", " exps)
evalExp :: NameExpressions -> Exp -> EvalRes
evalExp d = evalExp' d' where
d' = evalExp' d' <$> d
evalExp' d' = cata $ sequenceA >=> \case
Ref name -> fromMaybe
(Left $ "No match for name: " ++ name)
(M.lookup name d')
x -> first (const "Not implemented") $ do Right $ case x of
Num n -> n
Add x y -> x + y
Sub x y -> x - y
Mult x y -> x * y
Div x y -> x / y
Call "sin" (arg1:_) -> sin arg1
Call "cos" (arg1:_) -> cos arg1
Call "tan" (arg1:_) -> tan arg1
Call "asin" (arg1:_) -> asin arg1
Call "acos" (arg1:_) -> acos arg1
Call "atan" (arg1:_) -> atan arg1
Call "sinh" (arg1:_) -> sinh arg1
Call "cosh" (arg1:_) -> cosh arg1
Call "tanh" (arg1:_) -> tanh arg1
Call "asinh" (arg1:_) -> asinh arg1
Call "acosh" (arg1:_) -> acosh arg1
Call "atanh" (arg1:_) -> atanh arg1
Call "log" (arg1:_) -> log arg1
Call "exp" (arg1:_) -> exp arg1
Call "abs" (arg1:_) -> abs arg1
Call "sqrt" (arg1:_) -> sqrt arg1
Call "pow" (arg1:arg2:_)) -> arg1 ** arg2
Neg x -> negate xContext
StackExchange Code Review Q#124390, answer score: 2
Revisions (0)
No revisions yet.