-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathExpr.hs
175 lines (141 loc) · 5.64 KB
/
Expr.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
-- Part 1 of the lab
-- Author: Johan Gustafsson
module Expr where
import Parsing
import Data.Char
import Data.Maybe
import Data.List
import Control.Applicative
-- Data type for representing simple mathematical expressions
data Expr
= Num Double
| Var
| UnF UFunc Expr
| BiF BFunc Expr Expr
deriving (Show)
{--
By defining a separate class and data type for the unary and binary
functions. We can have most of the function specific implementation
in one place and also keep it easily extendable.
--}
-- Data type to represent unary functions
data UFunc = Sin | Cos | Tan
deriving (Show, Enum)
-- This class lets us map desired implemented functions to each
-- of the defined unary functions.
class UOp a where
udop :: a -> (Double -> Double)
uStrRep :: a -> String
-- Definitions of each unary functions actual implementation and
-- their string representation.
instance UOp UFunc where
udop Sin = sin
udop Cos = cos
udop Tan = tan
uStrRep Sin = "sin"
uStrRep Cos = "cos"
uStrRep Tan = "tan"
-- Data type to represent binary functions
-- The order of definition decides the priority of evaluation
data BFunc = Add | Mul
deriving (Eq, Show, Enum)
-- This class lets us map desired implemented functions to each
-- of the defined binary functions.
class BOp a where
bdop :: a -> (Double -> Double -> Double)
bStrRep :: a -> String
-- Definitions of each binary functions actual implementation and
-- their string representation.
instance BOp BFunc where
bdop Add = (+)
bdop Mul = (*)
bStrRep Add = "+"
bStrRep Mul = "*"
-- Show a given expression
showExpr :: Expr -> String
showExpr (Num a) = show a
showExpr (Var) = "x"
showExpr (UnF f e) = uStrRep f ++ "(" ++ showExpr e ++ ")"
-- This makes use of the order of the defined binary functions to handle
-- priority for evaluation.
showExpr (BiF f1 e1 e2) = showFactor e1 ++ " " ++ bStrRep f1 ++ " " ++ showFactor e2
where showFactor (BiF f2 e1 e2) | i2 < i1 = "(" ++ showExpr (BiF f2 e1 e2) ++ ")"
where i1 = fromJust $ elemIndex f1 [(toEnum 0::BFunc)..]
i2 = fromJust $ elemIndex f2 [(toEnum 0::BFunc)..]
showFactor e = showExpr e
-- This function evaluates a given Expr using the given double as the Var value.
-- UnF functions/operators are extracted using udop and bdop respectively.
eval :: Expr -> Double -> Double
eval (Num n) _ = n
eval (Var) v = v
eval (UnF f e) v = (udop f) (eval e v)
eval (BiF f e1 e2) v = (bdop f) (eval e1 v) (eval e2 v)
numVarParser :: Parser Expr
numVarParser = fmap Num doubleParser +++ varParser
readExpr :: String -> Maybe Expr
readExpr s = let s' = filter (not.isSpace) s
in case parse (expr [(toEnum 0::BFunc)..]) s' of
Just (e,"") -> Just e
_ -> Nothing
-- Function to try an parse an expression
-- This takes the list of binary functions as input so it can
-- try and parse the functions in an correct order.
-- Maybe this could be remade even more, I'm not sure tbh.
-- Especially the funcTerm part.
expr :: [BFunc] -> Parser Expr
expr (f:[]) = foldr1 (BiF f) `fmap` chain funcTerm (stringParser (bStrRep f))
where funcTerm = foldl1 (+++) (map parseUF [(toEnum 0::UFunc)..])
+++ factor
expr (f:fs) = foldr1 (BiF f) `fmap` chain (expr fs) (stringParser (bStrRep f))
-- This is in my opinion ugly but I can't really say how to "prettify" it.
parseUF :: UFunc -> Parser Expr
parseUF f = do
(stringParser . uStrRep) f
e <- factor
return (UnF f e)
factor :: Parser Expr
factor = char '(' >-> (expr [(toEnum 0::BFunc)..]) <-< char ')' +++ numVarParser
-- Parse a string by using the char parser.
stringParser :: String -> Parser ()
stringParser s = sequence_ [ char c | c <- s]
varParser :: Parser Expr
varParser = do char 'x'
return Var
doubleParser :: Parser Double
doubleParser = do num <- readsP
return num
-- Simplify a given expression
-- I made this much smaller the second time around for code readability.
-- It's not as good but for the purpose of this lab it is ok.
simplify :: Expr -> Expr
simplify Var = Var
simplify (Num x) = Num x
simplify (UnF f e) = (UnF f (simplify e))
simplify (BiF Add e1 e2) = case (BiF Add p q) of
(BiF Add (Num x) (Num y)) -> Num $ x+y
(BiF Add (Num 0) q) -> q
(BiF Add p (Num 0)) -> p
(BiF Add p q) -> (BiF Add p q)
where p = simplify e1
q = simplify e2
simplify (BiF Mul e1 e2) = case (BiF Mul p q) of
(BiF Mul (Num x) (Num y)) -> Num $ x*y
(BiF Mul (Num 0) _) -> Num 0
(BiF Mul _ (Num 0)) -> Num 0
(BiF Mul (Num 1) q) -> q
(BiF Mul p (Num 1)) -> p
(BiF Mul p q) -> (BiF Mul p q)
where p = simplify e1
q = simplify e2
-- Differentiate the given expression with respect to x(Var)
-- Using simplify to keep things nice and tidy after differentation.
differentiate :: Expr -> Expr
differentiate Var = Num 1
differentiate (Num _) = Num 0
differentiate (UnF Sin e) = simplify $ BiF Mul (differentiate e) (UnF Cos e)
differentiate (UnF Cos e) = simplify $ BiF Mul (Num (-1)) (BiF Mul (differentiate e) (UnF Sin e))
differentiate (BiF Add e1 e2) = simplify $ BiF Add (differentiate e1) (differentiate e2)
differentiate (BiF Mul e1 e2) = simplify $ BiF Add (BiF Mul (differentiate e1) e2) (BiF Mul e1 (differentiate e2))
-- Just a shortcut function so I can write expressions as Strings and try them out
readAndDiff :: String -> Expr
readAndDiff s = differentiate $ simplify $ fromJust $ readExpr s