├── .gitignore ├── README.md ├── src ├── Vars.hs ├── Expr │ ├── Fractional.hs │ ├── Add.hs │ ├── Const.hs │ ├── Mul.hs │ ├── Num.hs │ └── Show.hs ├── CAS.hs └── Expr.hs ├── test ├── UnitTests │ ├── Base.hs │ ├── Addition.hs │ ├── Multiplication.hs │ ├── Show.hs │ └── Ordering.hs ├── QuickTests │ ├── Multiplication.hs │ ├── Ordering.hs │ ├── Addition.hs │ └── Arbitrary.hs ├── QuickTests.hs ├── Test.hs └── UnitTests.hs ├── notes.txt ├── cas.cabal ├── stack.yaml ├── Makefile ├── LICENSE └── comments.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore files generated by PyCharm (the IDE) 2 | .idea/ 3 | 4 | # Ignore compilation byproducts 5 | *.hi 6 | *.o 7 | 8 | # Ignore profiling output 9 | *.prof 10 | *.hp 11 | *.aux 12 | *.ps 13 | 14 | # Ignore Stack files 15 | .stack-work/ 16 | 17 | # Ignore copies (orig) of files 18 | src/orig-* 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Haskell CAS (Computer Algebra System) 2 | 3 | This project creates a CAS in Haskell to be used by other Haskell modules or equally well in the Haskell Interpreter. 4 | 5 | It comes with a handy Makefile which automates many of the common tasks associated with the project (compilation, testing, etc.). 6 | 7 | ## Testing 8 | 9 | This project uses *HUnit* for unit-testing. To run the unit tests simply execute ``make test``. 10 | -------------------------------------------------------------------------------- /src/Vars.hs: -------------------------------------------------------------------------------- 1 | -- | This module declares useful variables (expressions) which are used in ghci to aid in debugging and development. 2 | module Vars ( 3 | x, y, z, 4 | ) where 5 | 6 | import CAS 7 | 8 | -- | We define and export some useful symbols (the ubiquitous x, y, z). 9 | x, y, z :: Expr 10 | x = symbol "x" 11 | y = symbol "y" 12 | z = symbol "z" 13 | 14 | -- To create the variables we simply use the 'symbol' utility function that is defined in Expr and exported by CAS 15 | -- This function provides the only mechanism for creating Atoms that corresond to single symbols/variables 16 | -- i.e. x = Atom 1 "x" 1 = 1 * x^1 17 | -------------------------------------------------------------------------------- /test/UnitTests/Base.hs: -------------------------------------------------------------------------------- 1 | -- Defines objects that are used by ALL the sub-modules of UnitTests 2 | 3 | module UnitTests.Base 4 | where 5 | 6 | import Test.HUnit(assertEqual, assertBool, Assertion) 7 | 8 | import CAS 9 | 10 | -- Define variables (expressions) that are used in all of the tests below 11 | -- The variables are defined using the 'symbol' utility function defined in Expr and exported by CAS 12 | x, y, z :: Expr 13 | x = symbol "x" 14 | y = symbol "y" 15 | z = symbol "z" 16 | 17 | 18 | -- Define shorthand utility functions for assertions 19 | aE :: (Eq a, Show a) => String -> a -> a -> Assertion 20 | aE = assertEqual 21 | 22 | aB :: String -> Bool -> Assertion 23 | aB = assertBool 24 | -------------------------------------------------------------------------------- /test/QuickTests/Multiplication.hs: -------------------------------------------------------------------------------- 1 | -- Define all quicktests that are used to test the multiplicative properties of expresssions 2 | 3 | module QuickTests.Multiplication (tests) 4 | where 5 | 6 | import Test.QuickCheck (quickCheck, counterexample) 7 | 8 | import CAS 9 | 10 | import QuickTests.Arbitrary (arbitrary) 11 | 12 | 13 | tests = do 14 | quickCheck $ counterexample "Multiplying an expression by one" prop_Mul_1 15 | quickCheck $ counterexample "Multiplicative Commutation between two expressions" prop_Mul_Commute 16 | 17 | 18 | -- Define multiplicative properties 19 | 20 | prop_Mul_1 :: Expr Int -> Bool 21 | prop_Mul_1 e = e * 1 == e 22 | where types = e::(Expr Int) 23 | 24 | prop_Mul_Commute :: Expr Int -> Expr Int -> Bool 25 | prop_Mul_Commute e1 e2 = e1 * e2 == e2 * e1 26 | where types = (e1 :: Expr Int, e2 :: Expr Int) 27 | -------------------------------------------------------------------------------- /src/Expr/Fractional.hs: -------------------------------------------------------------------------------- 1 | -- | Makes Expr an instance of the Fractional class which gives us access to the (/) division operator. 2 | module Expr.Fractional 3 | ( 4 | (/) 5 | ) 6 | where 7 | 8 | import Expr (Expr(Atom, Inv)) 9 | import Expr.Num -- Since Fractional is a sub-class of Num, Expr needs to be an instance of Num before it can be an instance of Fractional 10 | 11 | instance Fractional Expr where 12 | (/) = divide 13 | fromRational = error "Undefined: Not needed for CAS" 14 | 15 | -- Division is simply defined in terms of the 'Inv' constructor which defines expressions raised to the power -1 16 | divide :: Expr -> Expr -> Expr 17 | divide a (Atom 1 _ 0) = a 18 | divide (Atom 1 _ 0) b = inverse b 19 | divide a (Atom 1 (Inv b) 1) = a * b 20 | divide (Atom 1 (Inv a) 1) b = inverse (a * b) 21 | divide a b = a * (inverse b) 22 | 23 | -- Encapsulate an expression inside an Inv inside an Atom 24 | inverse :: Expr -> Expr 25 | inverse e = Atom 1 (Inv e) 1 26 | -------------------------------------------------------------------------------- /src/Expr/Add.hs: -------------------------------------------------------------------------------- 1 | -- | Implement the addition arithmetic operation for expressions (with all of its inherent complexity). 2 | module Expr.Add 3 | ( 4 | add 5 | , negate 6 | ) 7 | where 8 | 9 | import Prelude hiding (negate) 10 | 11 | import Expr (Expr(Atom, Add)) 12 | 13 | -- The addition operation is implemented using this top-level function which takes two expressions and returns their sum 14 | -- Every Add expression is encapsulated inside an Atom for ease of similarity checks down the line 15 | add :: Expr -> Expr -> Expr 16 | add x (Atom 0 _ _) = x -- Implements additive identity. Any expression plus zero is unchanged. Note the pattern match using the coeff (first element) of Atom alone which if it is zero corresponds to the constant zero regardless of the core or power 17 | add (Atom 0 _ _) x = x 18 | add x y = Atom 1 (Add x y) 1 -- Naive implementation of addition. Every "returned" (fully processed) expression in this CAS "MUST" be encapsulated as an Atom so that the initial patterns designed around this assumption can come in to play 19 | 20 | 21 | negate :: Expr -> Expr 22 | negate (Atom c e p) = Atom (-c) e p 23 | negate _ = error "Expr.Add.negate is only defined for Expr.Atom" 24 | -------------------------------------------------------------------------------- /src/Expr/Const.hs: -------------------------------------------------------------------------------- 1 | -- | Module that defines the behaviour of constants in the CAS 2 | module Expr.Const 3 | ( 4 | fromInteger 5 | ) 6 | where 7 | 8 | import Prelude hiding (fromInteger) -- Hide fromInteger since we are defining a function with this very name in this module 9 | 10 | import Expr (Expr(Atom, Symbol)) 11 | 12 | -- We define a utility method for creating constant expressions. It takes an integer and creates a constant expression by creating an Atom with the constant as the coefficient, the core is the special symbol with an empty strings and its power is zero since any symbol to the power zero is equal to 1. The way to detect (pattern match) explicitly for a constant is to look for an Atom with power 0 since any core rasied to the power 0 is by definition equal to 1 and so is a constant 13 | fromInteger :: Integer -> Expr 14 | fromInteger c = Atom c (Symbol "") 0 15 | 16 | -- We are using Symbol "" instead of a dedicated constructor (e.g. Const) since we want to treat constants as just a special case of Atom which will allow us to deal with them like any other Atom without having to write a special pattern in every function to deal with them. This was the entire reason behind creating an Atom constructor containing both coefficient and power and forcing everything to be an Atom 17 | -------------------------------------------------------------------------------- /test/QuickTests/Ordering.hs: -------------------------------------------------------------------------------- 1 | -- Define quicktests that are used for testing the Ordering properties of Expressions 2 | 3 | module QuickTests.Ordering (tests) 4 | where 5 | 6 | import Test.QuickCheck (quickCheck, counterexample) 7 | 8 | import CAS 9 | 10 | import QuickTests.Arbitrary (arbitrary) 11 | 12 | 13 | tests = do 14 | quickCheck $ counterexample "Reverse Comparison Test" prop_Rev_Order 15 | quickCheck $ counterexample "Comparing equal expressions" prop_Compare_Equality 16 | quickCheck $ counterexample "Comparing unequal expressions" prop_Compare_Inequality 17 | 18 | 19 | -- Define properties (related to Ordering of expressions) that need to be tested 20 | 21 | prop_Rev_Order :: Expr Int -> Expr Int -> Bool 22 | prop_Rev_Order e1 e2 = (compare e1 e2) == (flip $ compare e2 e1) 23 | where types = (e1 :: Expr Int, e2 :: Expr Int) 24 | flip LT = GT 25 | flip GT = LT 26 | flip EQ = EQ 27 | 28 | prop_Compare_Equality :: Expr Int -> Bool 29 | prop_Compare_Equality e = (compare e e) == EQ 30 | where types = e :: (Expr Int) 31 | 32 | prop_Compare_Inequality :: Expr Int -> Expr Int -> Bool 33 | prop_Compare_Inequality e1 e2 = if e1 == e2 then (compare e1 e2) == EQ 34 | else (compare e1 e2) /= EQ 35 | -------------------------------------------------------------------------------- /src/Expr/Mul.hs: -------------------------------------------------------------------------------- 1 | -- | Implement the multiplication arithmetic operation for expressions (with all of its inherent complexity). 2 | module Expr.Mul 3 | ( 4 | mul 5 | ) 6 | where 7 | 8 | 9 | import Expr (Expr (Atom, Mul)) 10 | 11 | -- Function for multiplying two expressions 12 | mul :: Expr -> Expr -> Expr 13 | 14 | -- Whenever a constant (detected as an Atom with power 0) is multiplied with any other atom we simply multiply the coeff to the latter 15 | -- This implements the special case of multiplying by the identity: 1 16 | -- This is a good indication that this is a powerful and elegant rule since it handles the special case for free 17 | mul (Atom c _ 0) (Atom cy y yp) = Atom (c * cy) y yp 18 | mul x y@(Atom c _ 0) = mul y x -- Where the second expression is a constant we simply use commutation 19 | 20 | -- Whenver the two items being multiplied have the same core expression we simply raise the power and collect the coeff. 21 | mul x@(Atom xc xe xp) y@(Atom yc ye yp) 22 | | xe == ye = Atom (xc * yc) xe (xp + yp) 23 | | otherwise = mul2 x y 24 | 25 | -- When a product is constructed the colleced coeff should be in the outer Atom and all elements inside the product should have coeff 1 26 | 27 | 28 | mul2 :: Expr -> Expr -> Expr 29 | mul2 x y = Atom 1 (Mul x y) 1 -- The resulting Mul is encapsulated in an Atom for more efficient pattern-matching 30 | -------------------------------------------------------------------------------- /test/QuickTests.hs: -------------------------------------------------------------------------------- 1 | -- Copyright 2015 Abid Hasan Mujtaba 2 | -- 3 | -- Licensed under the Apache License, Version 2.0 (the "License"); 4 | -- you may not use this file except in compliance with the License. 5 | -- You may obtain a copy of the License at 6 | -- 7 | -- http://www.apache.org/licenses/LICENSE-2.0 8 | -- 9 | -- Unless required by applicable law or agreed to in writing, software 10 | -- distributed under the License is distributed on an "AS IS" BASIS, 11 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | -- See the License for the specific language governing permissions and 13 | -- limitations under the License. 14 | -- 15 | -- 16 | -- This module contains ALL Quick Tests (ones using randomly generated test data) for this project. 17 | -- It exports a single 'main' function that runs all of the tests defined in its sub-modules 18 | -- 19 | -- Source for QuickCheck usage: http://www.cse.chalmers.se/~rjmh/QuickCheck/manual.html 20 | 21 | module QuickTests (main) 22 | where 23 | 24 | import qualified QuickTests.Addition (tests) 25 | import qualified QuickTests.Multiplication (tests) 26 | import qualified QuickTests.Ordering (tests) 27 | 28 | 29 | main:: IO () 30 | main = do -- This IO Action runs only the property checks 31 | QuickTests.Addition.tests 32 | QuickTests.Multiplication.tests 33 | QuickTests.Ordering.tests 34 | -------------------------------------------------------------------------------- /test/Test.hs: -------------------------------------------------------------------------------- 1 | -- Copyright 2015 Abid Hasan Mujtaba 2 | -- 3 | -- Licensed under the Apache License, Version 2.0 (the "License"); 4 | -- you may not use this file except in compliance with the License. 5 | -- You may obtain a copy of the License at 6 | -- 7 | -- http://www.apache.org/licenses/LICENSE-2.0 8 | -- 9 | -- Unless required by applicable law or agreed to in writing, software 10 | -- distributed under the License is distributed on an "AS IS" BASIS, 11 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | -- See the License for the specific language governing permissions and 13 | -- limitations under the License. 14 | -- 15 | -- 16 | -- This module provides a test suite for the CAS module. 17 | -- 18 | -- The Stack build-system provides an automated way for running these tests: 19 | -- 20 | -- stack test 21 | 22 | 23 | import qualified UnitTests -- All Unit tests are defined in the UnitTests module (and its submodules). We use a qualified import to keep the 'main' function inside it in a separate namespace 24 | -- import qualified QuickTests 25 | 26 | 27 | main = do -- In the main function we simply run the tests. So running the executable (Test) will cause the tests to be executed 28 | putStrLn "\n\n============ Unit Tests ===============\n" 29 | UnitTests.main 30 | 31 | -- putStrLn "\n=========== Random Tests ===============\n" 32 | -- QuickTests.main 33 | -------------------------------------------------------------------------------- /notes.txt: -------------------------------------------------------------------------------- 1 | Profiling: 2 | 3 | - The program was throwing Stack Overflows during the quick-checks. 4 | - To diagnose these I wished to compile the tests with profiling activated. 5 | - This required the use of '-prof' and '-fprof-auto' flags in the 'ghc make' command. 6 | - This command failed because profiling wasn't enabled in ghc as well as in any of the other packages being used. 7 | 8 | To Enable Profiling: 9 | 10 | - sudo apt-get install ghc-prof # A variant of ghc with profiling enabled. This provides the 'base' module (which includes Prelude) with profiling 11 | - To upgrade any Haskell package to use profiling issue: sudo cabal install -p --reinstall 12 | - If any of the dependencies of the package doesn't have profiling enabled then this command will fail and one will have to reinstall the dependency (with profiling enabled) first. 13 | 14 | - To insist that cabal install all libraries with profiling enabled edit ~/.cabal/config and add the line 'library-profiling: True' (uncomment the default False valued line and change it) 15 | 16 | 17 | 18 | To-Do: 19 | 20 | - Automate the rule that Sum [] = Const 0 (sum over zero elements is equal to zero - the additive identity) 21 | - Automate the rule that Sum [e] = e (sum over single element is the element itself) 22 | 23 | - Investigate making Sum [list] behave like a list that is treat Sum as a concatenation of Expr elements e1:e2:e3:[]. This will greatly simplify pattern matching 24 | 25 | - Possibly use "View Patterns" to implement pattern matching complicated by the large number of constructors for Expr 26 | - Possibly consolidate Sum and Prod in to a single type with two constructors of its own. This will consolidate their similarities. 27 | 28 | - Some good ideas on CAS as a DSL: http://tom.lokhorst.eu/2009/09/deeply-embedded-dsls 29 | -------------------------------------------------------------------------------- /src/Expr/Num.hs: -------------------------------------------------------------------------------- 1 | -- | The Expr.Num module makes the Expr type an instance of the Num class allowing expressions to engage in arithmetic operations which form the core of the functionality of a CAS 2 | 3 | -- Source: https://www.haskell.org/tutorial/numbers.html (comprehensive tutorial on Haskell numeric types) 4 | 5 | module Expr.Num 6 | ( 7 | (+) -- Explicitly export the + function. Functions not explicitly exported will raise an exception if they are called. 8 | , (*) 9 | , (-) 10 | , fromInteger 11 | , negate 12 | ) 13 | where 14 | 15 | import Expr (Expr()) 16 | import qualified Expr.Const (fromInteger) 17 | import qualified Expr.Add (add, negate) 18 | import qualified Expr.Mul (mul) 19 | 20 | instance Num Expr where 21 | x + y = Expr.Add.add x y -- Addition is handled by a separate module 22 | x * y = Expr.Mul.mul x y 23 | x - y = x + negate y -- Subtraction is simply addition with the second expression negated 24 | abs x = error "Not implemented yet" 25 | signum x = error "Not implemented yet" 26 | fromInteger = Expr.Const.fromInteger -- The Num class is able to detect when we are about to arithmetically associate an integer with an expression. Once that happens 'fromInteger' is used to convert the Integer in to an expression corresponding to it so that it can be transparently associated with the expression 27 | negate = Expr.Add.negate 28 | 29 | -- Note: Since we are overloading existing arithmetic operators with established order of precedence we get this order for free for our expressions 30 | 31 | -- Note: By implementing (*) we get the implementation for (^) for free since it is defined in the Real type-class using multiplication for all instances of the Num class i.e. the (^) method has a uniform implementaion that only uses those aspects of an object that are defined for Num 32 | -------------------------------------------------------------------------------- /src/CAS.hs: -------------------------------------------------------------------------------- 1 | -- Copyright 2015 Abid Hasan Mujtaba 2 | -- 3 | -- Licensed under the Apache License, Version 2.0 (the "License"); 4 | -- you may not use this file except in compliance with the License. 5 | -- You may obtain a copy of the License at 6 | -- 7 | -- http://www.apache.org/licenses/LICENSE-2.0 8 | -- 9 | -- Unless required by applicable law or agreed to in writing, software 10 | -- distributed under the License is distributed on an "AS IS" BASIS, 11 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | -- See the License for the specific language governing permissions and 13 | -- limitations under the License. 14 | -- 15 | -- 16 | -- Author: Abid H. Mujtaba 17 | -- Date: 2015-01-11 18 | 19 | -- This module implements a Computer Algebra System for Haskell. 20 | -- 21 | -- This work is inspired in part by: https://github.com/hepek/Ramblings/blob/master/symb.lhs 22 | 23 | 24 | -- The following comment is an instruction to the compiler which gives us access to the ! pattern which is used to require strictness in specified variables 25 | -- {-# LANGUAGE BangPatterns #-} 26 | 27 | -- | The CAS module is the top-most entity in this library and gives access to 28 | -- the Expr class and all associated functions that correspond to standard 29 | -- arithmetic operations (+-*/^), .etc. 30 | module CAS -- A.1 31 | ( 32 | -- * Classes 33 | -- | The Expr class is the primary entity forming the foundation of the CAS. We export NONE of the constructors of Expr. The expressions must be built up using either 'symbol' for variable creation OR the defined arithmetic operators 34 | Expr (), 35 | symbol -- Defined in Expr module and exported here. This is the function that allows the user to declare symbolic variables (x, y, z .etc) 36 | -- * Methods 37 | -- , (^) -- A.4 38 | ) 39 | where 40 | 41 | import Expr 42 | import Expr.Show 43 | import Expr.Num 44 | import Expr.Fractional 45 | -------------------------------------------------------------------------------- /test/QuickTests/Addition.hs: -------------------------------------------------------------------------------- 1 | -- Define all quicktests that are used to test the additive properties of expresssions 2 | 3 | module QuickTests.Addition (tests) 4 | where 5 | 6 | import Test.QuickCheck (quickCheck, counterexample) 7 | 8 | import CAS 9 | 10 | import QuickTests.Arbitrary (arbitrary) 11 | 12 | 13 | -- If one wants a look at the generated expressions in any quickCheck simply replace the call with 'verboseCheck'. This is a good debugging strategy. 14 | -- We use 'counterexample' to attach a label to each test which will be printed if the test fails. This will let us know at a glance what went wrong. 15 | 16 | tests = do 17 | quickCheck $ counterexample "Adding zero to an expression" prop_Add_0 18 | quickCheck $ counterexample "Adding an expression with itself" prop_Add_equal 19 | quickCheck $ counterexample "Add a constant times an expression with another contant times its expressions" prop_Add_equal2 20 | quickCheck $ counterexample "Subtract an expression from itself" prop_Sub_equal 21 | quickCheck $ counterexample "Additive Commutation between two expressions" prop_Add_Commute 22 | 23 | 24 | -- Any function that starts with "prop_" is considered a property by QuickCheck 25 | -- Define properties that test the additive features of expressions 26 | 27 | prop_Add_0 :: Expr Int -> Bool -- A property of expressions is that adding zero to an expression should result in the same expression 28 | prop_Add_0 e = e + 0 == e 29 | where types = e::(Expr Int) 30 | 31 | prop_Add_equal :: Expr Int -> Bool 32 | prop_Add_equal e = e + e == (2 * e) 33 | where types = e :: (Expr Int) 34 | 35 | prop_Add_equal2 :: Expr Int -> Bool 36 | prop_Add_equal2 e = (2 * e) + (5 * e) == (7 * e) 37 | where types = e :: (Expr Int) 38 | 39 | prop_Sub_equal :: Expr Int -> Bool 40 | prop_Sub_equal e = (e - e) == 0 41 | where types = e :: (Expr Int) 42 | 43 | prop_Add_Commute :: Expr Int -> Expr Int -> Bool 44 | prop_Add_Commute e1 e2 = e1 + e2 == e2 + e1 45 | where types = (e1 :: Expr Int, e2 :: Expr Int) 46 | -------------------------------------------------------------------------------- /test/UnitTests.hs: -------------------------------------------------------------------------------- 1 | -- Copyright 2015 Abid Hasan Mujtaba 2 | -- 3 | -- Licensed under the Apache License, Version 2.0 (the "License"); 4 | -- you may not use this file except in compliance with the License. 5 | -- You may obtain a copy of the License at 6 | -- 7 | -- http://www.apache.org/licenses/LICENSE-2.0 8 | -- 9 | -- Unless required by applicable law or agreed to in writing, software 10 | -- distributed under the License is distributed on an "AS IS" BASIS, 11 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | -- See the License for the specific language governing permissions and 13 | -- limitations under the License. 14 | -- 15 | -- 16 | -- This module provides accesss to ALL Unit Tests for this project. 17 | -- It exports a single 'main' function that runs all of the unit tests which are defined in sub-modules of UnitTests (inside the test/UnitTests/ folder) 18 | -- 19 | -- Source for HUnit usage: https://wiki.haskell.org/HUnit_1.0_User's_Guide 20 | 21 | 22 | module UnitTests (main) 23 | where 24 | 25 | -- We explicitly import all the functions, types and constructors that we use in THIS module 26 | import Test.HUnit (Test(TestList), Counts, runTestTT) 27 | 28 | import qualified UnitTests.Show (tests) 29 | import qualified UnitTests.Addition (tests) 30 | import qualified UnitTests.Multiplication (tests) 31 | -- import qualified UnitTests.Ordering (tests) 32 | 33 | 34 | main :: IO Counts 35 | main = do -- This IO Action runs only the unit tests 36 | runTestTT tests 37 | 38 | 39 | -- Each sub-module of UnitTests defines a list of TestCase objects called 'tests' (its only export) containing a list of the unit tests it implements 40 | -- We simply concatenate these tests to construct a single 'TestList' which 'main' then executes using the 'runTestTT' function (from Test.HUnit) 41 | tests :: Test 42 | tests = TestList $ 43 | [] 44 | ++ UnitTests.Show.tests 45 | ++ UnitTests.Addition.tests 46 | ++ UnitTests.Multiplication.tests 47 | -- ++ UnitTests.Ordering.tests 48 | -------------------------------------------------------------------------------- /cas.cabal: -------------------------------------------------------------------------------- 1 | name: cas 2 | version: 0.1.0.0 3 | synopsis: Computer Algebra System (CAS) implemented in Haskell 4 | description: Please see README.md 5 | homepage: https://github.com/abid-mujtaba/cas#readme 6 | license: Apache-2.0 7 | license-file: LICENSE 8 | author: Abid H. Mujtaba 9 | maintainer: abid.mujtaba@comsats.edu.pk 10 | copyright: 2015 11 | category: custom 12 | build-type: Simple 13 | -- extra-source-files: 14 | cabal-version: >=1.10 15 | 16 | library 17 | hs-source-dirs: src 18 | exposed-modules: CAS 19 | -- We must define other modules that are used by the library but are not exposed to external users 20 | other-modules: Expr 21 | , Expr.Show 22 | , Expr.Num 23 | , Expr.Const 24 | , Expr.Add 25 | , Expr.Mul 26 | , Expr.Fractional 27 | , Vars 28 | build-depends: base >= 4.7 && < 5 29 | , regex-posix 30 | default-language: Haskell2010 31 | 32 | 33 | test-suite cas-test 34 | type: exitcode-stdio-1.0 35 | hs-source-dirs: test 36 | main-is: Test.hs 37 | build-depends: base 38 | , cas 39 | , HUnit 40 | , QuickCheck 41 | other-modules: UnitTests 42 | , UnitTests.Base 43 | , UnitTests.Addition 44 | , UnitTests.Multiplication 45 | , UnitTests.Ordering 46 | , UnitTests.Show 47 | , QuickTests 48 | , QuickTests.Arbitrary 49 | , QuickTests.Addition 50 | , QuickTests.Multiplication 51 | , QuickTests.Ordering 52 | ghc-options: -threaded -rtsopts -with-rtsopts=-N 53 | default-language: Haskell2010 54 | 55 | source-repository head 56 | type: git 57 | location: https://github.com/abid-mujtaba/haskell-cas 58 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by 'stack init' 2 | # 3 | # Some commonly used options have been documented as comments in this file. 4 | # For advanced use and comprehensive documentation of the format, please see: 5 | # http://docs.haskellstack.org/en/stable/yaml_configuration/ 6 | 7 | # Resolver to choose a 'specific' stackage snapshot or a compiler version. 8 | # A snapshot resolver dictates the compiler version and the set of packages 9 | # to be used for project dependencies. For example: 10 | # 11 | # resolver: lts-3.5 12 | # resolver: nightly-2015-09-21 13 | # resolver: ghc-7.10.2 14 | # resolver: ghcjs-0.1.0_ghc-7.10.2 15 | # resolver: 16 | # name: custom-snapshot 17 | # location: "./custom-snapshot.yaml" 18 | resolver: lts-6.1 19 | 20 | # User packages to be built. 21 | # Various formats can be used as shown in the example below. 22 | # 23 | # packages: 24 | # - some-directory 25 | # - https://example.com/foo/bar/baz-0.0.2.tar.gz 26 | # - location: 27 | # git: https://github.com/commercialhaskell/stack.git 28 | # commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a 29 | # - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a 30 | # extra-dep: true 31 | # subdirs: 32 | # - auto-update 33 | # - wai 34 | # 35 | # A package marked 'extra-dep: true' will only be built if demanded by a 36 | # non-dependency (i.e. a user package), and its test suites and benchmarks 37 | # will not be run. This is useful for tweaking upstream packages. 38 | packages: 39 | - '.' 40 | # Dependency packages to be pulled from upstream that are not in the resolver 41 | # (e.g., acme-missiles-0.3) 42 | extra-deps: [] 43 | 44 | # Override default flag values for local packages and extra-deps 45 | flags: {} 46 | 47 | # Extra package databases containing global packages 48 | extra-package-dbs: [] 49 | 50 | # Control whether we use the GHC we find on the path 51 | # system-ghc: true 52 | # 53 | # Require a specific version of stack, using version ranges 54 | # require-stack-version: -any # Default 55 | # require-stack-version: ">=1.1" 56 | # 57 | # Override the architecture used by stack, especially useful on Windows 58 | # arch: i386 59 | # arch: x86_64 60 | # 61 | # Extra directories used by stack for building 62 | # extra-include-dirs: [/path/to/dir] 63 | # extra-lib-dirs: [/path/to/dir] 64 | # 65 | # Allow a newer minor version of GHC than the snapshot specifies 66 | # compiler-check: newer-minor -------------------------------------------------------------------------------- /src/Expr.hs: -------------------------------------------------------------------------------- 1 | -- | The Expr module defines the Expr type-class and consequently forms the core of the CAS module 2 | module Expr 3 | ( 4 | Expr (..) -- The (..) indicates that ALL constructors of the Expr data type are exported 5 | , symbol 6 | , ExprType (..) 7 | , exprtype 8 | ) 9 | where 10 | 11 | import Text.Regex.Posix 12 | 13 | -- | The Expr type forms the core of the Computer Algebra System. 14 | -- Its strength lies in its recursive definition of what an expression can be. 15 | data Expr = 16 | Symbol String -- A Symbol is an internal data-type corresponding to a single variable 17 | | Atom Integer (Expr) Integer -- The work-horse of the Expr class. This type corresponds to any expression with an integer coefficient (possibly negative) and the core expression is raised to a (possibly negative) integer power 18 | | Add Expr Expr -- Define addition as a constructor that pulls together just two expressions. Addition of more than two expressions will use 'Add' recursively e.g. Add x (Add y z). Such a definition is exactly like 'Cons' is used to recursively build up lists and indeed the list pattern matching paradigm will be used here 19 | | Mul Expr Expr -- Define multiplication analogously to addition 20 | | Inv Expr -- An "inverse" expression is basically an expression raised to the power -1. This is the mechanism for working with fractions 21 | deriving (Eq) -- By declaring that Expr derives from the Eq type-class we declare that two Expressions can be compared for equality using a naive comparison where the expressions are matched recursively in their entirety 22 | 23 | 24 | symbol :: String -> Expr 25 | symbol name 26 | -- We use guards to test that the string passed to the function is valid 27 | -- The first guard uses regex which are posix based hence the strange character class names 28 | | name =~ "^[[:alpha:]][[:alnum:]_]*$" = Atom 1 (Symbol name) 1 29 | | otherwise = error "Valid symbol strings start with an alphabet and only contain alphabets, numbers and _ (underscore)." 30 | 31 | 32 | -- A type that declares the expression-type 33 | data ExprType = 34 | CNT 35 | | SYM 36 | | EXP 37 | | ADD 38 | | MUL 39 | | INV 40 | deriving (Show) 41 | 42 | 43 | -- A function that takes an expression and returns the encapsulating (outer-most) ExprType IGNORING the power of the exponent 44 | exprtype :: Expr -> ExprType 45 | exprtype (Atom _ _ 0) = CNT 46 | exprtype (Atom _ (Symbol _) _) = SYM 47 | exprtype (Atom _ (Add _ _) _) = ADD 48 | exprtype (Atom _ (Mul _ _) _) = MUL 49 | exprtype (Atom _ (Inv _) _) = INV 50 | -------------------------------------------------------------------------------- /test/UnitTests/Addition.hs: -------------------------------------------------------------------------------- 1 | 2 | -- Defines a list of the Unit Tests that test aspects of Addition 3 | 4 | module UnitTests.Addition (tests) 5 | where 6 | 7 | -- import Prelude hiding ((^)) -- This allows us to use the ^ operator defined in CAS without collision with Prelude.^ 8 | import Test.HUnit (Test(TestLabel, TestCase)) 9 | 10 | import CAS 11 | import UnitTests.Base 12 | 13 | 14 | -- We add a label to the TestCase by using the TestLabel constructor. 15 | -- The assertions are grouped together as a single TestCase 16 | -- Since the assertions are IO () we use the 'do' keyword to group together a sequence of them 17 | -- The first assertion that fails causes the entire TestCase to fail and the subsequent assertions are not tested 18 | -- Each assertEqual call takes the format: aE 19 | 20 | tests = [ -- We create a list of TestCases 21 | TestLabel "Adding zero to an expression" $ 22 | TestCase $ do 23 | 24 | aE "test1" (x + 0) x 25 | aE "test2" (0 + x) x 26 | 27 | -- TestLabel "Adding similar products" $ 28 | -- TestCase $ do 29 | -- 30 | -- let e = x + y 31 | -- 32 | -- aE "test1" (2 * e) (e + e) 33 | -- aE "test2" (5 * e) ((2 * e) + (3 * e)) 34 | -- aE "test3" (3 * e) ((-2 * e) + (5 * e)) 35 | -- aE "test4" (-7 * e) ((-3 * e) + (4 * (-e))) 36 | -- , 37 | -- 38 | -- TestLabel "Adding element to product of same element" $ 39 | -- TestCase $ do 40 | -- 41 | -- aE "test1" (3 * x) (x + (2 * x)) 42 | -- aE "test2" (-2 * x) (x + (-3 * x)) 43 | -- aE "test3" (3 * x * y) ((x * y) + (2 * x * y)) 44 | -- aE "test4" (3 * x^2) (x^2 + (2 * x^2)) 45 | -- , 46 | -- 47 | -- TestLabel "Subtracting equal expressions" $ 48 | -- TestCase $ do 49 | -- 50 | -- let e1 = -1 + x 51 | -- -- let e2 = 2 * (-x) / y 52 | -- let e3 = -y + (2 * x * y) 53 | -- let e4 = x + y 54 | -- let e5 = -1 + e4 55 | -- let e6 = e5 + z 56 | -- 57 | -- aE "test2" 0 (x - x) 58 | -- aE "test3" 0 (e1 - e1) 59 | -- -- aE "test4" 0 (e2 - e2) 60 | -- aE "test5" 0 (e3 - e3) 61 | -- aE "test6" 0 (e4 - e4) 62 | -- aE "test7" 0 (e5 - e5) 63 | -- aE "test8" 0 (e6 - e6) 64 | -- , 65 | -- 66 | -- TestLabel "Additive Commutation" $ 67 | -- TestCase $ do 68 | -- 69 | -- let e2 = (1 + 2 * y) 70 | -- let e3 = (2 + y) 71 | -- let e4 = (1 + 2 * z) 72 | -- 73 | -- let e5 = 9 * y * z^2 74 | -- let e6 = z * (z + 1)^2 75 | -- 76 | -- aE "test1" (x + y) (y + x) 77 | -- aE "test2" (x + e2) (e2 + x) 78 | -- aE "test3" (e3 + e4) (e4 + e3) 79 | -- aE "test4" (e5 + e6) (e6 + e5) 80 | ] 81 | -------------------------------------------------------------------------------- /test/UnitTests/Multiplication.hs: -------------------------------------------------------------------------------- 1 | -- Defines a list of the Unit Tests that test aspects of Multiplication 2 | 3 | module UnitTests.Multiplication (tests) 4 | where 5 | 6 | -- import Prelude hiding ((^)) -- This allows us to use the ^ operator defined in CAS without collision with Prelude.^ 7 | import Test.HUnit (Test(TestCase, TestLabel)) 8 | 9 | import CAS 10 | import UnitTests.Base 11 | 12 | 13 | tests = [ 14 | TestLabel "Multiplying by identity (1)" $ 15 | TestCase $ do 16 | 17 | aE "test1" x (1 * x) 18 | aE "test2" x (x * 1) 19 | aE "test3" (x + y) ((x + y) * 1) 20 | aE "test4" (x + y) (1 * (x + y)) 21 | aE "test5" (x * y) (x * y * 1) 22 | aE "test6" (x * y) (x * 1 * y) 23 | aE "test7" (x * y) (1 * x * y) 24 | , 25 | 26 | TestLabel "Exponentiation through multiplication" $ 27 | TestCase $ do 28 | 29 | aE "test1" (x^2) (x * x) 30 | aE "test2" (x^3) (x * x * x) 31 | -- 32 | -- TestLabel "Multiplying by oneself" $ 33 | -- TestCase $ do 34 | -- 35 | -- aE "test1" x^2 36 | 37 | -- TestLabel "Multiplicative Commutation" $ 38 | -- TestCase $ do 39 | -- 40 | -- let e1 = (x + 1) 41 | -- let e2 = (y + 2) 42 | -- let e3 = (z * (x + (2 * y))) 43 | -- let e4 = (((x * y) + 1) * (y + z)) 44 | -- 45 | -- aE "test1" (x * y) (y * x) 46 | -- aE "test2" (e1 * e2) (e2 * e1) 47 | -- aE "test3" (e3 * e4) (e4 * e3) 48 | -- , 49 | -- 50 | -- TestLabel "Division" $ 51 | -- TestCase $ do 52 | -- 53 | -- let w = Symbol "w" 54 | -- let q = w * x * y * z 55 | -- 56 | -- aE "test1" 1 (x / x) 57 | -- aE "test2" x (x^2 / x) 58 | -- aE "test3" (1/x) (x/x^2) 59 | -- aE "test4" (x^2) (x^7 / x^5) 60 | -- aE "test5" (1/x^3) (x^5 / x^8) 61 | -- aE "test6" (x + y) ((x + y)^3 / (x + y)^2) 62 | -- aE "test7" (x * z) ((x * y * z) / y) 63 | -- aE "test8" (x * y * z) ((x * y^2 * z) / y) 64 | -- aE "test9" (w * x * z) (q / y) 65 | -- aE "test10" (1 / (w * x * z)) (y / q) 66 | -- , 67 | -- 68 | -- TestLabel "Multiplying Fractions" $ 69 | -- TestCase $ do 70 | -- 71 | -- let xi = 1/x 72 | -- let yi = 1/y 73 | -- let f1 = x/y 74 | -- let f2 = (y^3/x^2) 75 | -- 76 | -- aE "test1" (2/x) (2 * xi) 77 | -- aE "test2" (y/x) (xi * y) 78 | -- aE "test3" (1/(x * y)) (xi * yi) 79 | -- aE "test4" (1/(x^2)) (xi * xi) 80 | -- aE "test5" 1 (x * xi) 81 | -- aE "test6" x (y * (x/y)) 82 | -- aE "test7" xi (yi * (y/x)) 83 | -- aE "test8" (y^2/x) (f1 * f2) 84 | ] 85 | -------------------------------------------------------------------------------- /test/UnitTests/Show.hs: -------------------------------------------------------------------------------- 1 | -- Defines a list of tests that test the "show" aspects of expressions 2 | -- that is, how they are rendered in to strings by the "show" function 3 | 4 | module UnitTests.Show 5 | where 6 | 7 | import Test.HUnit (Test(TestCase, TestLabel)) 8 | 9 | import CAS 10 | import UnitTests.Base 11 | 12 | 13 | tests = [ 14 | TestLabel "Rendering plain symbols" $ 15 | TestCase $ do 16 | 17 | aE "test1" "x" $ show x 18 | aE "test2" "y" $ show y 19 | aE "test3" "z" $ show z 20 | , 21 | 22 | TestLabel "Rendering pre-ordered addition of symbols and constants" $ 23 | TestCase $ do 24 | 25 | aE "test1" "(x + y)" $ show (x + y) 26 | aE "test2" "(y + z)" $ show (y + z) 27 | aE "test3" "(x + z)" $ show (x + z) 28 | aE "test4" "(x + 2)" $ show (x + 2) 29 | aE "test5" "(y + 3)" $ show (y + 3) 30 | , 31 | 32 | TestLabel "Rendering pre-ordered addition of more than two symbols and constants" $ 33 | TestCase $ do 34 | 35 | aE "test1" "(x + y + z)" $ show (x + y + z) 36 | aE "test2" "(x + y + 3)" $ show (x + y + 3) 37 | , 38 | 39 | TestLabel "Rendering subtraction of symbols and constants" $ 40 | TestCase $ do 41 | 42 | aE "test1" "(x - y)" $ show (x - y) 43 | aE "test2" "(x - 3)" $ show (x - 3) 44 | aE "test3" "(x + y - z)" $ show (x + y - z) 45 | aE "test4" "(x - y + z)" $ show (x - y + z) 46 | , 47 | 48 | TestLabel "Rendering addition and subtraction where the first symbol is negative" $ 49 | TestCase $ do 50 | 51 | aE "test1" "(-x + y)" $ show (-x + y) 52 | aE "test2" "(-x - y)" $ show (-x - y) 53 | , 54 | 55 | TestLabel "Rendering pre-ordered constants, symbols and sums being multiplied" $ 56 | TestCase $ do 57 | 58 | aE "test1" "x y" $ show (x * y) 59 | aE "test2" "2 z" $ show (2 * z) 60 | aE "test3" "-3 x" $ show (-3 * x) 61 | aE "test4" "2 (x + y)" $ show (2 * (x + y)) 62 | aE "test5" "2 (x + y) z" $ show (2 * (x + y) * z) 63 | , 64 | 65 | TestLabel "Rendering pre-ordered sum of products" $ 66 | TestCase $ do 67 | 68 | aE "test1" "(2 x + y)" $ show (2 * x + y) 69 | aE "test2" "(-3 x + z)" $ show (-3 * x + z) 70 | aE "test3" "(2 x y + 3 z)" $ show (2 * x * y + 3 * z) 71 | , 72 | 73 | TestLabel "Rendering (pre-ordered) products of exponents" $ 74 | TestCase $ do 75 | 76 | aE "test1" "x^2" $ show (x^2) 77 | aE "test2" "x^3" $ show (x^3) 78 | aE "test3" "x^2 y" $ show (x^2 * y) 79 | aE "test4" "x y^2" $ show (x * y^2) 80 | aE "test5" "x (y + z)" $ show (x * (y + z)) 81 | aE "test6" "(x + y)^2" $ show ((x + y)^2) 82 | , 83 | 84 | TestLabel "Rendering divided terms" $ 85 | TestCase $ do 86 | 87 | aE "test1" "1 / x" $ show (1 / x) 88 | aE "test2" "x / y" $ show (x / y) 89 | aE "test3" "1 / x^2" $ show (1 / x^2) 90 | aE "test4" "x / (y z)" $ show (x / (y*z)) 91 | aE "test5" "x / (y + z)" $ show (x / (y + z)) 92 | aE "test6" "x / 2" $ show (x / 2) 93 | aE "test7" "(x + y) / 2" $ show ((x + y) / 2) 94 | ] 95 | -------------------------------------------------------------------------------- /test/UnitTests/Ordering.hs: -------------------------------------------------------------------------------- 1 | -- Defines a list of the Unit Tests that test aspects of Ordering of expressions 2 | 3 | module UnitTests.Ordering (tests) 4 | where 5 | 6 | -- import Prelude hiding ((^)) -- This allows us to use the ^ operator defined in CAS without collision with Prelude.^ 7 | import Test.HUnit(Test(TestCase, TestLabel)) 8 | 9 | import CAS 10 | import UnitTests.Base 11 | 12 | 13 | tests = [ 14 | TestLabel "Order of Product Elements" $ 15 | TestCase $ do 16 | 17 | aE "test1" "(x * y)" $ show (x * y) 18 | aE "test2" "(2 * x)" $ show (x * 2) 19 | aE "test3" "(2 * x^2 * y)" $ show (2 * y * x^2) 20 | aE "test4" "-(2 * x * y^2)" $ show (x * (-2) * y^2) 21 | aE "test5" "(x * y * (y + z))" $ show (x * y * (y + z)) 22 | aE "test6" "(x * z^2 * (x + y))" $ show ((x + y) * z^2 * x) 23 | aE "test7" "(x * z^2 * (x + y) * (y + z))" $ show ((y + z) * z^2 * (x + y) * x) 24 | aE "test8" "(x * y^2 * (y + z)^3)" $ show ((y + z)^2 * y^2 * (y + z) * x) 25 | aE "test9" "((y + z) * ((x * y) + 1))" $ show ((x * y + 1) * (y + z)) 26 | , 27 | 28 | TestLabel "Graded Reversed Lexical Order" $ 29 | TestCase $ do 30 | 31 | aE "test1" EQ $ compare x x 32 | aE "test2" LT $ compare 2 x 33 | aE "test3" GT $ compare x y 34 | aE "test4" GT $ compare (x^2) x 35 | aE "test5" LT $ compare x (y^2) 36 | aE "test6" GT $ compare x (2 * y) 37 | aE "test7" LT $ compare (2 * y) (3 * x) 38 | aE "test8" EQ $ compare (2 * x) (2 * x) 39 | aE "test9" GT $ compare (x * y) (y * z) 40 | aE "test10" LT $ compare (x * y) (x^2) 41 | aE "test11" GT $ compare (x * y^2 * z) (x * y * z^2) 42 | aE "test12" GT $ compare (x * y * z) (x * z^2) 43 | aE "test13" EQ $ compare (x * y * z) (x * y * z) 44 | aE "test14" EQ $ compare (x * y^2 * z) (x * y^2 * z) 45 | aE "test15" LT $ compare x (2 * x) 46 | aE "test16" GT $ compare (3 * x) x 47 | aE "test17" LT $ compare x (-2 * x) -- Negative constants shouldn't impact the order of terms 48 | , 49 | 50 | TestLabel "Order of Added Elements" $ 51 | TestCase $ do 52 | 53 | aE "test1" "(x + 2)" $ show (x + 2) 54 | aE "test2" "(x + y)" $ show (x + y) 55 | aE "test3" "(x^2 + y)" $ show (x^2 + y) 56 | aE "test4" "(y^2 + x)" $ show (x + y^2) 57 | aE "test5" "(y + (2 * z) + 1)" $ show $ y + (1 + 2 * z) 58 | aE "test6" "(x - y)" $ show (x - y) 59 | aE "test7" "(x - y)" $ show (-y + x) 60 | aE "test8" "(x/y + 1)" $ show (1 + x/y) 61 | aE "test9" "(x + x/y)" $ show (x + x/y) 62 | aE "test10" "(1/x + 1/y)" $ show (1/x + 1/y) 63 | aE "test11" "(x/y + y/z)" $ show (x/y + y/z) 64 | aE "test12" "(x/y + z/y)" $ show (x/y + z/y) 65 | aE "test13" "(x^2/y + z^2/x)" $ show (x^2/y + z^2/x) 66 | , 67 | 68 | TestLabel "Comparing expressions" $ 69 | TestCase $ do 70 | 71 | aE "test1" EQ $ compare x x 72 | aE "test2" GT $ compare x y 73 | aE "test3" LT $ compare y x 74 | aE "test4" EQ $ compare (x + y) (y + x) 75 | aE "test5" GT $ compare (x + y) (y + z) 76 | aE "test6" GT $ compare (x - z - 2) (1 - y) 77 | aE "test7" GT $ compare (x^2 + y^2) (z^2) 78 | aE "test8" GT $ compare y (2*z) 79 | aE "test9" LT $ compare z (3*y) 80 | aE "test10" GT $ compare (x + 1) (y + 2) 81 | aE "test11" GT $ compare x (-x) 82 | aE "test12" GT $ compare (z + 1) (-z + 1) 83 | aE "test13" GT $ compare (z^2) ((z + 1)^2) 84 | ] 85 | -------------------------------------------------------------------------------- /src/Expr/Show.hs: -------------------------------------------------------------------------------- 1 | -- | The Expr.Show module makes the Expr type an instance of the Show class allowing expressions to be printed (shown) 2 | module Expr.Show 3 | ( 4 | show 5 | , showActual 6 | ) 7 | where 8 | 9 | import Expr (Expr (Symbol, Atom, Add, Mul, Inv)) -- We need access to ALL constructors of Expr since we need to pattern match against them 10 | import qualified Expr.Add (negate) 11 | 12 | -- We declare Expr to be an instance of the Show type-class 13 | -- This requires that we define the 'show' function and what it is supposed to do when acting on an object of type Expr 14 | instance Show Expr where 15 | show (Symbol s) = s -- Inside every Symbol is a String. We simply tell 'show' that for a Symbol object simply show the String inside 16 | show (Atom c _ 0) = show c -- A constant is detected when we have an atom with power 0. This pattern detects a constant and renders it by showing simply the coefficient 17 | show (Atom c e@(Inv _) 1) = show c ++ show e -- An Inv encapsulated inside an isolated Atom 18 | show (Atom 1 e 1) = show e 19 | show (Atom c e p) = showCoeff c ++ show e ++ showPower p -- We use String concatenation to define how an atom is to be represented as a String. We define separate functions for dealing with the constant and power parts of an atom 20 | -- Added expressions are always surrounded by parentheses. Recursively added expressions share a single set of parentheses 21 | show (Add x y@(Atom c _ _)) 22 | | c >= 0 = "(" ++ showInSum x ++ " + " ++ showInSum y ++ ")" 23 | | otherwise = "(" ++ showInSum x ++ " - " ++ showInSum (Expr.Add.negate y) ++ ")" -- If the second argument has negative coeff then we show a minus sign and use negate to flip the sign of the expression before showing it 24 | -- show (Mul x (Atom 1 (Inv y) 1)) = show x ++ " / (" ++ show y ++ ")" 25 | show (Mul x y) = show x ++ showInMul y 26 | show (Inv y) = " / " ++ showInInv y -- Isolated Inv 27 | 28 | -- Define how to show the coefficient of an Atom is to be rendered/shown 29 | showCoeff :: Integer -> String 30 | showCoeff 1 = "" -- A coeff of 1 is not shown 31 | showCoeff (-1) = "-" -- A coeff of -1 is simply shown with a minus sign 32 | showCoeff c = show c ++ " " -- The coeff is separated from the next part by a single space 33 | 34 | 35 | -- Define how the power of an Atomj is to be rendered 36 | showPower :: Integer -> String 37 | showPower 1 = "" -- A power of 1 is not shown 38 | showPower p = "^" ++ show p 39 | 40 | 41 | -- Function for dealing with recursively added expressions 42 | showInSum :: Expr -> String 43 | -- If an expression consists of an addition with NO coefficient and no power then it is included in the original set of parentheses defined by 'show (Add x y)' 44 | -- If the coeff of the second term is negative then a minus sign is shown as the connector 45 | showInSum (Atom 1 (Add x y@(Atom c _ _)) 1) 46 | | c < 0 = showInSum x ++ " - " ++ showInSum (Expr.Add.negate y) -- Since we are showing a "-" sign we negate the second argument before we show it 47 | | otherwise = showInSum x ++ " + " ++ showInSum y 48 | showInSum e = show e -- Every other kind of expression is then relegated back to the original show for rendering 49 | 50 | 51 | -- Function for dealing with expressions inside an Inv 52 | showInInv :: Expr -> String 53 | showInInv e@(Atom _ (Mul _ _) _) = "(" ++ show e ++ ")" -- Products inside Inv should be surrounded by parentheses 54 | showInInv e = show e 55 | 56 | 57 | -- Function for dealing with expressions inside a Mul 58 | showInMul :: Expr -> String 59 | showInMul (Atom 1 ie@(Inv _) 1) = show ie -- An inverse inside a Mul is shown with no space and rendering passed on to 'show' which deals with Inv properly 60 | showInMul b = " " ++ show b 61 | 62 | 63 | -- Utility function for showing the actual constructor and recursive nature of an expression. 64 | showActual :: Expr -> String 65 | showActual (Symbol s) = s 66 | showActual (Add a b) = "(Add " ++ showActual a ++ " " ++ showActual b ++ ")" 67 | showActual (Mul a b) = "(Mul " ++ showActual a ++ " " ++ showActual b ++ ")" 68 | showActual (Atom c e p) = "(Atom " ++ show c ++ " " ++ showActual e ++ " " ++ show p ++ ")" 69 | showActual (Inv e) = "(Inv " ++ show e ++ ")" 70 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Abid Hasan Mujtaba 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | # 16 | # This is the Makefile which provides targets for common actions that one can perform with this project. 17 | 18 | # We provide a list of phony targets which specify actions that are not based on changes in the code-base 19 | .PHONY: clean, test, ghci, unit, quick, prof, heap, stats, prof_test, heap-d, heap-r, heap-b, heap-y 20 | 21 | # We define a simple variable for specifying which main function in the Test.hs script to use 22 | # The default value is 'main' and is used when the 'make test' is executed 23 | # The 'unit' and 'quick' targets redefine this variable before running 'test' 24 | MAIN = main 25 | 26 | # Analogous to MAIN we define PROF and PROF_FLAGS variables which are empty be default but are populated when the 'prof' target is executed. These add flags to the compilation and execution commands that allow profiling to occur. 27 | PROF = 28 | PROF_FLAGS = 29 | 30 | clean: # Clean the compilation by-products (.hi, .o, .prof files and executables) 31 | rm -f *.hi *.o *.prof *.hp *.aux *.ps Test test 32 | 33 | 34 | # test: Test 35 | # ./Test ${PROF} 36 | 37 | # Note: An @ symbol placed at the start of a command stops the executed command from being printed. We simply run the 'Test' executable. Profiling flags may be present if the 'prof' target is executed 38 | 39 | # The test target has the file Test as its dependency. 40 | # If the file doesn't exist the 'Test' rule is executed. 41 | # If it does exist the rule is still tested for recursive dependencies 42 | 43 | 44 | # Define targets for running a subset of the tests. This is carried out by redefining the MAIN variable 45 | # Note how 'test' is the only target of both 'unit' and 'quick'. The drawback of this strategy is that the 'Test' target is only activated when one of the .hs scripts change. So running one of the test targets after another one does NOT recompile the module and so the previously specified tests are run. This can be overcome by either running 'make clean' or by "touching" one of the dependencies of the 'Test' target. 46 | 47 | # unit: MAIN = main_unit 48 | # unit: test 49 | # 50 | # quick: MAIN = main_quick 51 | # quick: test 52 | 53 | # Define a phony target for compiling and executing the tests with profiling activated. This makes use of the PROF and PROF_FLAGS variables which are empty by default. 54 | 55 | # Time and Space Profiling. Generates a Test.prof file. 56 | prof_test: PROF_FLAGS = -prof -fprof-auto-calls 57 | prof_test: test 58 | 59 | prof: PROF = +RTS -p 60 | prof: prof_test 61 | 62 | 63 | # Source for heap profile options: https://downloads.haskell.org/~ghc/7.6.3/docs/html/users_guide/prof-heap.html 64 | 65 | # Heap Profiling by cost-center. This generates a Test.hp file. One can plot the profile data by running 'hp2ps Test.hp' which generates a Test.ps file with a graph in it. 66 | # The -L20 flag tells the profile to use 20 characters (instead of the default 25) for the methods being profiled. 67 | # Note that the profiled method names are in reverse order with the last method in the call-tree listed first. 68 | heap: PROF = +RTS -hc -L20 69 | heap: prof_test 70 | 71 | # Heap profile by "closure description" 72 | # This displays thunks on the heap in particular closures which don't have a well-defined name 73 | heap-d: PROF = +RTS -hd -L20 74 | heap-d: prof_test 75 | 76 | # Heap Profiling that focuses on retainers 77 | heap-r: PROF = +RTS -hr -L20 78 | heap-r: prof_test 79 | 80 | # Biographical Heap Profiling 81 | heap-b: PROF = +RTS -hc -hbdrag,void 82 | heap-b: prof_test 83 | 84 | # Heap Profile broken down by "type" 85 | heap-y: PROF = +RTS -hy 86 | heap-y: prof_test 87 | 88 | 89 | 90 | # Display the graph generated by the heap profile 91 | graph: 92 | hp2ps Test.hp 93 | okular Test.ps > /dev/null 2>&1 & 94 | 95 | 96 | stats: PROF_FLAGS = -rtsopts 97 | stats: PROF = +RTS -sstderr 98 | stats: test 99 | 100 | 101 | # Test: Test.hs CAS.hs Vars.hs Makefile 102 | # ghc --make ${PROF_FLAGS} -main-is Test.$(MAIN) Test.hs 103 | 104 | # We declare Test.hs to be a dependency of the executable Test. 105 | # If the timestamp on Test.hs is newer than that of Test Make knows that code changes have been made and so it runs the command (rule) specified. 106 | # Similarly if CAS.hs was changed recently we want the compilation to occur again to incorporate these changes. 107 | # The command simply compiles the Test.hs file and creates the Test executable 108 | # Note the use of -main-is which is used to specify the main function since it is inside the Test module and not a module named Main which is where ghc searches for it by default. 109 | # Note the use of the MAIN variable to define which function to use as the main function while compiling the script. The different targets (test, unit and quick) define this differently to access different main functions to limit the tests being run 110 | # Note the use of the PROF_FLAGS variable which when set for the 'prof' target causes the test module to be compiled with profiling enabled. 111 | 112 | 113 | # ghci: 114 | # make clean 115 | # ghci Vars.hs 116 | 117 | # Running ghci with a compiled module adds both Prelude and CAS together which causes conflicts in such things as the ^ operator. So in this target we first remove the compiled modules and then run ghci. 118 | # By providing the module name after ghci the module is loaded from the start. 119 | # Since Vars.hs imports CAS before it defines useful symbols and constants you automatically import CAS when you load the Vars module 120 | -------------------------------------------------------------------------------- /test/QuickTests/Arbitrary.hs: -------------------------------------------------------------------------------- 1 | -- Module that makes Expr and instance of the Arbitrary type-class allowing 2 | -- random expressions to be generated for use in QuickTests 3 | 4 | -- This module only need export the function 'arbitrary' since it is the existence of that function (defined for 'Arbitrary (Expr a)' that makes Expr and instance of the Arbitrary class) 5 | 6 | module QuickTests.Arbitrary (arbitrary) 7 | where 8 | 9 | import Test.QuickCheck 10 | 11 | import CAS 12 | 13 | -- Since Expr is a custom class we MUST make it an instance of the Arbitrary type-class before we can use it inside QuickCheck properties. The instantiation will let QuickCheck know how to generate random objects of type Expr 14 | -- 'arbitrary' is a definition (it is a function that takes no arguments so it is in effect a constant) which in this context must be of type 'Gen (Expr a)' i.e. an IO which corresponds to a random expression. 15 | -- We define it using the 'sized' function which takes as its single argument a function taking an integer and returning a Gen (Expr a) 16 | -- When we use 'sized' we get access to the size integer that QuickCheck uses to create arbitrary instances. We can use this size value to more intelligently construct the expressions (which is the purpose of arbitrary') 17 | 18 | instance (Show a, Integral a) => Arbitrary (Expr a) where 19 | arbitrary = sized arbitrary' 20 | 21 | 22 | arbitrary' :: (Show a, Integral a) => Int -> Gen (Expr a) 23 | arbitrary' 0 = arbitrary_const -- Base case which we define to be an arbitrary constant 24 | arbitrary' 1 = oneof [arbitrary_atom, arbitrary_neg_atom] -- When the required size is 1 we simply return an atomic expression (which can be negative) 25 | arbitrary' n = do 26 | ns <- split n 27 | assemble ns 28 | 29 | where 30 | assemble (b:bs) = assemble' bs $! arbitrary' b -- The result of split is never [] (at least a singleton) 31 | 32 | assemble' [] e = e 33 | assemble' (c:cs) e = assemble' cs $! apply op (arbitrary' c) e 34 | 35 | op = oneof [pure (+), pure (*)] 36 | 37 | apply o a b = o <*> a <*> b 38 | 39 | split 0 = pure [] 40 | split a = do 41 | p <- pick a 42 | fmap (p:) (split (a - p)) 43 | 44 | pick a = choose (1, a) 45 | 46 | -- The non-base case uses recursion, monad theory, and applicative functor techniques. 47 | -- Since the result of arbitrary' is the Gen monad (which is analogous to Random) all our calculations must be monadic and so we use 'do' notation. 48 | 49 | -- The first task in the 'do' is to use 'split' to construct a random separation of 'n' elements in to parts. 'split' takes an integer 'n' and returns a randomly generated list of integers which all add up to 'n'. 50 | -- It does so recursively. The base case is 'split 0' where we return an empty list. 51 | -- For non-zero 'n' we use 'pick' to get a random integer inside a Gen context. We use '<-' to extract the integer from the Gen context. 52 | -- The next statement inside the 'do' concats the integer to the list inside 'split (a - p)'. Since the recursive call 'split (p - a)' returns a list inside a Gen we use fmap to append 'p' to the list inside the Gen to get a larger list inside the Gen. 53 | -- Since pick returns a monad and split is called from inside a monadic do sequence we are forced to respect the context throughout the calculation. 54 | 55 | -- The definition of assemble basically sets it up to use assemble' which performs a "strict" accumulation of the expression as it goes along 56 | -- Note how we take the first integer 'b' and use arbitrary' to create an expression from it. This serves as the initial state of the accumulator. We use $! to force strict evaluation of the result of arbitrary' b to avoid a Stack Overflow 57 | 58 | -- The base case of assemble' occurs when the list of integers is empty in which case we simply return the accumulated expression 'e' 59 | -- For the recursive case we apply an operation between arbitrary' c and the expression e which is the accumulator to create the new accumulator. The result of this operation is evaulated strictly and becomes the new accumulator for the recusrive call to assemble' using cs 60 | 61 | 62 | -- 'apply' is a function which takes three arguments. The first is an operator (* or +) placed inside the Gen context. It is choosen randomly by the definition of 'op'. 63 | -- The definition of 'apply' takes the operator and two arguments and uses applicative functor technique to apply the operation between the two expressions, all of them inside the Gen context (since the whole calculation is Gen monadic). 64 | -- Using currying this means that 'apply op' is a function that takes two Gen monads and returns a Gen monad i.e. its signature is "(Gen a -> Gen a) -> Gen a -> Gen a -> Gen a". 65 | 66 | 67 | -- Constants and Symbols are the atomic expressions. Everything else is constructed from these (or by encapsulatng them in some fashion). 68 | -- We collect the Const and Symbol expression in to a single arbitrary definition which produces them with equal likelihood 69 | -- This definition will be used to create negative atomic expressions as well. 70 | arbitrary_atom :: (Integral a, Show a) => Gen (Expr a) 71 | arbitrary_atom = oneof [arbitrary_const, arbitrary_symbol] 72 | 73 | -- arbitrary_const returns a random Const object by taking a random integer from 1 to 9 and wrapping it inside Const. 74 | -- We don't include 0 because it leaves sums unchanged and more importantly it reduces products to zero which is counter-productive for testing. 75 | -- Negative constants are handled by 'arbitrary_negative' which takes positive constants and negates them. 76 | arbitrary_const :: (Integral a, Show a) => Gen (Expr a) 77 | arbitrary_const = frequency $ 78 | map (\(f, n) -> (f, return n)) $ 79 | [(1000, 1), (100, 2), (10, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (1, 9)] 80 | 81 | -- The constraint 'Integral a' in the signature is crucial since it allows us to use the const' smart constructor to create Const objects from randomly selected Int. 82 | 83 | -- We use 'frequency' to change the, well, frequency with which the constants are generated when arbitrary_const is called. Our aim is to have lower integers be more frequently produces than higher ones since it will keep the expressions manageable (verboseCheck lets us know how the distribution is coming out). 84 | -- 'frequency' takes a list of (Int, Gen) tuples where the integer is the weight with which the Gen is produced. So the higher the integer the more likely that Gen will be generated. 85 | 86 | -- We first create a list of (Int, Int) tuples where we list the integers 1 to 9 and attach the required weights to them. Highest for 1, then 2, then 3 and the rest are equally weighted at the bottom. 87 | -- The list of tuples for 1,2,3 is created explicitly. The remainder is added to it using ++ and is constructed by taking the list of integers from 4 to 9 and mapping a simple lambda function over it which transforms it in to a list of tuples with frequency 1. 88 | 89 | -- We then use map and a lambda function to create Gen (Const Int) objects out of the second element of each tuple using "return n". 90 | -- Note the use of pattern-matching within the lamdba function definition to gain access to the second element. 91 | 92 | -- Finally we present the constructed list to frequency for the generation of these objects. 93 | 94 | 95 | 96 | -- Analogous to 'arbitrary_const' this definition, 'arbitrary_symbol', returns a Symbol object corresponding (randomly) to x, y or z. 97 | -- Note that the signature reveals this to be a definition (and not a function). It corresponds to a randomly selected Expr. 98 | arbitrary_symbol :: Gen (Expr a) 99 | arbitrary_symbol = fmap Symbol $ elements ["x", "y", "z"] 100 | 101 | 102 | -- This definition creates randomly generated negative atomic expressions 103 | arbitrary_neg_atom :: (Show a, Integral a) => Gen (Expr a) 104 | arbitrary_neg_atom = fmap negate arbitrary_atom -- We map the negate function on the expression inside the Gen returned by arbitrary_atom 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /comments.md: -------------------------------------------------------------------------------- 1 | Copyright 2015 Abid Hasan Mujtaba 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | 16 | # Comments 17 | 18 | In this file we have placed the comments that have been made regarding the Haskell code in the CAS.hs file. The comments are very detailed and were taking too much space in the code file so they have been moved here. They have been given unique labels so they can be searched very quickly. 19 | 20 | At the bottom of the file are general comments about the development process and debugging. 21 | 22 | 23 | ## A. Module declaration 24 | 25 | **A.1** 26 | 27 | * We start by declaring this .hs file to be module. The module must have the same name as the file (CAS.hs).hs 28 | * In the module identifier after the module's name and delimited in parentheses we declare the classes, types and functions we want to export from the module. 29 | * The parentheses are followed by the keyword where and then the rest of the file is dedicated to defining the various objects that are being exported. 30 | 31 | **A.2** - ``Expr(Symbol)`` signifies that ONLY its ``Symbol`` value constructor is to be exported. All of the other constructors are hidden. Our intention is to allow a user to build up complex expressions by starting with ``Symbol``s and ``Const`` values as building blocks and tying them together with arithmetic operations without having to explicitly use the rest of the value constructors. To access ``Const`` values we use the ``const'`` function described in *A.3* 32 | 33 | **A.4** - We export the ``^`` infix function which we have defined in the module (overriding the Prelude definition of the same) 34 | 35 | 36 | 37 | ## B. Expr type 38 | 39 | **B.1** - We define the new data typeclass 'Expr' which corresponds to a general algebraic expression. This is achieved by using recursion in the definition of the constructors. By defining these carefully one can use pattern-matching to implement various functions for the 'Expr' typeclass. 40 | 41 | **B.2** - We declare 'Expr a' to be a type class where 'a' can be any concrete type 42 | 43 | **B.3** - A constant (basically a number) can be an expression by itself (the most basic kind there is) 44 | 45 | **B.4** - This is a recursive constructor with the Constructor name 'Sum' and which takes a list of 'Expr a' objects as its constructor parameters 46 | 47 | **B.5** - A fraction. Consists of a numerator and a denominator. 48 | 49 | **B.6** - Expression raised to an INTEGER power. 50 | 51 | **B.7** - The algebraic variables in the expression: x, y, z, .etc 52 | 53 | **B.8** - We declare Expr to be an instance of the Eq class since we will want to compare expressions for equality 54 | 55 | 56 | 57 | ## C. Expr instance of Show 58 | 59 | **C.1** 60 | 61 | * We declare 'Expr' to be an instance of the 'Show' typeclass. Since 'Expr a' is a typeclass with a type-parameter 'a' we declare that in our declaration of the instance we limit ourselves to types of class 'Show' i.e. this instance only refers to types 'Expr a' where 'a' itself is an instance of the Show class. This is the '(Show a) =>' part. 62 | * Then comes the actual instance declaration 'Show (Expr a)'. We need the parentheses when a type-parameter is included. 63 | * Lastly comes the 'where' keyword and after it on the following lines comes the functions that must be defined for a type to be an instance of the class. In the case of the 'Show' typeclass this only one function 'show' 64 | 65 | **C.2** - We pattern match on the 'Const a' constructor. In the case of constant we simply show the number. The 'a' in this line is NOT the same as the 'a' in the instance declaration line above it. Here 'a' matches the value inside the 'Const a' constructor. Since the instance declaration limits 'Expr a' to type-parameters 'a' that are an instance of 'Show' so we can run the 'show' method directly on the value 'a' inside the 'Const a' parameter 66 | 67 | **C.4** - The negation of an expression is simply appending '-' in front of it. We use the concatenation operator ':' to prepend '-' in front of the String representation of the expression 68 | 69 | **C.5** - Since 's' is a String (from the definition of the 'Symbol' constructor) we don't need to use 'show' here. Had we done so it would have printed the strings surrounded by quotation marks 70 | 71 | **C.6** 72 | 73 | * This is a utility function (NOT exported) for showing a list of expressions. The separator (+ or *) is specified. 74 | * The empty list should not occur but if it does we simply print (0) 75 | * For a non-empty list we print the surrounding parentheses and use another related utility function to print the meat of the expression. 76 | 77 | **C.7** - In this utility function for a single element we simply print the expression within. For a larger list we use a head:tail pattern-match to extract the head show it, add the separator and then use recursion on the rest. 78 | 79 | **C.8** - We analyze the elements of the list, in particular the first two elements. If the second element is negative we print a minus "-" and then recursively call showSum with the second element changed from negative to positive so that when it is printed another negative sign isn't printed. 80 | 81 | 82 | 83 | ## D. Expr instance of Num 84 | 85 | **D.1** 86 | 87 | * We define Expr to be an instance of the Num class which gives us access to the standard arithmetic operations. It is rather evident that we expect the type-parameter 'a' to be an instance of the Num class itself. 88 | * The really interesting thing is how we will map the arithmetic operators on to the constructors of the Expr class. Basically we use the operators to recursively construct every more complex expressions. This is why we will have to define simplify to carry out obvious simplification of the expression later on. 89 | 90 | **D.2** - We want ONLY constant integers in our expressions so we limit the type-constraint from the usual 'Num a' to 'Integral a'. This means that any calls to the methods defined in this class which uses non-integer Constants will raise an exception. 91 | 92 | **D.3** - We now use a function (sum') to carry out the actual addition. This allows us to write a specialized function to this effect. 93 | 94 | **D.4** 95 | 96 | * This definition is very clear. It however obfuscates the fact that this is a curried function. To see that one can define it equivalently as: ``(-) a b = sum' a (Neg b)`` 97 | * And now one can curry it as follows: ``let d = (-) (Const 4)`` then one can apply 'd' to other expressions: ``d x`` ---> ``(4 - x) = sum' (Const 4) (Neg (Symbol "x"))`` 98 | 99 | **D.5** - The Num typeclass defines a number of functions that we are not interested in implementing since they do not make sense for algebraic expressions. For these we simply define them to be equal to the 'undefined' function which will raise en exception if these are used. 100 | 101 | **D.6** 102 | 103 | * We define the fromInteger method which gives us the ability to detect integers in expressions from context and convert them in to expressions of type Const. An example will clarify. 104 | * With fromInteger so defined we can write '2 * x' and it will be interpreted and converted in to 'Const 2 * Symbol "x"'. This will save us from having to write 'Const 2' all the time. 105 | * Note the use of the ``const'`` smart constructor which is able to detect a negative integer and convert it in to a ``Neg (Const _)``. 106 | 107 | 108 | 109 | ## E. Expr instance of Fractional 110 | 111 | **E.1** 112 | 113 | * The instance declaration has a minor subtlety in the type-constraint 'Integral a'. Usually in such an overloading scenario the type-constraint would be (Fractional a). Note in the definition of the type Expr that the only constructor that (without recursion) uses the type-parameter 'a' is 'Const a'. For our application we are ONLY interested in expressions that contain integer constants so we restrict the type parameter 'a' here to 'Integral a'. 114 | * If a non-integer constant is found see (E.3) 115 | 116 | **E.2** - To construct the reciprocal part we use the ``frac`` function instead of ``Frac`` because the former is aware of how to properly construct fractions. It automatically simplifies the expression as we construct it. 117 | 118 | **E.3** - We define the fromRational method to be an 'error' so that when this method is called the specified error message is printed. This ensures that the constants in our expressions are only allowed to be integers. We will deal with rational fractions by using Frac. 119 | 120 | 121 | 122 | ## G. Simplification functions for expressions 123 | 124 | **G.1** 125 | 126 | * The purpose of the simplification of a ``Const`` is to ensure that NO ``Const`` object encapsulates a negative integer. 127 | * We use guards to check if the polarity of ``c`` is negative in which case we transform the ``Const`` in to a ``Neg (Const _)``. 128 | * When ``c`` is NOT negative the original ``Const`` should be returned. 129 | * We use the ``@`` symbol to generate an *as-pattern* with which we bind the name ``o`` to the pattern that has been successfully matched against on this line. 130 | * We use the name ``o`` to refer to the original ``Const`` when we return it with the last ``otherwise`` guard. 131 | 132 | 133 | **G.2** 134 | 135 | * We don't want the exponentiation of a ``Const`` to be stored inside an ``Exp`` object so we use pattern-matching to detect this possibility and construct the corresponding ``Const`` object. 136 | * To do so we use the ``^`` function from ``Prelude`` using the qualified import we made earlier. It raises the integer value inside the ``Const`` to the relevant power and then we store the result in a ``Const`` value to get back an ``Expr a`` as the function expects. 137 | 138 | **G.3** - Separate pattern for negative constant raised to a power since that isn't matched by the first pattern. Note the use of ``s`` to simplify the final ``Const`` in case the integer inside is negative. ``s`` will change that to a ``Neg (Const _)`` 139 | 140 | **G.4** 141 | 142 | * We define the simplification for a Sum expression that encapsulates a list of expressions. 143 | * We use pattern matching on the RHS to get the list of expressions 'xs' 144 | * The actual simplification is carried out by the ``simplify_sum`` utility function (this is analogous to how the action of ``+`` is carried out by ``sum'``). 145 | 146 | **G.5** - When we run out of patterns to match for simplification it means the expression cannot be simplified and so we return it as is. 147 | 148 | 149 | 150 | ## H. Collection of Const terms inside a Sum 151 | 152 | **H.1** 153 | 154 | * We use a let expression to bind ``(c, es)`` to the result of the ``foldr``. We use this binding in the function evaluation that follows. 155 | * The ``foldr`` takes a binary function, an initial accumulator which in this case is the tuple ``(0, [])`` and then folds the function over the list of expressions. 156 | * The idea is that the current accumulator and one element of the list is fed to the binary function ``fold_constants``. The function analyzes the element. If the element is a ``Const`` then its value is added to the first member of the accumulator which keeps track of the sum of constant values. 157 | * If the element passed in to ``fold_constants`` is NOT a ``Const`` then we leave the sum value unchanged and append the element to the list of expressions which forms the second part of the accumulator. 158 | * With this in mind it is obvious that the initial accumulator which appears in ``foldr`` must be ``(0, [])`` because we start with a constants sum value of 0 (and add to it element by element) and we start with an empty list to which we append non-Const expressions as we fold over the list ``xs`` 159 | * By using a ``foldr`` here we get to keep the order of elements from the original list 160 | 161 | **H.2** - The collected constant value ``c`` along with the simplified list of expressions ``es`` returned by the ``foldr`` is passed to the function ``append_constant`` which is defined using the ``where`` keyboard. We structure it this way because using guards in a function is the cleanest way of explaining what the code does. 162 | 163 | **H.3.** 164 | 165 | * If after the ``foldr`` the collected constant value ``c`` is 0 we simply return the collected list of expressions ``es``. 166 | * If ``c > 0`` then we simply append a ``Const`` expression corresponding to ``c`` at the end ``es``. 167 | * The remaining case corresponds to ``c < 0`` and we append it as a positive constant encapsulated in a ``Neg``. The idea is to keep *all constants in all expressions strictly positive* and use ``Neg`` where necessary. 168 | 169 | 170 | 171 | ## I. fold_constants 172 | 173 | *Binary function for collecting terms inside a Sum* 174 | 175 | **I.1** 176 | 177 | * The desired functionality for this binary function is defined in comment H.1. 178 | * Because it is to be used in a ``foldr`` (as contrasted with a ``foldl``) the element of the folded list is the first argument to ``foldr`` and the accumulator is the second. 179 | * Consequently the signature starts with an ``Expr a`` for the element and then ``(a, [Expr a])`` for the accumulator and returns an object of the same type as the accumulator 180 | * Note: By using a type-constraint of ``Integral a`` we can refer to integers inside ``Const n``. We use ``(a, [Expr a])`` because we want the first element of the accumulator to have the same type as that encapsulated by the ``Const``. It should be further noted that the definition of ``Expr`` shows that the only value constructor of ``Expr`` that uses the type-parameter ``a`` in its definition is ``Const``. 181 | 182 | **I.2** 183 | * We use pattern matching to access the element 'e', accumulator sum value 'm' and the collected list of expressions 'xs' 184 | * On the RHS we use a case expression to pattern-match on the element 'e' which is of type 'Expr a' 185 | * The first pattern we match is for 'e' being a Const with value n. In this case we simply add n to the current accumulator value m and leave the list of expressions unchanged (basically removing the Const from the list and placing its value inside the sum integer) 186 | 187 | **I.3** The second pattern matches for a negative constant and behave analogous to the first pattern. 188 | 189 | **I.4** If the element 'e' is not a (negative) constant we leave the sum value 'm' unchanged and prepend the element to the collected list. By virtue of prepending and not appending in a function that is used in a ``foldr`` we retain the ordering of the original list. 190 | 191 | 192 | 193 | ## J. empty_sum 194 | 195 | *Function that deals with the possibility of the list inside the Sum being empty* 196 | 197 | **J.1** - If the list of expressions destined to be encapsulated by Sum is empty (tested using the ``null`` function) we return a ``Const 0`` which is the logical equivalent of an empty sum (e.g. 3 + -3 = 0). 198 | 199 | **J.2** - If the list contains a single expression then we return just that expression (e.g. x + 0 = x). Otherwise we return the list encapsulated by a ``Sum`` 200 | 201 | 202 | 203 | ## K. Simplification of a Product Expression 204 | 205 | **K.1** 206 | 207 | * This is analogous to ``collect_sum_const`` with one major difference. We want to keep the numerator (``n``) and denominator (``d``) constants separate. 208 | * To that end the accumulator consists of two integers ``n`` and ``d`` and the simplified list of expressions ``es``. 209 | * ``fold_prod_constants`` takes an element of the list and checks (pattern-matches) agains a constant in either the numerator or denominator. 210 | * The initial accumulator consists of ``(1, 1, [])`` since the identity element for multiplication is ``1``. 211 | * NOTE: This is monoid behaviour. Eventually one can implement Sum and Prod as monoids along with the functions ``+`` and ``*`` respectively. 212 | 213 | **K.2** - We use the case expression to pattern match against the final values of ``n`` and ``d`` (which we get from the ``foldr``). Since the list of expressions is for a ``Prod`` a constant value of ``1`` is redundant (just as ``0`` is redundant in a sum), so we pattern match to deal with these possibilities. 214 | 215 | **K.3** 216 | 217 | * Again we use the case expression to pattern match against the element of the expression list (an arbitrary expression). If it is a ``Const`` we multiply it with the accumulator's numerator value. 218 | * Otherwise we simply append the expression to the accumulator's expression list ``es`` 219 | 220 | **K.4** - A careful study of ``collect_prod_const`` will reveal that there exists a special case where both ``n`` and ``d`` are equal to ``1`` and ``es`` is null (``[]``). In this case the function returns an empty list (a Product with no elements). This actually corresponds to a value of ``1`` which is what the corresponding pattern match returns. 221 | 222 | 223 | 224 | ## L. Expr instance of Ord 225 | 226 | **L.1** 227 | 228 | * We only need to define the ``compare`` function while making Expr an instance of the Ord class. Since it takes two objects, compares them and generates the resulting ``Ordering`` object (with values of ``LT``, ``EQ`` or ``GT``) we get the remaining comparison operators (``<``, ``<=``, ``==``, ``>``, ``>=``) for free. 229 | * In addition the Data.List.sort function will also work automatically once ``Expr`` is made an instance of the ``Ord`` type-class. 230 | * In this instantiation we are basically implementing the lexical order of expressions especially as used to place them inside a ``Sum``. 231 | * The lexical order is defined to be "Graded/Degree reverse lexicographic order" 232 | * This ``compare`` function starts by comparing the degree of expressions and if those match hands over to compare' 233 | 234 | **L.3** 235 | 236 | * We define the comparison of two ``Neg`` expressions to have the same ordering as the expressions itself. 237 | * When only one expression is negative then two possibilities exist. 238 | * If the expression inside the negative is the same as the other expression then the positive expression is greater. 239 | * If the absolute value of the two expressions are not equal then we return the ordering of the absolute value of the two. 240 | 241 | **L.4** 242 | 243 | * Catch-all pattern for comparing expressions which are NOT both exponents. 244 | * We compare the degree of both expressions to calculate an order. 245 | * Note that when the degrees are equal we pass on the arguments to the ``compare'`` function. 246 | 247 | **L.5** 248 | 249 | - We are comparing two Frac with equal degree. 250 | - First we compare the numerators. If it is not EQ we return the result. 251 | - Otherwise we compare the denominators. 252 | - Note the use of ``mappend`` to perform this lazily and only compare the denominators if the comparision for the numerators turns out to be equal. 253 | - We want a Frac to have higher precedence (is written first) whenever it is compared with a non-Frac expression. 254 | 255 | **L.6** - We pattern-matched to look at the scenario where we are comparing two ``Symbol`` objects. We want ``x`` to appear before ``y`` so ``compare' x y`` should be equal to ``GT`` as required by reverse lexical order. To achieve that we must reverse the alphabetic ordering of the ``String`` inside the ``Symbol``. 256 | 257 | **L.7** 258 | 259 | * In compare' we are handling exponents whose total degree matches (inner degree times exponent). We use pattern-matching to deal with several possibilities. 260 | * If the power of the two exponents match as well then the lexical order is given by the lexical order between the bases of the two exponents. 261 | * If the power doesn't much then in the system we are defining here we want the expression with the higher power (lower degree in the base) to come first which is why when ``pa < pb`` we return ``LT``. 262 | 263 | **L.8** 264 | 265 | * Now that we have compared an exponent with another exponent we deal with the possibility of an exponent being compared with a non-exponent expression with the same degree (so a Sum or Prod). 266 | * In such a case an exponent has higher order according to the rule defined in ``L.7``. We want a pure exponent to appear first inside a sum followed by a more complex expression with equal degree. 267 | 268 | **L.9** - When comparing two ``Sum``s we perform a comparison of the list of expressions encapsulated. 269 | 270 | **L.10** - This is the base case. If both lists are exhausted simultaneously it means the lists must be equal and so the ``Sum``s must be equal. 271 | 272 | **L.11** - The first list whose arguments are exhausted takes higher precedence. 273 | 274 | **L.12** 275 | 276 | * The recursion occurs by comparing the heads of both lists. If the comparison is ``EQ`` we move on to the remaining part (tail) of both lists. If the comparison is not ``EQ`` then we simply return the value. 277 | * We use ``mappend`` from ``Data.Monoid`` which treats ``Ordering`` as a monoid. ``mappend`` is defined such that if the first argument is ``LT`` or ``GT`` then that is the result of ``mappend`` **regardless** of the value of the second argument. 278 | * If the first argument is ``EQ`` then the result is the value of the second argument. This is exactly what we want since we place the recursive call in the second argument so that it is only called if the first argument is ``EQ``. 279 | 280 | **L.13** - Compare a ``Sum`` with a general expression (non-``Sum`` since that match occurs earlier). The comparison is carried out analogous to the two ``Sum`` comparison but with only the single other expression compared successively with elements of the list inside the ``Sum``. 281 | 282 | **L.14** - Comparison is an inherently anti-symmetric operation. We use the ``flipCompare`` method to flip the resulting ``Ordering`` after you have flipped the arguments. 283 | 284 | **L.15** - We are implementing graded reverse lexicographic order in which when comparing monomials one starts looking at the components from right to left, hence we reverse both lists before passing them to the ``cList`` function. 285 | 286 | **L.16** 287 | 288 | * There are three base cases for the recursive function ``cList``. 289 | * If both lists are empty that means all corresponding elements of both lists gave ``EQ`` result in comparison (that is they were equal) in which case the two lists must be equal and so the result must clearly by ``EQ``. **Alternately** from a theoretical point where we don't need to concern ourselves with how we get there, in lexical order the comparison of two empty lists should always result in ``EQ``. 290 | * If the first list becomes empty while the second one is not it means that the second list has an entry which means that the first list is lexically lower and vice versa. 291 | 292 | **L.17** 293 | 294 | * For the case of two non-empty lists we compare the head of both lists using `cmp`. 295 | * If the result of the comparison of heads is unequal to `EQ` than the rules that goves the monoid `mappend` means that the second argument of `mappend` is NOT evaluated and the first argument is returned as the result. 296 | * If `cmp` evalautes to `EQ` then the second argument is evaluated which is simply a recursive call to cList. 297 | 298 | **L.18** - The `cmp` function is defined to compare elements which are inside a product. These have slightly different rules than for sums and this is why we need a dedicated function for this. Note that this uses ``compare`` recursively. 299 | 300 | **L.19** 301 | 302 | * We compare two exponents with the same degree. 303 | * The first step is to compare the outer degrees of the exponents, since the base of each could have a degree higher than one. Analogous to total degree the element with lower outer degree is lexically lower. 304 | * If the outer degrees of the two expressions is equals, and since we are in compare' whether the total degree is equal it means that the inner degrees are also equal. 305 | * Consequently we simply pass both inner expressions to compare' to get the comparison result. 306 | 307 | 308 | ## M. showActual 309 | 310 | *Method for showing the actual structure of an ``Expr`` by printing out how it is made up of the constituent Value Constructors. It is used for debugging.* 311 | 312 | **M.1** - The simplest pattern to deal with. If an expression is simply a ``Const`` we simply print out ``Const ``. Note the use of string concatenation using ``++`` and the use of the ``show`` function to convert the integer constant ``c`` in to its String representation. 313 | 314 | **M.2** - For ``Symbol`` we simply return the String it encapsulates. Note that we don't use ``show`` here because using it on Strings adds additional quotes around the object which we don't want. 315 | 316 | **M.3** - For ``Neg`` we use a parenthesis to indicate its bounds and we use recursion by calling ``showActual`` on the expression inside the ``Neg``. 317 | 318 | **M.4** 319 | 320 | * We output is bounded on either side by ``"Sum ["`` and ``"]"``. 321 | * The text in the middle is created by applying a ``foldl`` over the list of expressions inside the ``Sum``. 322 | * The ``foldl`` uses the ``foldListElement`` over the elements and starts with an empty String as the accumulator. 323 | * After the ``foldl`` we have a String which corresponds to a comma-separated list of String representations of elements. 324 | * Because of the way the ``foldListElement`` is implemented this String has an extraneous ``", "`` at the beginning. We get rid of it by using the ``drop 2`` function which removes the first two elements of the String. 325 | 326 | **M.5** 327 | 328 | * The purpose of this function is to be used in a ``foldl``. It is supposed to be mapped over a list of expressions converting them in to ``showActual`` Strings and then concatenating them separated by ``", "``. 329 | * To the accumulator we add first the String ", " and then the String representation of the current element (expression) by recursively calling ``showActual``. 330 | * Note: By adding ``", "`` before each element we end up with a String that has ``", "`` at the start but NOT at the end. We like it this way because removing elements from the start of a list is much easier (and more efficient) than from the end of a list (simply use ``drop 2``). 331 | 332 | 333 | ## N. ``Const`` Constructor 334 | 335 | *A function for creating and returning a ``Const`` object. 336 | 337 | **N.1** - The purpose of this function is to act as a constructor. We need a special function for this because we want to ensure that **ALL** ``Const`` objects **ONLY** contain positive integers. 338 | 339 | **N.2** 340 | 341 | * Note the use of ``fromIntegral`` to convert the ``Integral`` ``c`` to an ``Int`` which is what is required by the ``Const`` constructor by definition. 342 | * When using function composition the argument to the composition needs to be separated because composition has lower precedence than function application, hence the ``$`` before the argument ``c``. 343 | * Once again we need a ``$`` to separate ``abs.fromIntegral`` from ``Const`` so that everything on the RHS of ``Const`` is calculated first, before it is used as an argument by ``Const``. An equivalent construction would be ``Const . abs . fromIntegral $ c`` where we include the ``Const`` constructor in the function composition because the constructor is after all like any other function. The choice of which construct to use is a preference, in this case the readability of the code. 344 | 345 | 346 | 347 | ## O. Degree of (Polynomial) Expression 348 | 349 | **O.1** 350 | 351 | * This is classic Haskell. We write down what something is NOT how to calculate it. 352 | * In this case we use pattern-matching to define the degree for each Value Constructor. 353 | * Some of them are obvious such as the degree of a constant or a symbol by itself. 354 | * Others use recursion such as the degree of the negative of an expression is equal to that of the expression itself, hence the definition for ``Neg e`` 355 | 356 | **O.2** - A product of polynomial expressions has degree equal to the sum of the degrees of its constituent parts (by definition). So we ``map`` ``degree`` (recursively) over the list to get a corresponding list comprising of the degree of each element of ``xs`` and then we use ``sum`` to add all of these number up to get the final result. 357 | 358 | **O.3** - QuickCheck tests revealed that degree was being called with an empty sum causing an exception. We explicitly set the degree of an empty sum to be zero to avoid the exception. 359 | 360 | **O.4** 361 | 362 | * The degree of the sum of polynomials is the highest (maximum) of the degree of its parts. To calculate this we ``map`` ``degree`` over ``xs`` and then ``foldl1'`` the ``max`` function over it. 363 | * ``max`` is a binary function that returns the larger of its two arguments. 364 | * We want to implement ``max`` over the whole list so we naturally use a fold. In this case ``fold11`` because with it the first element of the list serves as an accumulator. At first I was mistakenly using ``foldl max 0`` with ``0`` as the initial accumulator but that would have failed for a list with entirely negative degree polynomials. With ``foldl1`` we are guaranteed to get the correct maximum value. ``foldl1'`` applies ``max`` successively over the elements of ``xs`` and keeps track of the maximum value to date. 365 | 366 | 367 | 368 | ## P. *import* statements 369 | 370 | **P.1** 371 | 372 | * The exponentiation function ``^`` that normally raises a ``Num`` object by an ``Integer`` to produce an ``Integer`` i.e. it has signature: ``(^) :: (Integral b, Num a) => a -> b -> a``. 373 | * We want to use ``^`` to implement exponentiation of ``Expr`` objects by integers. 374 | * The problem is that ``^`` is implemented in the ``Prelude`` and not in any type-class. Thus we can't make ``Expr`` and instance of some type-class to override ``^``. 375 | * Therefore to override ``^`` we must suppress the definition in the ``Prelude`` to which end we import ``Prelude`` while hiding the function ``(^)``. 376 | * With this in place we can now define ``^`` ourselves. 377 | 378 | **P.2** - We make a qualified import of Prelude which will allow us to access it by name and access its members using the ``.`` terminology. It will allow us to refer to the hidden/suppressed ``^`` as ``Prelude.^``. 379 | 380 | **P.3** - We import the ``mappend`` function so that we can combine ``Ordering`` (``LT``, ``GT`` and ``EQ``) in a specific fashion. 381 | 382 | 383 | 384 | ## Q. Exponentiation using ^ 385 | 386 | **Q.1** - Since we have hidden/suppressed ``Prelude.^`` we can give our construction of ``^`` any signature we want. In this case we keep it in line with the ``Exp`` value constructor since that is what we want the output of the function to be. So ``^`` takes an ``Expr`` and an ``Integral`` and returns an ``Expr``. 387 | 388 | **Q.2** - We use the ``exp_`` utility function to construct the exponent. This allows us to use pattern-matching to carry out the proper construction. 389 | 390 | 391 | 392 | ## R. Multiplying expressions (using ``prod_``) 393 | 394 | **R.1a** 395 | 396 | * ``prod_`` is a utility function for multiplying two expressions. It is analogous to ``exp_``. It in turn calls ``prod``` which implements the meat of the multiplication functionality. 397 | * The purpose of ``prod_`` is to implement the more abstract rules of multiplication before ``prod'`` gets in to the specifics. Since it has to call ``proc'`` eventually we can implement commutation here and so any rules defined here must have their symmetric counter parts defined explicitly here as well. 398 | 399 | **R.1b** 400 | 401 | * Our aim is to ensure that all the components of ``Prod`` (all the elements inside the ``Prod``) are **always** positive. 402 | * To represent an expression which consists of the product of simple terms (``Const`` and ``Symbol``) which is negative overall we encapsulate the ``Prod`` inside a ``Neg`` which is the outermost expression. There will be no ``Neg`` *inside* a ``Prod``. 403 | * To that end we define the behaviour of ``Neg`` expressions in multiplication first keeping the above requirement/assumption in mind. 404 | * The product of two ``Neg`` expressions is simply the positive product of the content of the ``Neg``s. 405 | * Multiplying a negative expression with a positive expression gives an overall negative product. 406 | * Note how this will automatically deal with the possibility of the multiplication of ``Const`` and ``Symbol`` objects. 407 | * Note that with the ``Neg`` conditions implemented at the top none of the following patterns will concern themselves with ``Neg``. 408 | 409 | **R.1c** 410 | 411 | * We implement the most basic identities for multiplication. 412 | * Multiplying **anything** by zero results in zero. Note the use of ``_`` to represent an arbitrary expression we do not wish to use in the following definition. 413 | * Multiplying **anything** by one results in the same thing being returned. We use pattern matching to name the matched expression ``e`` and simply return it as is. 414 | 415 | **R.2** 416 | 417 | * When two exponents are multiplied, if they have the same base we get an exponent with their powers added. If the bases are unequal we simply hand off computation to ``prod'``. 418 | * Note that because equality of the bases of the exponents has been checked here ``prod'`` doesn't need to worry about it. 419 | * This means that for all the expressions that can be inside an ``Exp`` (all the value constructors) we don't need patterns to match for equality of the base with the expression. By being abstract here, at the cost of explicit commutation patterns, we save on a larger number of pattern-matches later. 420 | 421 | **R.3** - Checks if an exponent is being multiplied by an expression which is equal to its base. 422 | 423 | **R.4** - Implements the commutation of (R.3) by switching the arguments and calling ``prod_`` recursively. Saves us from having to implement the same logic all over again. 424 | 425 | **R.5** 426 | 427 | * This rule really shows the power of Haskell, in particular recursion. 428 | * When multiplying two ``Prod``s together simply treat the first one as a list of expressions and multiply them successively to the second ``Prod`` building up the solution recursively. 429 | * This lets all of the above-defined rules do all the heavy-lifting and ensures that all of the necessary rules are met. 430 | * The first pattern matches the base case of the list of expressions containing a single element. 431 | * By construction a ``Prod`` can never be empty. Even a singleton ``Prod`` is nonsensical but we take it to be the same as the element itself. 432 | * The second pattern extracts the first element from the first ``Prod``, multiplies it with the second product ``p2`` using ``prod_``. 433 | * We then multiply this new ``Prod`` with a ``Prod`` consisting of the remaining list of elements ``es`` by calling ``prod_`` recursively. 434 | 435 | **R.6** 436 | 437 | * This pattern matches for an arbitrary expression multiplied by a product. 438 | * It implements the two general rules obeyed by multiplication. 439 | * One, if the expression exists inside the product the result is the product with that expression squared. 440 | * Two, if there exists an exponent inside the product which has the expression as a base then the result is the product with the exponent with an incremented power. 441 | 442 | **R.6a** 443 | 444 | * The result of ``match a ps`` is of type ``Maybe [Expr a]``. 445 | * When an element inside ``ps`` matches the expression ``a`` or an exponent of ``a`` ``match`` returns the new product list wrapped inside a Maybe. 446 | * If no match occurs it returns ``Nothing`` to indicate that the calculation failed. 447 | * Inside ``branch`` we use pattern-matching to deal with these two possibilities. 448 | * If the result is ``Nothing`` we simply pass the element and ``Prod`` to ``prod'`` to carry out the multiplication. 449 | * If the result is an ``[Expr a]`` wrapped inside a ``Just`` we use pattern-matching to extract the list and put it inside ``Prod``. 450 | 451 | **R.6b** - This is the base case of the recursion. It indicates that no matching element (expression itself or its exponent) was found so the calculation is a failure and we return ``Nothing`` to indicate this. 452 | 453 | **R.6c** 454 | 455 | * We use pattern-matching to extract the ``Exp`` element from the head of the list. 456 | * If the base of the exponent matches the expression ``c`` we replace the exponent with one with incremented power at the head of the list. 457 | * We wrap this updated product list inside a ``Just`` and return it. 458 | * If there is not match we use recursion. We want the current element ``ea`` at the head followed by the recursive result of ``match`` applied to the rest of the list ``es``. This is not straight-forward because the result of ``match`` is either a ``Just`` or a ``Nothing``. 459 | * We are in classic **functor** territory. 460 | * If the result of the recursive ``match`` is ``Nothing`` it means the recursion has failed and so the result from this append should also be ``Nothing`` 461 | * If the result of the recursive ``match`` is successful it will contain a list of expressions *inside* a ``Just``. So we have to concatenate ``ea`` with a list that is inside a context (``Just``). 462 | * This begs for the use of ``fmap``. We construct a concatenation function by currying ``:`` by making it ``(ea:)``. Normally applying ``(ea:)`` to a list of expressions will add ``ea`` at the head of the list. Using ``fmap`` we apply this function to the content of the ``Maybe``. 463 | * So ``fmap (ea:) Just [...]`` will add ``ea`` to the head of the list inside the ``Just`` 464 | * Additionally, since ``Maybe`` is a functor, ``fmap`` is aware of its context and so by construction ``fmap`` of any function over a ``Nothing`` gives a ``Nothing``. 465 | * Both are requirements are met. 466 | 467 | **R.6d** - The match for the expression is more general than the exponent so it has be placed lower in the list. This proceeds in complete analogy to (R.1k) 468 | 469 | **R.7** - Implements commutation for a ``Prod`` being multiplied with an arbitrary expression. We simply switch the arguments around because the patterns look for an arbitrary expression multiplied with a ``Prod``. 470 | 471 | **R.8** - Implements the abstract rule that when the same expression is multiplied by itself it is equivalent to raising the expression by the power 2. We use the ``exp_`` method to achieve this which in turn implements its own set of rules for constructing exponents, thereby guaranteeing proper construction. 472 | 473 | 474 | 475 | ## S. Exponentiation 476 | 477 | **S.1** 478 | 479 | * We define an intermediary function between ``(^)`` and ``exp'``. 480 | * Its purpose it to deal with the possibility of negative powers in an exponent without falling in to an infinite recursion. 481 | * When the power is negative we invert the exponent by creating a ``Frac`` with ``Const 1`` in the numerator and place the exponent in the denominator but with a positive exponent by sending the absolute value of ``p`` to the ``exp'`` call. 482 | * When the power is zero we return ``1``. This will in all likelihood shadow the rule ``exp` _ 0 = Const 1`` but we leave the latter in place for the sake of completeness. 483 | 484 | **S.2** 485 | 486 | * The first pattern we match is for a negative base. 487 | * We extract the expression within the ``Neg``. 488 | * If the power is even we realise that the negative part is turned to a positive ``(-1)^(2n) = 1`` so we simply return the expression inside the ``Neg`` exponentiated using a recursive call. 489 | * If the power is odd the result must be negative overall because ``(-1)^(2n+1) = -1`` and so we surround the recursive exponent with a final ``Neg``.. 490 | 491 | **S.3** 492 | 493 | * Note the use of ``Prelude.^`` to refer to the ``^`` inside ``Prelude`` which we hid when we imported ``Prelude``. Here is where the qualified import of ``Prelude`` comes in handy. 494 | * We overrode ``^`` to only work with the first argument being ``Expr a`` while here we want to raise in Int to an Int power for which we must use the function defined inside ``Prelude``. 495 | 496 | **S.4** - Exponentiation of a fraction is implemented simply as the exponentiation of its numerator and denominator separately. 497 | 498 | **S.5** - When our patterns are exhausted we simply exponent the expression (this can be ``Symbol``, ``Sum`` or ``Prod``). 499 | 500 | 501 | 502 | ## T. Multiplication using ``prod'`` 503 | 504 | **T.1** 505 | 506 | * ``prod'`` is a utility function for multiplying two expressions. It is called by ``prod_``. It is completely analogous to ``sum'`` with the same kind of pattern-matching implemented. 507 | * We use pattern matching to define the common multiplication scenarios. This will save us time because we won't have to simplify terms afterwards (and repeatedly). 508 | * ``prod'`` uses pattern matching to implement the rules for multiplying any expression with any other expression. 509 | * Multiplication is commutative ``A * B = B * A``. This is implemented by the catch-all pattern at the very bottom ``prod' a b = prod' b a``. 510 | * This means all possible combinations of expressions (value constructors) must be explicitly stated. 511 | * For very combination the symmetric reversed combination is covered by commutation and need not be specified. 512 | * We start with ``Const`` and specify rules for multiplying it with each kind of value constructor (including ``Const`` itself). 513 | * This means that when we move on to the rules for ``Symbol`` we won't need to deal with the case of ``Symbol * Const`` because that will be covered here. 514 | * As we work our way through all the constructors we will be left with less and less rules to write for each. 515 | * Every value constructor must have a rule for multiplying with its own data type. 516 | * Note how we use exhaustive rules for ``Neg`` in ``prod_`` to take care of all of them in a simple fashion. 517 | 518 | **T.2** 519 | 520 | * We do a pattern match for a ``Const`` being multiplied by any other expression. 521 | * If the pattern matches we pass the arguments to the ``prod_c`` utility function which is dedicated only to the multiplication of ``Const`` with other expressions. 522 | * This has two advantages. One, it makes the code in ``prod'`` more concise because we don't place all the ``Const`` patterns here. 523 | * Two, the code becomes more efficient because if the first expression in ``prod'`` is NOT a ``Const`` then it fails this one pattern here and moves on, without having to fail against every pattern that is described in ``prod_c``. Thus branching is implemented within the patterns. 524 | 525 | 526 | 527 | ## U. Multiplying by ``Const`` 528 | 529 | **U.1a** - This is a utility function for multiplying a ``Const`` expression with other expressions. 530 | 531 | **U.1b** - This pattern matches two positive constants being multiplied and the result is a ``Const`` and not a ``Prod``. This ensures that ``Const`` multiplication is automatically simplified. 532 | 533 | **U.2** 534 | 535 | * Note that the ``Prod`` is constructed with the ``Const`` first and the ``Symbol`` afterwards. This saves on simplification. 536 | * The case of ``prod' (Symbol _) (Const _)`` is taken care of by the commutation implemented in the last pattern which sends that case to this pattern by switching the arguments around and once again ensuring that the ``Const`` comes first in the function arguments. 537 | 538 | **U.3** - This is based on the assumption that no exponent will ever have a ``Const`` in its base because such exponents will already have been converted in to the resulting ``Const`` values. 539 | 540 | 541 | *Multiplying a ``Const`` with a ``Prod`` 542 | 543 | **U.5** 544 | 545 | * This pattern is based on the assumption that every ``Prod`` has the same construction format: ``[, ...]`` where first element is a ``Const`` if one is present. 546 | * Note that the case of the product and/or the constant being negative is handled by the rules defined for abstract ``Neg`` objects being multiplied which use recursion. 547 | * Instead of the where-pattern we could have gone with multiple patterns but they would have looked cumbersome. This way not only is the logic cleaner but the whole category of ``Const * Prod`` can be rejected in one pattern test rather than having multiple pattern tests. 548 | 549 | **U.5a** - This pattern corresponds to the first element (of the list of elements inside the ``Prod``) being a ``Const``. We multiply the constant values together and append it to the rest of the list of expressions ``es`` which we got by pattern-matching. 550 | 551 | **U.5b** - If the first element of the list is not a ``Const`` we simply append the ``Const`` in front of the entire list. 552 | 553 | **U.6** - ``prod_c`` is **only** intended for ``Const`` patterns. So the catch-all pattern at the bottom raises an error which will inform us if ``prod_c`` is every used incorrectly. 554 | 555 | 556 | 557 | ## V. Multiplying by ``Symbol`` 558 | 559 | **V.1** 560 | 561 | * By performing ``Symbol`` pattern matching in another function altogether all calls where ``Symbol`` comes first, even if it is before ``Const``, is captured by the ``prod_s`` function and execution never falls to the catch-all pattern at the end of ``prod'`` which implements commutation. 562 | * Therefore we must explicitly provide the rule for ``Symbol x Const``. We simply pass this on to the ``prod_c`` where the rule has already been defined. We are using commutation to simplify our task. 563 | 564 | **V.2** 565 | 566 | * Note the use of both as-patterns and pattern-matching within the ``Symbol`` construct to gain access to both the ``Symbol``s as a whole and their contents. 567 | * We use ``a`` and ``b``, the ``String``s inside the ``Symbol``s to determine whether they match or not and if not the order between them. 568 | * We use guards to look at the various scenarios. 569 | * We explicitly implement the ordering of symbols inside the product. 570 | * Note that by accessing the ``String`` inside each ``Symbol`` we carry out our comparison not on the ``Symbol`` as a whole but on its contents. Thus NO reference is made to the ``compare`` function defined when ``Expr`` was made an instance of the ``Ord`` class. 571 | * We did not explicitly mention the scenario ``a == b`` since that is taken care of in ``prod_`` 572 | 573 | **V.3** 574 | 575 | * Deals with the explicit case of a ``Symbol`` being multiplied by an ``Exp`` whose base is a ``Symbol`` and NOT a general ``Expr``. 576 | * We order the ``Symbol`` and the ``Exp (Symbol _)`` based on the lexical order of the two symbols. 577 | 578 | **V.4** - This pattern deals with the possibility that a general expression can form the base of an exponent, e.g. ``x * (y + 2)^3``. In this case we have implemented the ordering that the ``Symbol`` always appearing before the ``Exp``. 579 | 580 | **V.5** - Analogous to (U.5) we use ``where`` to specify a function with pattern-matching to handle the insertion of a ``Symbol`` in to an existing ``Product``. 581 | 582 | **V.6** - Any symbol must be inserted after the ``Const`` part. We place the ``Const`` in the front and then use recursion to insert the ``Symbol`` in the remaining list of expressions. 583 | 584 | **V.7** 585 | 586 | * An excellent example of mixing guards and pattern-matching. 587 | * We use pattern-matching to identify the scenario where the first element in the list is ``Symbol``. Then we use guards to compare the two Symbols, the one passed in and the one at the head of the list. 588 | * The first guard deals with the possibility of the incoming ``Symbol`` String being lexically smaller than the head ``Symbol`` String. In this case we simply place the incoming (smaller) ``Symbol`` first, followed by the head ``Symbol``, followed by the rest of the list and the recursion terminates. 589 | * Otherwise technically corresponds only to ``b > c`` it means the incoming ``Symbol`` is lexically higher than the current symbol and so we place ``sc`` first and then use recursion to insert ``sa`` in the remaining list. 590 | * The ``b == c`` case is handled generally in ``prod_``. 591 | 592 | **V.8** - Analogous to (V.6) but with a focus on multiplying symbols with possibly their own exponents. 593 | 594 | **V.9** 595 | 596 | * Catch-all pattern. If none of the patterns above match this catch-all assumes that the list of expressions is bizarre enough to warrant simply plopping the symbol ahead of it. 597 | * Note that this pattern will also match the scenario where ``es`` is ``[]`` (empty, possible in a recursion scenario) which corresponds to the symbol ending up at the end of the list. 598 | 599 | 600 | 601 | ## W. Multiplying by ``Exp`` 602 | 603 | 604 | **W.1** - In analogy to (V.1) we provide the explicit rules for ``Exp`` multiplied by ``Const`` and ``Symbol`` by punting the calculation to the rules defined there. We have to explicit here because we moved all ``Exp`` matches to a separate function and so the pattern is matched even when we are multiplying by ``Const`` and ``Symbol``. 605 | 606 | **W.2** 607 | 608 | * This deals with the multiplication of two exponents both of which have pure ``Symbol``s as their base. 609 | * ``prod_`` deals with the possibility of both symbols being the same. 610 | * Since the symbols are guaranteed to be different here, we use their lexical order to order the exponents in the Product 611 | 612 | **W.3** 613 | 614 | * We implement ordering for when an exp with a symbol base is multiplied with a general exp. 615 | * We have to provide the reversed pattern explicitly because both constructors are ``Exp``. There is no reason for this to go all the way down to the reversal pattern. It is caught by the patttern in ``prod_`` and assigned to ``prod_e`` where we have to deal with it. 616 | 617 | **W.4** - We specify a sorting order here where a simple exponent of a single symbol raised to a power precedes a ``Sum`` while a non-symbol (general) expression raised to a power comes after a ``Sum``. The ordering is rather arbitrary. 618 | 619 | **W.5** - The possibility of there being a sum in the exponent which is the same as the sum being multiplied with is handled earlier in ``prod_`` so is ignored here. For a general expression inside the ``Exp`` we place the ``Sum`` first. Once again the ordering is rather arbitrary. 620 | 621 | **W.6** 622 | 623 | * Rules for multiplying an exponent of a symbol with a general product. 624 | * This is completely analogous to the rules for multiplying a symbol with a general product. 625 | * Note how we pass in the String inside the ``Symbol`` and the associated power from the exponent to the ``mul`` function defined using ``where`` syntax. 626 | * Note the catch-all pattern at the bottom. It declares that an exponent of a symbol has higher precedence than all other expressions except for the ones for which rules/patterns are defined above. 627 | 628 | **W.7** 629 | 630 | * Rules for multiplying an exponent with a general (non-symbol) base with a general product. 631 | * Note that there is no catch-all at the bottom so we have a base case at the top dealing with the possibility of any empty Product list (due to recursion) in which case we simply return the exponent as a singleton list. 632 | * We match for the head being exponent first so that it isn't hidden by the pattern that follows (which is more general - as a rule patterns move from specific to general). 633 | * Note the *otherwise* case where the exponent is always sent to ``mul`` recursively when ``b != c``. This opens up the possibility of the list ``es`` becoming empty which is why we need the base case at the top. 634 | * These patterns allow for general expressions inside the exponent to be multiplied with the same expression or an exponent of it in the Product. This is a desired simplification we want during expression construction (when we are multiplying expressions). 635 | 636 | 637 | 638 | ## Y. Multiplying by ``Sum`` 639 | 640 | **Y.1** - The order of two sum expressions inside a product is based on their lexical order given by ``compare``. The possibility of the two sums being equal is handled earlier by ``prod_``. 641 | 642 | **Y.2** - The purpose of ``mul`` is to place the Sum ``sa`` inside the list of expressions that comprise the product. 643 | 644 | **Y.3** - Recursive base case. If we arrive at the end of the list then this is where the Sum must be placed. 645 | 646 | **Y.4** - The rules we are implementing require that a Sum be placed after all Symbols and Exponent of Symbols. 647 | 648 | **Y.5** 649 | 650 | * When comparing with a non-symbol(-exponent) element we find out whether the Sum is lexically "greater" than the element in question. This is done according to rules that are slightly different from that used for constructing Sums and so we use the ``cmp_prod`` function. If the Sum is "greater" than the element it appears first in the list. 651 | * Equality is considered an error since that possibility should have been taken care off by an earlier pattern match. 652 | 653 | **Y.6** 654 | 655 | * The special rules for constructing Products require that (when not comparing with symbols or exponent there-of - handled earlier) if the degree of the Sum is lower than that of the second element it appears first and vice versa. This is the opposite of how ``compare`` (used in construction of Sums) behaves. 656 | * If the degrees are equal we fall back to using ``compare``. 657 | 658 | 659 | ## Z. Adding expressions 660 | 661 | **Z.1** 662 | 663 | * A smart constructor from creating a ``Sum`` from a list of expressions. A never of utility function used for defining addition relations create a list of elements. These can end up being empty or singleton so we need a smart constructor to handle these possibilities. 664 | * An empty list inside a ``Sum`` can be created when negative expressions cancel positive ones. In this case it is obvious that the result should be zero. 665 | * Similarly cancellation can create lists with only a single expression in which case the sum should be equal to just that simple expression. 666 | 667 | **Z.2** - Implement the basic rules of addition. Adding 0 to any expression leaves it unchanged. We also implement the commutation. 668 | 669 | **Z.3** 670 | 671 | * Since `Neg` is intrinsically bound up with the concept of addition we deal with some of the ``Neg`` relations up front. 672 | * When one adds to expressions both inside ``Neg`` we add the inner expressions and place the result in a ``Neg`` context. 673 | * For adding an expression with a ``Neg`` expression (the former is guaranteed to not be a ``Neg`` because of the pattern above) we implement commutation on the pattern. 674 | * This means that all subsequent patterns **only** need to match with ``Neg`` in the **first** argument (the first one is handled using commutation here). 675 | 676 | **Z.4** 677 | 678 | * When adding something with a ``Neg`` ``Sum`` we first test if the expression withing the ``Neg`` is equal to the second expression in which case the answer should be zero. 679 | * Otherwise we map ``Neg`` over all of the elements converting it in to a ``Sum`` over ``Neg`` elements before we carry out the addition. 680 | 681 | **Z.5** 682 | 683 | * The first pattern is the base case of recursion when we end up with a sum containing a singleton list. By the logic from ``sum_list`` this is equal to the element/expression itself. So we extract the expression from inside the singleton and pass it recursively to ``sum_``. 684 | * When adding two ``Sum`` we first check for the possibility of the two sums being equal in which case we replace them by twice the expression. 685 | * Otherwise we add one element at a time from the first list in to the second list. 686 | * Note how we use recursion to add the remaining first list with the now extended second list until the first list becomes empty and the second list contains the result of the completed addition. 687 | 688 | **Z.6** 689 | 690 | * This is analogous to how we multiply an arbitrary expression with a ``Prod``. 691 | * We are looking for the possibility that the expression or its negative exists in the ``Sum`` already in which case we have to cancel or combine the two together. 692 | * We iterate over the list looking for a possible match and use a ``Maybe`` to deal with the possibilit of failure (i.e. no match is found). 693 | 694 | **Z.7** - Commutation pattern corresponding to (Z.6). 695 | 696 | **Z.8** - We test fot the possibility of a general expression being added to its own negative which should result in a zero. 697 | 698 | **Z.9** - When handling two expressions which are not sums we test for equality. If they are equal we replace them with twice their value otherwise we pass the arguments to the ``sum'`` utility function for further processing. 699 | 700 | **Z.10** - When adding a ``Const`` or ``Neg (Const _)`` with another expression we simply pass the arguments to the utility function ``sum_c`` which is dedicated to the addition of (negative) ``Const`` with any general expression. 701 | 702 | **Z.11** - With the special case of Const (because of compacting), Prod because of factoring and Sum (because the operation is addition itself) taken care of we can lump all the rest in to one function ``sum_x`` which uses the ``compare`` function to order the elements in the desired lexical order. 703 | 704 | 705 | 706 | ## AA. Adding ``Const`` using ``sum_c`` 707 | 708 | **AA.1** 709 | 710 | * When adding a constant with a ``Sum`` our aim is to find the possible (negative) constant inside the sum and combine the two inside the sum. 711 | * Note that we used a general pattern for the first argument and did not specify ``(Const _)``. This is because had we done so we would have had to define an exactly analogous pattern for ``Neg (Const`` which would be wasteful. 712 | * The down-side to our desire to not have repetition in the patterns and functions is that we must ensure that this utility function ``sum_c`` is **only** called with a (negative) constant and nothing else i.e. it is only called when pattern-matching against these constructors. Since ``sum_c`` is only called from within ``sum'`` for just such a case this is guaranteed. 713 | 714 | **AA.2** - Base case of the recursion. Note that if this singleton is the final result of ``add` then the function ``sum_list`` will unpack it in to the single expression inside. 715 | 716 | **AA.3** 717 | 718 | * If the constant is lexically higher than the head we simply place the constant in that location. 719 | * If the constant is lexically lower than the head we place the head in the position and recursively call ``add`` again with the tail of the list since the constant may need to be located further down the list. 720 | * When we have lexical equality it is guaranteed, from the implementation of the ``compare`` function, that the head element is also a (negative) constant (has degree zero). In this case we simply add the two elements to get a unified ``Const``. 721 | * We make a recursive call to ``add`` with the newly created ``Const`` to deal with the possibility of the addition resulting in a ``Const 0``. The recursive call to ``add`` will remove the zero if that is the case. 722 | 723 | **AA.4** - If the second element is neither a constant nor a sum we simply create a ``Sum`` from just the second argument (singleton) and then recursively call ``sum_c`` on it. This works because ``sum_c`` with ``Sum`` as the second argument implements all of the rules for constructing a ``Sum`` with the correct lexical order. 724 | 725 | 726 | 727 | ## AB. Adding non-``Const/Sum/Prod`` expressions using ``sum_x`` 728 | 729 | **AB.1** - If the second argument is a ``Const`` we simply pass the arguments to ``sum_c``. 730 | 731 | **AB.2** 732 | 733 | * This is becoming a common pattern when dealing with adding expressions to ``Sum`` or multiplying expressions to ``Prod``. 734 | * The first pattern is the base case for recursion. 735 | * In the second pattern we extract the head of the list for further processing. 736 | * We use the ``compare`` function to perform lexical ordering. 737 | * We use a ``case`` expression to read the result of the ``compare`` and branch execution. 738 | * Note that ``compare`` via the result ``EQ`` provides a mechanism for collecting equal symbols together. 739 | * Note the recursive call to ``add`` when the incoming expression is lexically higher than the head. We pass over the head and continue the process. 740 | 741 | **AB.3** 742 | 743 | * The case of the two expressions being equal is dealt with by ``sum_`` when it deals with the equality of general expressions. 744 | * For unequal expressions we simply place them in graded reverse lexical order inside the ``Sum``. 745 | * The ``compare`` function was written for the express purpose of placing general expressions in lexical order (firstly by using their degree). 746 | * ``compare`` returns an ``Ordering`` so we use ``case`` on the result to branch execution. 747 | * Note that ``EQ`` here corresponds to the result of ``compare``. Two expressions which are unequal should never have ``EQ`` for the result of ``compare`` thus this is explicitly declared to be an error. 748 | 749 | 750 | 751 | 752 | ## AC. Adding ``Prod`` using ``sum_p`` 753 | 754 | **AC.1** - We are adding two (possibly negative) ``Prod`` expressions together. We are looking for the possibility of them both being a constant (possibly 1) multiplied by the **same** remaining expression. 755 | 756 | **AC.2** - We use pattern-matching to ensure that the second expression is also a (negative) product in which case we hand over the calculation to `sum_pc`. Note that the first argument is guaranteed to be a (negative) product based on how ``sum_p`` is called from ``sum_`` only after such a pattern-match has occurred. 757 | 758 | **AC.3** - If the second argument is not a product we simply pass calculation on to ``sum_x`` which is designed for general (non-special) addition of expressions. 759 | 760 | **AC.4** - The purpose of ``split`` is to take a (negative) product and return a tuple consisting of a constant (which can be negative or even ``1``) and the rest of the list of expressions inside the product. The output is used by the `add` function defined below it. 761 | 762 | **AC.5** 763 | 764 | * First we check that the two lists ``as`` and ``bs`` match. 765 | * If they do not the entire exercise is futile so we simply send the reconstituted products to ``sum_x``. 766 | * If they do match we add the constants together and multiply it with the rest of the product. By using ``*`` here we guarantee that all the edge cases of ``0``, ``1`` and negative constants will be looked after automatically. 767 | 768 | 769 | 770 | ## AD. Constructing ``Frac`` 771 | 772 | **AD.1** - High level matching of numerator and denominator. If the two match we simply cancel them out and return 1. Otherwise we use ``frac'`` to further probe the possibility of cancellation. 773 | 774 | **AD.2** 775 | 776 | - We are dividing a ``Prod`` by a non-``Prod`` expression and we are looking for the possibility of cancellation. 777 | - To check for cancellation we recurse over the list of expressions inside the ``Prod`` and look for a possible cancellation using ``frac_`` function which is designed for this purpose. It tries all possible cancellations and returns a ``Nothing`` if unsuccessful. 778 | - To that end we use the ``divide`` function defined after ``where``. 779 | - It uses two lists and the denominator. 780 | - The first list contains all of the elements that have already been tried and for which no cancellation occured. 781 | - The second list contains all of the elements that remain. 782 | 783 | **AD.3** 784 | 785 | - This is the base case where the second list is empty. 786 | - If the second list becomes empty it means no cancellation was possible and so we construct the uncancelled fraction. 787 | - We use ``reverse`` on ``es`` because the way the elements are collected in ``es`` is in reverse order. The first element from ``xs`` is put in ``es``, then the second one is added using ``:`` so that it is now in the order 2:1:[] rather than 1:2:[]. 788 | 789 | **AD.4** 790 | 791 | - This is the general (non-base case) and it in turn uses the ``branch`` function to carry out its task. 792 | - We pass to branch the processed and unprocessed list of expressions ``es`` and ``xs`` respectively along with the current element ``x`` and the denominator ``d``. 793 | - In addition the last argument is constructed by calling ``frac_`` on ``x`` and ``d``. 794 | - If the call to ``frac_`` returns ``Nothing`` it means no simplification was possible and so we append ``x`` to ``es`` (note how the element is added in reverse order that is to the start of ``es`` and not its end) and pass the remaining list ``xs`` in a recursive call to ``divide`` 795 | - If ``frac_`` returns a ``Just e`` then the simplification was successful and we return a ``Prod`` consisting of the simplified term multipled by ``es`` and ``xs``. 796 | - Since ``es`` was constructed in a reverse fashion we use ``reverse`` to put it right end up before we stitch it with ``xs``. 797 | 798 | **AD.5** - For the case of a non-``Prod`` expression being divided by a ``Prod`` we use the ``rec`` function to flip it around and use the ``Prod`` divided by non-``Prod`` function defined above. 799 | 800 | **AD.6** - This is the catch-all at the bottom which is used for all patterns that do not match the ones defined above for ``frac'``. For these we use ``frac_`` to attempt a simplification. If it works the simplified expression is returned otherwise an explicit ``Frac`` is constructed. 801 | 802 | **AD.7** 803 | 804 | * ``frac_`` expects two non-``Prod`` elements and attempts to simplify them during division. 805 | * It uses pattern-matching to handle different cases. 806 | * If a simplification is performed the result is returned wrapped inside a ``Just`` context. 807 | * Otherwise a ``Nothing`` is returned to indicate that no simplification was possible. 808 | * It checks for one or both expressions being exponents and finally equality between elements. 809 | * ``Prod`` in either numerator and/or denominator is handled in an element wise fashion where ``frac_`` is called for each element in a recursive fashion. 810 | 811 | ## Debugging 812 | 813 | ### ``trace`` 814 | 815 | Source: *https://wiki.haskell.org/Debugging* 816 | 817 | ``trace`` is an extremely useful function for debugging Haskell code and is invaluable during development. It should however never be left in production code. 818 | 819 | The signature of ``trace`` is ``String -> a -> a``. You pass it a ``String`` which is usually a debugging message (sort of a ``printf``) and the actual value you want the function to return and ``trace`` will print the message and then return the value. 820 | 821 | Basically it allows us to print a message (as a side-effect) without altering the actual purpose of our code. Invaluable. 822 | 823 | To use ``trace`` one must import it as follows: ``import Debug.Trace(trace)`` which only imports this one function from the ``Debug.Trace`` module. 824 | 825 | A point to note is that any function that uses ``trace`` will probably need access to the ``Show`` type-class so ``Show a`` may have to be added as a type constraint to the calling function. 826 | --------------------------------------------------------------------------------