{- CS380 Assignment 3
Name:
College email:
Resources / collaborators:
**DUE ON GRADESCOPE BEFORE CLASS ON WEDNESDAY, FEBRUARY 15, 2017.**
-}
{-# LANGUAGE GADTSyntax, StandaloneDeriving #-}
module Halgebra where
import Arith
import Parser
import Data.Ratio
--------------------------------------------------------------------------------
-- The Halgebra computer algebra system
--
{-
In this assignment, you will write functions that can be used to solve
linear equations in one variable, x. The final result will be the `solve`
function, at the end of this file. `solve`'s type is `Equation -> Rational`;
it takes an `Equation` in the variable x and returns the `Rational` that
is a solution to the equation. (`Rational`, exported in the `Prelude` but
originally from `Data.Ratio`, is a numerical type that can store rational
numbers of arbitrary precision. By "numerical type", I mean that `Rational`
in an instance of the `Num` class, meaning that `3 :: Rational` is accepted.)
This assignment is less prescribed than previous ones, with a few function
signatures given to you, and the rest left for you to figure out.
Here is the general approach you will take:
1. Set one side of the input equation to 0. That is, create a `Sum` that
evaluates to 0 whenever the original equation holds. (This step
is really simple!)
2. Simplify your `Sum` into a list of `SimpleTerm`s. Each `SimpleTerm`
is a `Rational` coefficient perhaps multiplied by x. This step is done
by writing the three functions `simpleSum`, `simpleTerm`, and
`simpleFactor`, which will be mutually recursive. (That is, they
will call one another.) You will likely need to write several helper
functions.
3. Separate out the list of `SimpleTerm`s into those that mention x and
those that don't.
4. Add together the coefficients of x and the `SimpleTerm`s that do not
mention x. Call the former `x_coef` and the latter `constant`.
5. The solution to the equation is `(-constant)/x_coef`.
Here is an example:
Start: 1 + 2*x = 3 * (x - 5*(x + 1))
After step 1: (1 + 2*x) - (3 * (x - 5*(x + 1)))
After step 2: [1, 2x, -3x, 15x, 15]
After step 3: ([2x, -3x, 15x], [1, 15])
After step 4: (14, 16)
After step 5: -8/7
This homework assignment requires the Arith.hs and Parser.hs files as
posted on our syllabus page. It also requires the `parsec` package. You
might need to
cabal install parsec
to install this on your system.
Hints:
* The `fromInteger :: Num a => Integer -> a` function can convert from
`Integer` to any other numerical type (like `Rational`).
* By default, `Rational`s print out somewhat oddly in GHCi, using a `%`
to denote division. I've given you `prettyRational` that does a better
job.
* There are three ways solving can fail:
1) You can have a non-linear equation, where you try to multiply x by x.
2) You can have a non-linear equation, where you try to divide by x.
3) All the x's can cancel out.
In any of these scenarios, just call `error :: String -> a` with an
appropriate error message. Do *not* try to detect (and accept) an equation
with x/x in it, such that the division by x is OK. Any division by
an expression with x in it is rejected.
(This approach toward failure is regrettable. Really, we should return
a `Maybe Rational`. But that will complicate things too much at this
stage. We shall return!)
* Simplifying (a + b + c) * (d + e + f) means you multiply everything
in the left sum by everything in the right sum, producing *nine* output
terms. A list comprehension might be useful.
* Simplifying (a + b + c) / (d + e + f) is harder. Because we reject any
denominator mentioning x, we can just add the d, e, and f (which must be
x-less `SimpleTerm`s), and then divide each of a, b, and c by the sum.
* Write unit tests! You will thank yourself later.
-}
-- a prettier rendering of Rationals
prettyRational :: Rational -> String
prettyRational r
| denominator r == 1
= show (numerator r)
| otherwise
= "(" ++ show (numerator r) ++ "/" ++ show (denominator r) ++ ")"
-- a SimpleTerm is a coefficient and, perhaps, an x
data SimpleTerm where
SimpleTerm :: Rational -- the coefficient
-> Bool -- True <=> there is an x; False <=> there isn't
-> SimpleTerm
-- You may wish to uncomment one of these instances for debugging / unit testing:
{-
-- This Show instance is pretty
instance Show SimpleTerm where
show (SimpleTerm coef has_x) = show_coef ++ maybe_show_x
where
show_coef = prettyRational coef
maybe_show_x | has_x = "x"
| otherwise = ""
-}
{-
-- This Show instance is ugly
deriving instance Show SimpleTerm
-}
-- Step 1
gatherOnOneSide :: Equation -> Sum
gatherOnOneSide = error "unimplemented"
-- Simplify a Sum to a list of SimpleTerms (Step 2)
simpleSum :: Sum -> [SimpleTerm]
simpleSum = error "unimplemented"
-- Simplify a Term to a list of SimpleTerms (Step 2)
simpleTerm :: Term -> [SimpleTerm]
simpleTerm = error "unimplemented"
-- Simplify a Factor to a list of SimpleTerms (Step 2)
simpleFact :: Factor -> [SimpleTerm]
simpleFact = error "unimplemented"
-- Step 3
partitionTerms :: [SimpleTerm]
-> ( [SimpleTerm] -- these mention x
, [SimpleTerm] ) -- these don't
partitionTerms = error "unimplemented"
-- Step 4
sumPartitions :: ( [SimpleTerm] -- these mention x
, [SimpleTerm] ) -- these don't
-> ( Rational -- sum of coefficients of x
, Rational ) -- sum of constants
sumPartitions = error "unimplemented"
-- Step 5
extractSolution :: ( Rational -- coefficient of x, "a"
, Rational ) -- constant, "b"
-> Rational -- solution to "a*x + b = 0"
extractSolution = error "unimplemented"
-- Put them all together
solve :: Equation -> Rational
solve = extractSolution .
sumPartitions .
partitionTerms .
simpleSum .
gatherOnOneSide