├── LICENSE.md ├── README.md ├── assets └── gidti-cover-front-back.pdf └── manuscript ├── Book.txt ├── Sample.txt ├── about-author.md ├── appendices.md ├── chapter1.md ├── chapter2.md ├── chapter3.md ├── chapter4.md ├── chapter5.md ├── conclusion.md ├── further-reading.md ├── images └── title_page.jpg ├── introduction.md └── preface-and-acknowledgments.md /LICENSE.md: -------------------------------------------------------------------------------- 1 | This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Introduction to Dependent Types with Idris 2 | ------------------------------------------ 3 | Welcome to the Git repository of my first self-published book (later re-published with Apress). You can access the book homepage at https://leanpub.com/gidti. 4 | 5 | Feel free to make contributions to it by either filing an issue or a pull request. As a contributor, you can e-mail me if you want to be included in the book. 6 | 7 | The book is dedicated to my wife Dijana, and our kids. 8 | 9 | ISBN: 978-1484292587 10 | -------------------------------------------------------------------------------- /assets/gidti-cover-front-back.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bor0/gidti/bc66446911af47fc24d3930f8514b68c08164545/assets/gidti-cover-front-back.pdf -------------------------------------------------------------------------------- /manuscript/Book.txt: -------------------------------------------------------------------------------- 1 | preface-and-acknowledgments.md 2 | introduction.md 3 | chapter1.md 4 | chapter2.md 5 | chapter3.md 6 | chapter4.md 7 | chapter5.md 8 | conclusion.md 9 | further-reading.md 10 | appendices.md 11 | about-author.md 12 | -------------------------------------------------------------------------------- /manuscript/Sample.txt: -------------------------------------------------------------------------------- 1 | preface-and-acknowledgments.md 2 | introduction.md 3 | chapter1.md 4 | chapter4.md 5 | further-reading.md 6 | about-author.md 7 | -------------------------------------------------------------------------------- /manuscript/about-author.md: -------------------------------------------------------------------------------- 1 | # About the author 2 | 3 | Boro Sitnikovski has over 10 years of experience working professionally as a Software Engineer. He started programming using the Assembly programming language on an Intel x86 at the age of 10. While in high school, he won several prizes for competitive programming, varying from fourth, third, and first place. 4 | 5 | He is an Informatics graduate; his Bachelor's thesis was titled "Programming in Haskell using algebraic data structures", and his Master's thesis was titled "Formal verification of Instruction Sets in Virtual Machines". He has also published papers on software verification. Other research interests include programming languages, mathematics, logic, algorithms, and writing correct software. 6 | 7 | He is a strong believer in the open-source philosophy and contributes to various open-source projects. 8 | 9 | In his spare time, he enjoys time with his family. 10 | -------------------------------------------------------------------------------- /manuscript/appendices.md: -------------------------------------------------------------------------------- 1 | # Appendices 2 | 3 | ## Appendix A: Writing a simple type checker in Haskell 4 | 5 | This appendix provides a short introduction to the design of type checkers. It is based on the examples of (and may serve as a good introduction to) the book Types and Programming Languages. 6 | 7 | ### Evaluator 8 | 9 | *Syntax*: The syntax per Backus-Naur form is defined as: 10 | 11 | ```text 12 | ::= | | If Then Else | 13 | ::= T | F | IsZero 14 | ::= O 15 | ::= Succ | Pred 16 | ``` 17 | 18 | For simplicity we represent all of them in a single `Term`: 19 | 20 | ```haskell 21 | data Term = 22 | T 23 | | F 24 | | O 25 | | IfThenElse Term Term Term 26 | | Succ Term 27 | | Pred Term 28 | | IsZero Term 29 | deriving (Show, Eq) 30 | ``` 31 | 32 | *Rules of inference* 33 | 34 | | Name | Rule | 35 | | ------------ | ---- | 36 | | E-IfTrue | {$$}\frac{v_2 \to v_2'}{\text{If T} \text{ Then } v_2 \text{ Else } v_3 \to v_2'}{/$$} | 37 | | E-IfFalse | {$$}\frac{v_3 \to v_3'}{\text{If F} \text{ Then } v_2 \text{ Else } v_3 \to v_3'}{/$$} | 38 | | E-If | {$$}\frac{v_1 \to v_1'}{\text{If } v_1 \text{ Then } v_2 \text{ Else } v_3 \to \text{If } v_1' \text{ Then } v_2 \text{ Else } v_3}{/$$} | 39 | | E-Succ | {$$}\frac{v_1 \to v_1'}{\text{Succ }v_1 \to \text{ Succ } v_1'}{/$$} | 40 | | E-PredZero | {$$}\frac{}{\text{Pred O} \to \text{O}}{/$$} | 41 | | E-PredSucc | {$$}\frac{}{\text{Pred(Succ } v \text {)} \to v}{/$$} | 42 | | E-Pred | {$$}\frac{v \to v'}{\text{Pred }v \to \text{ Pred } v'}{/$$} | 43 | | E-IszeroZero | {$$}\frac{}{\text{IsZero O} \to \text{T}}{/$$} | 44 | | E-IszeroSucc | {$$}\frac{}{\text{IsZero(Succ } v \text {)} \to \text{F}}{/$$} | 45 | | E-IsZero | {$$}\frac{v \to v'}{\text{IsZero }v \to \text{ IsZero } v'}{/$$} | 46 | | E-Zero | {$$}\frac{}{O}{/$$} | 47 | | E-True | {$$}\frac{}{T}{/$$} | 48 | | E-False | {$$}\frac{}{F}{/$$} | 49 | 50 | Recall that {$$}\frac{x}{y}{/$$} can be thought of as the implication {$$}x \to y{/$$} at the metalanguage level, where the actual arrow {$$}\to{/$$} is the implication at the object level (in this case, simply `eval`). 51 | 52 | Given these rules, we will reduce terms by pattern matching on them. Implementation in Haskell is mostly "copy-paste" according to the rules: 53 | 54 | ```haskell 55 | eval :: Term -> Term 56 | eval (IfThenElse T v2 _) = v2 57 | eval (IfThenElse F _ v3) = v3 58 | eval (IfThenElse v1 v2 v3) = let v1' = eval v1 in IfThenElse v1' v2 v3 59 | eval (Succ v1) = let v1' = eval v1 in Succ v1' 60 | eval (Pred O) = O 61 | eval (Pred (Succ v)) = v 62 | eval (Pred v) = let v' = eval v in Pred v' 63 | eval (IsZero O) = T 64 | eval (IsZero (Succ t)) = F 65 | eval (IsZero v) = let v' = eval v in IsZero v' 66 | eval T = T 67 | eval F = F 68 | eval O = O 69 | eval _ = error "No rule applies" 70 | ``` 71 | 72 | As an example, evaluating `eval $ Pred $ Succ $ Pred O` corresponds to the following inference rules: 73 | 74 | ```text 75 | ----------- E-PredZero 76 | pred O -> O 77 | ----------------------- E-Succ 78 | succ (pred O) -> succ O 79 | ------------------------------------- E-Pred 80 | pred (succ (pred O)) -> pred (succ O) 81 | ``` 82 | 83 | ### Type checker 84 | 85 | *Syntax*: In addition to the previous syntax, we create a new one for types: 86 | 87 | ```text 88 | ::= Bool | Nat 89 | ``` 90 | 91 | In Haskell: 92 | 93 | ```haskell 94 | data Type = TBool | TNat 95 | ``` 96 | 97 | *Rules of inference*: Getting a type of a term expects a term, and either returns an error or the type derived: 98 | 99 | ```haskell 100 | typeOf :: Term -> Either String Type 101 | ``` 102 | 103 | | Name | Rule | 104 | | -------- | ---- | 105 | | T-True | {$$}\frac{}{\text{T : TBool}}{/$$} | 106 | | T-False | {$$}\frac{}{\text{F : TBool}}{/$$} | 107 | | T-Zero | {$$}\frac{}{\text{O : TNat}}{/$$} | 108 | | T-If | {$$}\frac{t_1\text{ : Bool}, t_2\text{ : }T, t_3\text{ : }T}{\text{If }t_1 \text{ Then } t_2 \text{ Else } t_3\text{ : }T}{/$$} | 109 | | T-Succ | {$$}\frac{t\text{ : TNat }}{\text{Succ } t \text{ : TNat}}{/$$} | 110 | | T-Pred | {$$}\frac{t\text{ : TNat }}{\text{Pred } t \text{ : TNat}}{/$$} | 111 | | T-IsZero | {$$}\frac{t\text{ : TNat }}{\text{IsZero } t \text{ : TBool}}{/$$} | 112 | 113 | Code in Haskell: 114 | 115 | ```haskell 116 | typeOf T = Right TBool 117 | typeOf F = Right TBool 118 | typeOf O = Right TNat 119 | typeOf (IfThenElse t1 t2 t3) = 120 | case typeOf t1 of 121 | Right TBool -> 122 | let t2' = typeOf t2 123 | t3' = typeOf t3 in 124 | if t2' == t3' 125 | then t2' 126 | else Left "Types mismatch" 127 | _ -> Left "Unsupported type for IfThenElse" 128 | typeOf (Succ k) = 129 | case typeOf k of 130 | Right TNat -> Right TNat 131 | _ -> Left "Unsupported type for Succ" 132 | typeOf (Pred k) = 133 | case typeOf k of 134 | Right TNat -> Right TNat 135 | _ -> Left "Unsupported type for Pred" 136 | typeOf (IsZero k) = 137 | case typeOf k of 138 | Right TNat -> Right TBool 139 | _ -> Left "Unsupported type for IsZero" 140 | ``` 141 | 142 | Going back to the previous example, we can now "safely" evaluate (by type checking first), depending on the type check results. 143 | 144 | ### Environments 145 | 146 | Our simple language supports evaluation and type checking but does not allow for defining constants. To do that, we will need some kind of an environment that will hold information about constants. 147 | 148 | ```haskell 149 | type TyEnv = [(String, Type)] -- Type env 150 | type TeEnv = [(String, Term)] -- Term env 151 | ``` 152 | 153 | We also extend our data type to contain `TVar` for defining variables, and meanwhile also introduce the `Let ... in ...` syntax: 154 | 155 | ```haskell 156 | data Term = 157 | ... 158 | | TVar String 159 | | Let String Term Term 160 | ``` 161 | 162 | Here are the rules for variables: 163 | 164 | | Name | Rule | 165 | | ---------------- | ---- | 166 | | Add binding | {$$}\frac{\Gamma, a \text{ : }T}{\Gamma \vdash a \text{ : }T}{/$$} | 167 | | Retrieve binding | {$$}\frac{a \text{ : }T \in \Gamma}{\Gamma \vdash a \text{ : }T}{/$$} | 168 | 169 | Haskell definitions: 170 | 171 | ```haskell 172 | addType :: String -> Type -> TyEnv -> TyEnv 173 | addType varname b env = (varname, b) : env 174 | 175 | getTypeFromEnv :: TyEnv -> String -> Maybe Type 176 | getTypeFromEnv [] _ = Nothing 177 | getTypeFromEnv ((varname', b) : env) varname = 178 | if varname' == varname then Just b else getTypeFromEnv env varname 179 | ``` 180 | 181 | We have the same exact functions for terms: 182 | 183 | ```haskell 184 | addTerm :: String -> Term -> TeEnv -> TeEnv 185 | getTermFromEnv :: TeEnv -> String -> Maybe Term 186 | ``` 187 | 188 | *Rules of inference (evaluator)*: `eval'` is the same as `eval`, with the following additions: 189 | 190 | 1. New parameter (the environment) to support retrieval of values for constants 191 | 2. Pattern matching for the new `Let ... in ...` syntax 192 | 193 | ```haskell 194 | eval' :: TeEnv -> Term -> Term 195 | eval' env (TVar v) = case getTermFromEnv env v of 196 | Just ty -> ty 197 | _ -> error "No var found in env" 198 | eval' env (Let v t t') = eval' (addTerm v (eval' env t) env) t' 199 | ``` 200 | 201 | We will modify `IfThenElse` slightly to allow for evaluating variables: 202 | 203 | ```haskell 204 | eval' env (IfThenElse T t2 t3) = eval' env t2 205 | eval' env (IfThenElse F t2 t3) = eval' env t3 206 | eval' env (IfThenElse t1 t2 t3) = 207 | let t' = eval' env t1 in IfThenElse t' t2 t3 208 | ``` 209 | 210 | The remaining definitions can be copy-pasted. 211 | 212 | *Rules of inference (type checker)*: `typeOf'` is the same as `typeOf`, with the only addition to support `env` (for retrieval of types for constants in an env) and the new let syntax. 213 | 214 | ```haskell 215 | typeOf' :: TyEnv -> Term -> Either String Type 216 | typeOf' env (TVar v) = case getTypeFromEnv env v of 217 | Just ty -> Right ty 218 | _ -> Left "No type found in env" 219 | typeOf' env (Let v t t') = case typeOf' env t of 220 | Right ty -> typeOf' (addType v ty env) t' 221 | _ -> Left "Unsupported type for Let" 222 | ``` 223 | 224 | For the remaining cases, the pattern matching clauses need to be updated to pass `env` where applicable. 225 | 226 | To conclude, the evaluator and the type checker almost live in two separate worlds -- they do two separate tasks. If we want to ensure the evaluator will produce the correct results, the first thing is to assure that the type checker returns no error. Another interesting observation is how pattern matching the data type is similar to the hypothesis part of the inference rules. The relationship is due to the Curry-Howard isomorphism. When we have a formula {$$}a \vdash b{/$$} ({$$}a{/$$} implies {$$}b{/$$}), and pattern match on {$$}a{/$$}, it's as if we assumed {$$}a{/$$} and need to show {$$}b{/$$}. 227 | 228 | ## Appendix B: Theorem provers 229 | 230 | ### Metamath 231 | 232 | Metamath is a programming language that can express theorems accompanied by a proof checker. The interesting thing about this language is its simplicity. We start by defining a formal system (variables, symbols, axioms and rules of inference) and proceed with building new theorems based on the formal system. 233 | 234 | As we've seen, proofs in mathematics (and Idris to some degree) are usually done at a very high level. Even though the foundations are formal systems, it is very difficult to do proofs at a low level. However, we will show that there are such programming languages like Metamath that work at the lowest level, that is formal systems. 235 | 236 | The most basic concept in Metamath is the substitution method[^apbn1]. Metamath uses an RPN stack[^apbn2] to build hypotheses and then rewrites using the rules of inference in order to reach a conclusion. Metamath has a very simple syntax. A token is a Metamath token if it starts with `$`, otherwise, it is a user-generated token. Here is a list of Metamath tokens: 237 | 238 | 1. `$c` defines constants 239 | 1. `$v` defines variables 240 | 1. `$f` defines the type of variables (floating hypothesis) 241 | 1. `$e` defines required arguments (essential hypotheses) 242 | 1. `$a` defines axioms 243 | 1. `$p` defines proofs 244 | 1. `$=` and `$.` start and end body of a proof 245 | 1. `$(` and `$)` start and end code comments 246 | 1. `${` and `$}` start and end proof blocks 247 | 248 | Besides these tokens, there are several rules: 249 | 250 | 1. A hypothesis is either defined by using the token `$e` or `$f` 251 | 1. For every variable in `$e`, `$a` or `$p`, there has to be a `$f` token defined, that is any variable in essential hypothesis/axiom/proof must have defined a type 252 | 1. An expression that contains `$f`, `$e` or `$d` is active in the given block from the start of the definition until the end of the block. An expression that contains `$a` or `$p` is active from the start of the definition until the end of the file 253 | 1. Proof blocks have an effect on the access of definitions, i.e. scoping. For a given code in a block, only `$a` and `$p` remain visible outside of the block 254 | 255 | In the following example, we'll define a formal system and demonstrate the use of the rule modus ponens in order to get to a new theorem, based on our initial axioms. 256 | 257 | ```text 258 | $( Declaration of constants $) 259 | $c -> ( ) wff |- I J $. 260 | 261 | $( Declaration of variables $) 262 | $v p q $. 263 | 264 | $( Properties of variables, i.e. they are well-formed formulas $) 265 | wp $f wff p $. $( wp is a "command" we can use in RPN that represents a well-formed p $) 266 | wq $f wff q $. 267 | 268 | $( Modus ponens definition $) 269 | ${ 270 | mp1 $e |- p $. 271 | mp2 $e |- ( p -> q ) $. 272 | mp $a |- q $. 273 | $} 274 | 275 | $( Definition of initial axioms $) 276 | wI $a wff I $. $( wI is a "command" that we can use in RPN that represents a well-formed I $) 277 | wJ $a wff J $. 278 | wim $a wff ( p -> q ) $. 279 | ``` 280 | 281 | We created constants (strings) `->`, `wff`, etc. that we will use in our system. Further, we defined `p` and `q` to be variables. The strings `wp` and `wq` specify that `p` and `q` are `wff` (well-formed formulas) respectively. The definition of modus ponens says that for a given {$$}p{/$$} (`mp1`) and a given {$$}p \to q{/$$} (`mp2`) we can conclude {$$}q{/$$} (`mp`) i.e. {$$}p, p \to q \vdash q{/$$}. Note that outside of this block, only `mp` is visible per the rules above. Our initial axioms state that `I`, `J`, and `p -> q` are well-formed formulas. We separate `wff` from `|-`, because otherwise if we just used `|-` then all of the formulas would be true, which does not make sense. 282 | 283 | Having defined our formal system, we can proceed with the proof: 284 | 285 | ```text 286 | ${ $( Use block scoping to hide the hypothesis outside of the block $) 287 | $( Hypothesis: Given I and I -> J $) 288 | proof_I $e |- I $. 289 | proof_I_imp_J $e |- ( I -> J ) $. 290 | $( Goal: Proof that we can conclude J $) 291 | proof_J $p |- J $= 292 | wI $( Stack: [ 'wff I' ] $) 293 | wJ $( Stack: [ 'wff I', 'wff J' ] $) 294 | $( We specify wff for I and J before using mp, since the types have to match $) 295 | proof_I $( Stack: [ 'wff I', 'wff J', '|- I' ] $) 296 | proof_I_imp_J $( Stack: [ 'wff I', 'wff J', '|- I', '|- ( I -> J )' ] $) 297 | mp $( Stack: [ '|- J' ] $) 298 | $. 299 | $} 300 | ``` 301 | 302 | With the code above, we assume `proof_I` and `proof_I_imp_J` in some scope/context. Further, with `proof_J` we want to show that we can conclude `J`. To start the proof, we put `I` and `J` on the stack by using the commands `wI` and `wJ`. Now that our stack contains `[ 'wff I', 'wff J' ]`, we can use `proof_I` to use the first parameter from the stack to conclude `|- I`. Since `proof_I_imp_J` accepts two parameters, it will use the first two parameters from the stack, i.e. `wff I` and `wff J` to conclude `|- I -> J`. Finally, with `mp` we use `|- I` and `|- I -> J` from the stack to conclude that `|- J`. 303 | 304 | ### Simple Theorem Prover 305 | 306 | In this section we'll put formal systems into action by building a proof tree generator in Haskell. We should be able to specify axioms and inference rules, and then query the program so that it will produce all valid combinations of inference in an attempt to reach the target result. 307 | 308 | We start by defining our data structures: 309 | 310 | ```haskell 311 | -- | A rule is a way to change a theorem 312 | data Rule a = Rule { name :: String, function :: a -> a } 313 | -- | A theorem is consisted of an axiom and list of rules applied 314 | data Thm a = Thm { axiom :: a, rulesThm :: [Rule a], result :: a } 315 | -- | Proof system is consisted of axioms and rules between them 316 | data ThmProver a = ThmProver { axioms :: [Thm a], rules :: [Rule a] } 317 | -- | An axiom is just a theorem already proven 318 | mkAxiom a = Thm a [] a 319 | ``` 320 | 321 | To apply a rule to a theorem, we create a new theorem whose result is all the rules applied to the target theorem. We will also need a function that will apply all the rules to all consisted theorems: 322 | 323 | ```haskell 324 | thmApplyRule :: Thm a -> Rule a -> Thm a 325 | thmApplyRule thm rule = Thm (axiom thm) (rulesThm thm ++ [rule]) 326 | ((function rule) (result thm)) 327 | 328 | thmApplyRules :: ThmProver a -> [Thm a] -> [Thm a] 329 | thmApplyRules prover (thm:thms) = map (thmApplyRule thm) (rules prover) 330 | ++ (thmApplyRules prover thms) 331 | thmApplyRules _ _ = [] 332 | ``` 333 | 334 | In order to find a proof, we search through the theorem results and see if the target is there. If it is, we just return. Otherwise, we recursively go through the theorems and apply rules in order to attempt to find the target theorem. 335 | 336 | ```haskell 337 | -- | Construct a proof tree by iteratively applying theorem rules 338 | findProofIter :: (Ord a, Eq a) => 339 | ThmProver a -> a -> Int -> [Thm a] -> Maybe (Thm a) 340 | findProofIter _ _ 0 _ = Nothing 341 | findProofIter prover target depth foundProofs = 342 | case (find (\x -> target == result x) foundProofs) of 343 | Just prf -> Just prf 344 | Nothing -> let theorems = thmApplyRules prover foundProofs 345 | proofsSet = fromList (map result foundProofs) 346 | theoremsSet = fromList (map result theorems) in 347 | -- The case where no new theorems are produced, A union B = A 348 | if (union proofsSet theoremsSet) == proofsSet then Nothing 349 | -- Otherwise keep producing new proofs 350 | else findProofIter prover target (depth - 1) 351 | (mergeProofs foundProofs theorems) 352 | ``` 353 | 354 | Where `mergeProofs` is a function that merges 2 lists of theorems, avoiding duplicates. An example of usage: 355 | 356 | ```haskell 357 | muRules = [ 358 | Rule "One" (\t -> if (isSuffixOf "I" t) then (t ++ "U") else t) 359 | , Rule "Two" (\t -> 360 | case (matchRegex (mkRegex "M(.*)") t) of 361 | Just [x] -> t ++ x 362 | _ -> t) 363 | , Rule "Three" (\t -> subRegex (mkRegex "III") t "U") 364 | nn, Rule "Four" (\t -> subRegex (mkRegex "UU") t "") 365 | ] 366 | 367 | let testProver = ThmProver (map mkAxiom ["MI"]) muRules in 368 | findProofIter testProver "MIUIU" 5 (axioms testProver) 369 | ``` 370 | 371 | As a result we get that for a starting theorem `MI`, we apply rule "One" and rule "Two" (in that order) to get to `MIUIU` (our target proof that we've specified). 372 | 373 | [^apbn1]: Using this method makes it easy to follow any proof _mechanically_. However, this is very different from understanding the _meaning_ of a proof, which in some cases may take a long time of studying. 374 | 375 | [^apbn2]: Reverse Polish Notation is a mathematical notation where functions follow their arguments. For example, to represent {$$}1 + 2{/$$}, we would write {$$}1 \ 2 \ +{/$$}. 376 | 377 | ## Appendix C: IO, Codegen targets, compilation, and FFI 378 | 379 | This section is most relevant to programmers that have experience with programming languages such as C, Haskell, JavaScript. It will demonstrate how Idris can interact with the outside world (IO) and these programming languages. 380 | 381 | In the following examples, we will see how we can compile Idris code. A given program in Idris can be compiled to a binary executable or a back-end for some other programming language. If we decide to compile to a binary executable, then the C back-end will be used by default. 382 | 383 | ### IO 384 | 385 | IO stands for Input/Output. Examples of a few IO operations are: write to a disk file, talk to a network computer, launch rockets. 386 | 387 | Functions can be roughly categorized into two parts: **pure** and **impure**. 388 | 389 | 1. Pure functions are functions that will produce the same result every time they are called 390 | 1. Impure functions are functions that might return a different result on a function call 391 | 392 | An example of a pure function is {$$}f(x) = x + 1{/$$}. An example of an impure function is {$$}f(x) = \text{launch} \ x \ \text{rockets}{/$$}. Since this function causes side-effects, sometimes the launch of the rockets may not be successful (e.g. the case where we have no more rockets to launch). 393 | 394 | Computer programs are not usable if there is no interaction with the user. One problem that arises with languages such as Idris (where expressions are mathematical and have no side effects) is that IO contains side effects. For this reason, such interactions will be encapsulated in a data structure that looks something like: 395 | 396 | ``` 397 | data IO a -- IO operation that returns a value of type a 398 | ``` 399 | 400 | The concrete definition for `IO` is built within Idris itself, that is why we will leave it at the data abstraction as defined above. Essentially `IO` describes all operations that need to be executed. The resulting operations are executed externally by the Idris Run-Time System (or `IRTS`). The most basic IO program is: 401 | 402 | ``` 403 | main : IO () 404 | main = putStrLn "Hello world" 405 | ``` 406 | 407 | The type of `putStrLn` says that this function receives a `String` and returns an `IO` operation. 408 | 409 | ``` 410 | Idris> :t putStrLn 411 | putStrLn : String -> IO () 412 | ``` 413 | 414 | We can read from the input similarly: 415 | 416 | ``` 417 | getLine : IO String 418 | ``` 419 | 420 | In order to combine several IO functions, we can use the `do` notation as follows: 421 | 422 | ``` 423 | main : IO () 424 | main = do 425 | putStr "What's your name? " 426 | name <- getLine 427 | putStr "Nice to meet you, " 428 | putStrLn name 429 | ``` 430 | 431 | In the REPL, we can say `:x main` to execute the IO function. Alternatively, if we save the code to `test.idr`, we can use the command `idris test.idr -o test` in order to output an executable file that we can use on our system. Interacting with it: 432 | 433 | ```shell 434 | boro@bor0:~$ idris test.idr -o test 435 | boro@bor0:~$ ./test 436 | What's your name? Boro 437 | Nice to meet you, Boro 438 | boro@bor0:~$ 439 | ``` 440 | 441 | Let's slightly rewrite our code by abstracting out the concatenation function: 442 | 443 | ``` 444 | concat_string : String -> String -> String 445 | concat_string a b = a ++ b 446 | 447 | main : IO () 448 | main = do 449 | putStr "What's your name? " 450 | name <- getLine 451 | let concatenated = concat_string "Nice to meet you, " name 452 | putStrLn concatenated 453 | ``` 454 | 455 | Note how we use the syntax `let x = y` with pure functions. In contrast, we use the syntax `x <- y` with impure functions. 456 | 457 | The `++` operator is a built-in one used to concatenate lists. A `String` can be viewed as a list of `Char`. In fact, Idris has functions called `pack` and `unpack` that allow for conversion between these two data types: 458 | 459 | ``` 460 | Idris> unpack "Hello" 461 | ['H', 'e', 'l', 'l', 'o'] : List Char 462 | Idris> pack ['H', 'e', 'l', 'l', 'o'] 463 | "Hello" : String 464 | ``` 465 | 466 | ### Codegen 467 | 468 | The keywords `module` and `import` allow us to specify a name of the currently executing code context and load other modules by referring to their names respectively. We can implement our own back-end for a given programming language, for which we need to create a so-called Codegen (`CG`) program. An empty `CG` program would look like this: 469 | 470 | ``` 471 | module IRTS.CodegenEmpty(codegenEmpty) where 472 | 473 | import IRTS.CodegenCommon 474 | 475 | codegenEmpty :: CodeGenerator 476 | codegenEmpty ci = putStrLn "Not implemented" 477 | ``` 478 | 479 | Since Idris is written in Haskell, the package `IRTS` (Idris Run-Time System) is a Haskell collection of modules. It contains data structures where we need to implement Idris commands and give definitions for how they map to the target language. For example, a `putStr` could map to `printf` in C. 480 | 481 | ### Compilation 482 | 483 | We will show how Idris can generate a binary executable and JavaScript code, as well as the difference between total and partial functions and how Idris handles both cases. We define a dependent type `Vect`, which will allow us to work with lists and additionally have the length of the list at the type level: 484 | 485 | ``` 486 | data Vect : Nat -> Type -> Type where 487 | VNil : Vect Z a 488 | VCons : a -> Vect k a -> Vect (S k) a 489 | ``` 490 | 491 | In the code above we define `Vect` as a data structure with two constructors: empty (`VNil`) or element construction (`VCons`). An empty vector is of type `Vect 0 a`, which can be `Vect 0 Int`, `Vect 0 Char`, etc. One example of a vector is `VCons 1 VNil : Vect 1 Integer`, where a list with a single element is represented and we note how the type contains the information about the length of the list. Another example is: `VCons 1.0 (VCons 2.0 (VCons 3.0 VNil)) : Vect 3 Double`. 492 | 493 | We will see how total and partial functions can both pass the compile-time checks, but the latter can cause a run-time error. 494 | 495 | ``` 496 | --total 497 | list_to_vect : List Char -> Vect 2 Char 498 | list_to_vect (x :: y :: []) = VCons x (VCons y VNil) 499 | --list_to_vect _ = VCons 'a' (VCons 'b' VNil) 500 | 501 | vect_to_list : Vect 2 Char -> List Char 502 | vect_to_list (VCons a (VCons b VNil)) = a :: b :: [] 503 | 504 | wrapunwrap : String -> String 505 | wrapunwrap name = pack (vect_to_list (list_to_vect (unpack name))) 506 | 507 | greet : IO () 508 | greet = do 509 | putStr "What is your name? " 510 | name <- getLine 511 | putStrLn ("Hello " ++ (wrapunwrap name)) 512 | 513 | main : IO () 514 | main = do 515 | putStrLn "Following greet, enter any number of chars" 516 | greet 517 | ``` 518 | 519 | We've defined functions `list_to_vect` and `vect_to_list` that convert between dependently typed vectors and lists. Further, we have another function that calls these two functions together. Note how we commented out the total keyword and the second pattern match for the purposes of this example. Now, if we check values for this partial function: 520 | 521 | ``` 522 | Idris> list_to_vect [] 523 | list_to_vect [] : Vect 2 Char 524 | Idris> list_to_vect ['a'] 525 | list_to_vect ['a'] : Vect 2 Char 526 | Idris> list_to_vect ['a','b'] 527 | list_to_vect 'a' (VCons 'b' VNil) : Vect 2 Char 528 | Idris> list_to_vect ['a','b','c'] 529 | list_to_vect ['a', 'b', 'c'] : Vect 2 Char 530 | ``` 531 | 532 | It is obvious that we only get a value for the test `['a', 'b']` case. We can note that for the remaining cases the value is not calculated (computed). If we go further and investigate what happens at run-time: 533 | 534 | ``` 535 | Idris> :exec 536 | Following greet, enter any number of chars 537 | What is your name? Hello 538 | Idris> :exec 539 | Following greet, enter any number of chars 540 | What is your name? Hi 541 | Hello Hi 542 | Idris> 543 | ``` 544 | 545 | Idris stopped the process execution. Going one step further, after we compile: 546 | 547 | ```shell 548 | boro@bor0:~$ idris --codegen node test.idr -o test.js 549 | boro@bor0:~$ node test.js 550 | Following greet, enter any number of chars 551 | What is your name? Hello 552 | /Users/boro/test.js:177 553 | $cg$7 = new $HC_2_1$Prelude__List___58__58_($cg$2.$1, new $HC_2_1$Prelude__List___58__58_($cg$9.$1, $HC_0_0$Prelude__List__Nil)); 554 | ^ 555 | 556 | TypeError: Cannot read property '$1' of undefined 557 | ... 558 | boro@bor0:~$ node test.js 559 | Following greet, enter any number of chars 560 | What is your name? Hi 561 | Hello Hi 562 | ``` 563 | 564 | We get a run-time error from JavaScript. If we do the same with the C back-end: 565 | 566 | ```shell 567 | boro@bor0:~$ idris --codegen C test.idr -o test 568 | boro@bor0:~$ ./test 569 | Following greet, enter any number of chars 570 | What is your name? Hello 571 | Segmentation fault: 11 572 | boro@bor0:~$ ./test 573 | Following greet, enter any number of chars 574 | What is your name? Hi 575 | Hello Hi 576 | ``` 577 | 578 | It causes a segmentation fault, which is a run-time error. As a conclusion, if we use partial functions then we need to do additional checks in the code to cover the cases for potential run-time errors. Alternatively, if we want to take full advantage of the safety that the type system offers, we should define all functions as total. 579 | 580 | By defining the function `list_to_vect` to be total, we specify that every input has to have an output. All the remaining checks are done at compile-time by Idris and with that, we're guaranteed that all callers of this function satisfy the types. 581 | 582 | ### Foreign Function Interface 583 | 584 | In this example, we'll introduce the FFI system, which stands for Foreign Function Interface. It allows us to call functions written in other programming languages. 585 | 586 | We can define the file `test.c` as follows: 587 | 588 | ```c 589 | #include "test.h" 590 | 591 | int succ(int i) { 592 | return i+1; 593 | } 594 | ``` 595 | 596 | Together with `test.h`: 597 | 598 | ```c 599 | int succ(int); 600 | ``` 601 | 602 | Now we can write a program that calls this function as follows: 603 | 604 | ``` 605 | module Main 606 | 607 | %include C "test.h" 608 | %link C "test.o" 609 | 610 | succ : Int -> IO Int 611 | succ x = foreign FFI_C "succ" (Int -> IO Int) x 612 | 613 | main : IO () 614 | main = do x <- succ 1 615 | putStrLn ("succ 1 =" ++ show x) 616 | ``` 617 | 618 | With the code above we used the built-in function `foreign` together with the built-in constant `FFI_C` which are defined in Idris as follows: 619 | 620 | ``` 621 | Idris> :t foreign 622 | foreign : (f : FFI) -> ffi_fn f -> (ty : Type) -> {auto fty : FTy f [] ty} -> ty 623 | Idris> FFI_C 624 | MkFFI C_Types String String : FFI 625 | ``` 626 | 627 | This can be useful if there's a need to use a library that's already written in another programming language. Alternatively, with IRTS we can export Idris functions to C and call them from a C code. We can define `test.idr` as follows: 628 | 629 | ``` 630 | nil : List Int 631 | nil = [] 632 | 633 | cons : Int -> List Int -> List Int 634 | cons x xs = x :: xs 635 | 636 | show' : List Int -> IO String 637 | show' xs = do { putStrLn "Ready to show..." ; pure (show xs) } 638 | 639 | testList : FFI_Export FFI_C "testHdr.h" [] 640 | testList = Data (List Int) "ListInt" $ Fun nil "nil" $ Fun cons "cons" $ Fun show' "showList" $ End 641 | ``` 642 | 643 | Running `idris test.idr --interface -o test.o` will generate two files: `test.o` (the object file) and `testHdr.h` (the header file). Now we can input the following code in some file, e.g. `test_idris.c`: 644 | 645 | ```c 646 | #include "testHdr.h" 647 | 648 | int main() { 649 | VM* vm = idris_vm(); 650 | ListInt x = cons(vm, 10, cons(vm, 20, nil(vm))); 651 | printf("%s\n", showList(vm, x)); 652 | close_vm(vm); 653 | } 654 | ``` 655 | 656 | We will now compile and test everything together: 657 | 658 | ```shell 659 | boro@bor0:~$ ${CC:=cc} test_idris.c test.o `${IDRIS:-idris} $@ --include` `${IDRIS:-idris} $@ --link` -o test 660 | boro@bor0:~$ ./test 661 | Ready to show... 662 | [10, 20] 663 | ``` 664 | 665 | With this approach, we can write verified code in Idris and export its functionality to another programming language. 666 | 667 | ## Appendix D: Implementing a formal system 668 | 669 | So far we have been mostly using a set of formal systems to prove software correctness. In this appendix, we show how to _both_ create and use a formal system in order to be able to prove facts. For that, we will provide a minimal implementation of Propositional logic, as described in chapter 2. 670 | 671 | Here's the syntax of the formal system expressed in BNF, and the code in Idris: 672 | 673 | ``` 674 | --prop ::= P | Q | R | prop brelop prop 675 | --brelop ::= "&&" | "->" 676 | data Prop = P | Q | R | And Prop Prop | Imp Prop Prop 677 | ``` 678 | 679 | We now have a way to represent some logical formulas, e.g. {$$}P \land Q{/$$} as `And P Q`. Further, in our implementation, we also need a way to differentiate between well-formed formulas and theorems since not all well-formed formulas are theorems. For that, we provide the `Proof` data type and a way to extract a proof: 680 | 681 | ``` 682 | data Proof a = MkProof a 683 | total fromProof : Proof a -> a 684 | fromProof (MkProof a) = a 685 | ``` 686 | 687 | Note that `MkProof (And P Q)` ({$$}\vdash P \land Q{/$$}) is different from `And P Q` ({$$}P \land Q{/$$}). However, the `MkProof` constructor mustn't be used directly; proofs should only be constructed given the rules that we provide next. 688 | 689 | ``` 690 | -- A, B |- A /\ B 691 | total ruleJoin : Proof Prop -> Proof Prop -> Proof Prop 692 | ruleJoin (MkProof x) (MkProof y) = MkProof (And x y) 693 | -- A, B |- A 694 | total ruleSepL : Proof Prop -> Proof Prop 695 | ruleSepL (MkProof (And x y)) = MkProof x 696 | ruleSepL x = x 697 | ``` 698 | 699 | Another powerful rule is the *Implication Rule*. It accepts a non-proven term `Prop`, whereas other rules accept proven terms; the hypothesis needn't be necessarily true, it only states that "If this hypothesis were a theorem, then that would be a theorem". The second argument is a function `(Proof Prop -> Proof Prop)` that accepts a `Proof` and returns a `Proof`; basically, another rule which will be used to transform the hypothesis `x`. As a result, it produces the theorem {$$}x \to y(x){/$$}. 700 | 701 | ``` 702 | total ruleImplication : Prop -> (Proof Prop -> Proof Prop) -> Proof Prop 703 | ruleImplication x f = f (MkProof x) 704 | ``` 705 | 706 | For example, we can prove that {$$}\vdash P \land Q \to P \land P{/$$} with `ruleImplication (And P Q) (\x => ruleJoin (ruleSepL x) (ruleSepL x))`. 707 | 708 | In Idris we get construction of well-formed formulas for free due to algebraic data structures. In some untyped programming languages, such as Python, we could use hashmaps to simulate the types. In the following code we have to be extra careful with the conditional checks, whereas in Idris it is simpler due to pattern matching. 709 | 710 | ```python 711 | def And(x, y): return {'x': x, 'y': y, 'type': 'and'} 712 | def Imp(x, y): return {'x': x, 'y': y, 'type': 'imp'} 713 | def MkProof(x): return {'v': x, 'type': 'proof'} 714 | def fromProof(x): return x['v'] if isinstance(x, dict) and 'v' in x else None 715 | def ruleJoin(x, y): 716 | if not isinstance(x, dict) or not isinstance(y, dict) or not 'type' in x or not 'type' in y or x['type'] != 'proof' or y['type'] != 'proof': return None 717 | return MkProof(And(fromProof(x), fromProof(y))) 718 | def ruleSepL(x): 719 | if not isinstance(x, dict) or not 'type' in x or x['type'] != 'proof': return None 720 | wff = fromProof(x) 721 | if wff['type'] == 'and': return MkProof(wff['x']) 722 | return MkProof(wff) 723 | def ruleImplication(x, y): return MkProof(Imp(x, fromProof(y(MkProof(x))))) if isinstance(x, dict) and 'type' in x and callable(y) else None 724 | ``` 725 | 726 | Note how we embedded a formal system (Propositional logic) within a formal system (Idris/Python), and we were able to reason about it through symbolic manipulation. 727 | -------------------------------------------------------------------------------- /manuscript/chapter1.md: -------------------------------------------------------------------------------- 1 | # 1. Formal systems 2 | 3 | Before we can construct proofs of correctness for software we need to understand what a proof is and what it means for a proof to be valid. This is the role of _formal systems_. The purpose of formal systems is to let us reason about reasoning -- to manipulate logical proofs in terms of their _form_, rather than their _content_. This level of abstraction makes formal systems powerful tools. 4 | 5 | I> ### Definition 1 6 | I> 7 | I> A **formal system** is a model of abstract reasoning. A formal system consists of: 8 | I> 9 | I> 1. A **formal language** that contains: 10 | I> 1. A finite set of _symbols_, which can be combined into finite strings called _formulas_ 11 | I> 1. A _grammar_, which is a set of rules that tells us which formulas are "well-formed" 12 | I> 1. A set of **axioms**, that is, formulas we accept as "valid" without justification 13 | I> 1. A set of **inference rules** that tell us how we can derive new valid formulas from old ones 14 | 15 | Inside a given formal system the grammar determines which formulas are _syntactically_ sensible, while the inference rules govern which formulas are _semantically_ sensible. The difference between these two is important. For example, thinking of the English language as a (very complicated!) formal system, the sentence "Colorless green ideas sleep furiously" is syntactically valid (since different parts of speech are used in the right places), but is semantically nonsense. 16 | 17 | After a formal system is defined, other formal systems can extend it. For example, set theory is based on first-order logic, which is based on propositional logic which represents a formal system. We'll discuss this theory briefly in the next chapter. 18 | 19 | I> ### Definition 2 20 | I> 21 | I> For a given formal system, the system is **incomplete** if there are statements that are true but which cannot be proved to be true inside that system. Conversely, the system is **complete** if all true statements can be proved. 22 | 23 | The statement "This statement is not provable" can either be true or false. In the case it is true, then it is not provable. Alternatively, in the case it is false then it is provable, but we're trying to prove something false. Thus the system is incomplete because some truths are unprovable. 24 | 25 | I> ### Definition 3 26 | I> 27 | I> For a given formal system, the system is **inconsistent** if there is a theorem in that system that is contradictory. Conversely, the system is **consistent** if there are no contradictory theorems. 28 | 29 | A simple example is the statement "This statement is false". This statement is true if and only if[^ch1n1] it is false, and therefore it is neither true nor false. 30 | 31 | In general, we often put our focus on which parts of mathematics can be formalized in concrete formal systems, rather than trying to find a theory in which all of mathematics can be developed. The reason for that is Gödel's incompleteness theorem. This theorem states that there doesn't exist[^ch1n2] a formal system that is both complete and consistent. As a result, it is better to reason about a formal system outside of the system (at the metalanguage level), since the object level (rules within the system) can be limiting. As the famous saying goes to "think outside of the box", and similarly to how we sometimes do meta-thinking to improve ourselves. 32 | 33 | In conclusion, formal systems are our attempt to abstract models, whenever we reverse engineer nature in an attempt to understand it better. They may be imperfect but are nevertheless useful tools for reasoning. 34 | 35 | ## 1.1. MU puzzle example 36 | 37 | The MU puzzle is a formal system that we'll have a look at as an example. 38 | 39 | I> ### Definition 4 40 | I> 41 | I> We're given a starting string `MI`, combined with a few inference rules, or transformation rules: 42 | I> 43 | I> | **No.** | **Rule** | **Description** | **Example** | 44 | I> | ------- | -------------------------- | -------------------------------------- | ------------------ | 45 | I> | 1 | x`I` {$$}\to{/$$} x`IU` | Append `U` at a string ending in `I` | `MI` to `MIU` | 46 | I> | 2 | `M`x {$$}\to{/$$} M`xx` | Double the string after `M` | `MIU` to `MIUIU` | 47 | I> | 3 | x`III`y {$$}\to{/$$} x`U`y | Replace `III` inside a string with `U` | `MUIIIU` to `MUUU` | 48 | I> | 4 | x`UU`y {$$}\to{/$$} xy | Remove `UU` from inside a string | `MUUU` to `MU` | 49 | 50 | In the inference rules the symbols `M`, `I`, and `U` are part of the system, while `x` is a variable that stands for any symbol(s). For example, `MI` matches rule 2 for `x = I`, and it can also match rule 1 for `x = M`. Another example is `MII` that matches rule 2 for `x = II` and rule 1 for `x = MI`. 51 | 52 | We will show (or prove) how we can get from `MI` to `MIIU` using the inference rules: 53 | 54 | 1. `MI` (axiom) 55 | 1. `MII` (rule 2, `x = I`) 56 | 1. `MIIII` (rule 2, `x = II`) 57 | 1. `MIIIIIIII` (rule 2, `x = IIII`) 58 | 1. `MUIIIII` (rule 3, `x = M`, `y = IIIII`) 59 | 1. `MUUII` (rule 3, `x = MU`, `y = II`) 60 | 1. `MII` (rule 4, `x = M`, `y = II`) 61 | 1. `MIIU` (rule 1, `x = MI`) 62 | 63 | We can represent the formal description of this system as follows: 64 | 65 | 1. Formal language 66 | 1. Set of symbols is {$$}\{ M, I, U \}{/$$} 67 | 1. A string is well-formed if the first letter is `M` and there are no other `M` letters. Examples: `M`, `MIUIU`, `MUUUIII` 68 | 1. `MI` is the starting string, i.e. axiom 69 | 1. The rules of inference are defined in Definition 4 70 | 71 | Q> Can we get from `MI` to `MU` with this system? 72 | Q> 73 | Q> To answer this, we will use an invariant[^ch1n3] with mathematical induction to prove our claim. 74 | 75 | Note that, to be able to apply rule 3, we need to have the number of subsequent `I`'s to be divisible by 3. Let's have our invariant say that "There is no sequence of `I`'s in the string that with length divisible by 3": 76 | 77 | 1. For the starting axiom, we have one `I`. Invariant OK. 78 | 1. Applying rule 2 will be doubling the number of `I`'s, so we can have: `I`, `II`, `IIII`, `IIIIIII` (in particular, {$$}2^n{/$$} `I`'s). Invariant OK. 79 | 1. Applying rule 3 will be reducing the number of `I`'s by 3. But note that {$$}2^n - 3{/$$} is still not divisible by 3[^ch1n4]. Invariant OK. 80 | 81 | We've shown that with the starting axiom `MI` it is not possible to get to `MU` because no sequence of steps can turn a string with one `I` into a string with no `I`s. But if we look carefully, we've used a different formal system to reason about `MU` (i.e. divisibility by 3, which is not part of the MU system). This is because the puzzle cannot be solved in its own system. Otherwise, an algorithm would keep trying different inference rules of `MU` indefinitely (not knowing that `MU` is impossible). 82 | 83 | Every useful formal system has this limitation. As we've seen, Gödel's theorem shows that there's no formal system that can contain all possible truths, because it cannot prove some truths about its own structure. Thus, having experience with different formal systems and combining them as needed can be useful. 84 | 85 | [^ch1n1]: The word iff is an abbreviation for "If and only if" and means that two statements are logically equivalent. 86 | 87 | [^ch1n2]: Note that this theorem only holds for systems that allow expressing arithmetic of natural numbers (e.g. Peano, set theory, but first-order logic also has some paradoxes if we allow self-referential statements). This is so because the incompleteness theorem relies on the Gödel numbering concept, which allows a formal system to reason about itself by using symbols in the system to map expressions in that same system. For example, 0 is mapped to 1, S is 2, = is 3, so {$$}0 = S0 \iff (1, 3, 2, 1){/$$}. Using this we can express statements about the system within the system - self-referential statements. We will look into these systems in the next chapter. 88 | 89 | [^ch1n3]: An invariant is a property that holds whenever we apply any of the inference rules. 90 | 91 | [^ch1n4]: After having introduced ourselves to proofs, you will be given an exercise to prove this fact. 92 | -------------------------------------------------------------------------------- /manuscript/chapter2.md: -------------------------------------------------------------------------------- 1 | # 2. Classical mathematical logic 2 | 3 | All engineering disciplines involve some usage of logic. The foundations of Idris, as we will see later, are based on a system that implements (or encodes) classical mathematical logic so that we can easily "map" this logic and its inference rules to computer programs. 4 | 5 | ## 2.1. Hierarchy of mathematical logic and definitions 6 | 7 | At its core, mathematical logic deals with mathematical concepts expressed using formal logical systems. In this section, we'll take a look at the hierarchy of these logical systems. The reason why we have different levels of hierarchies is that at each level we have more power in expressiveness. Further, these logical systems are what will allow us to produce proofs. 8 | 9 | ### 2.1.1. Propositional logic 10 | 11 | I> ### Definition 1 12 | I> 13 | I> The propositional branch of logic is concerned with the study of **propositions**, which are statements that are either {$$}\top{/$$} (true) or {$$}\bot{/$$} (false). Variables can be used to represent propositions. Propositions are formed by other propositions with the use of logical connectives. The most basic logical connectives are {$$}\land{/$$} (and), {$$}\lor{/$$} (or), {$$}\lnot{/$$} (negation), and {$$}\to{/$$} (implication). 14 | 15 | For example, we can say `a = Salad is organic`, and thus the variable `a` represents a true statement. Another statement is `a = Rock is organic`, and thus `a` is a false statement. The statement `a = Hi there!` is neither a true nor a false statement, and thus is not a proposition. 16 | 17 | The "and" connective means that both {$$}a{/$$} and {$$}b{/$$} have to be true in order for {$$}a \land b{/$$} to be true. For example, the statement `I like milk and sugar` is true as a whole iff both `I like milk` and `I like sugar` are true. 18 | 19 | | {$$}\textbf{a}{/$$} | {$$}\textbf{b}{/$$} | {$$}a \land b{/$$} | 20 | | ------------------- | ------------------- | ------------------ | 21 | | {$$}\top{/$$} | {$$}\top{/$$} | {$$}\top{/$$} | 22 | | {$$}\top{/$$} | {$$}\bot{/$$} | {$$}\bot{/$$} | 23 | | {$$}\bot{/$$} | {$$}\top{/$$} | {$$}\bot{/$$} | 24 | | {$$}\bot{/$$} | {$$}\bot{/$$} | {$$}\bot{/$$} | 25 | 26 | The "or" connective means that either of {$$}a{/$$} or {$$}b{/$$} has to be true in order for {$$}a \lor b{/$$} to be true. It will also be true if both {$$}a{/$$} and {$$}b{/$$} are true. This is known as inclusive or. For example, the statement `I like milk or sugar` is true as a whole if at least one of `I like milk` or `I like sugar` is true. 27 | 28 | This definition of "or" might be a bit counter-intuitive to the way we use it in day to day speaking. When we say `I like milk or sugar` we normally mean one of them but not both. This is known as exclusive or, however, for the purposes of this book we will be using inclusive or. 29 | 30 | | {$$}\textbf{a}{/$$} | {$$}\textbf{b}{/$$} | {$$}a \lor b{/$$} | 31 | | ------------------- | ------------------- | ----------------- | 32 | | {$$}\top{/$$} | {$$}\top{/$$} | {$$}\top{/$$} | 33 | | {$$}\top{/$$} | {$$}\bot{/$$} | {$$}\top{/$$} | 34 | | {$$}\bot{/$$} | {$$}\top{/$$} | {$$}\top{/$$} | 35 | | {$$}\bot{/$$} | {$$}\bot{/$$} | {$$}\bot{/$$} | 36 | 37 | The negation connective simply swaps the truthiness of a proposition. The easiest way to negate any statement is to just prepend `It is not the case that ...` to it. For example, the negation of `I like milk` is `It is not the case that I like milk`, or simply `I don't like milk`. 38 | 39 | | {$$}\textbf{a}{/$$} | {$$}\lnot a{/$$} | 40 | | ------------------- | ---------------- | 41 | | {$$}\top{/$$} | {$$}\bot{/$$} | 42 | | {$$}\bot{/$$} | {$$}\top{/$$} | 43 | 44 | The implication connective allows us to express conditional statements, and its interpretation is subtle. We say that {$$}a \to b{/$$} is true if anytime {$$}a{/$$} is true, it is necessarily also the case that {$$}b{/$$} is true. Another way to think about implication is in terms of _promises_; {$$}a \to b{/$$} represents a promise that if {$$}a{/$$} happens, then {$$}b{/$$} also happens. In this interpretation, the truth value of {$$}a \to b{/$$} is whether or not the promise is kept, and we say that a promise is kept unless it has been broken. 45 | 46 | For example, if we choose `a = Today is your birthday` and `b = I brought you a cake`, then {$$}a \to b{/$$} represents the promise `If today is your birthday, then I brought you a cake`. Then there are four different ways that today can play out: 47 | 48 | 1. Today is your birthday, and I brought you a cake. The promise is kept, so the implication is true 49 | 1. Today is your birthday, but I did not bring you a cake. The promise is not kept, so the implication is false 50 | 1. Today is not your birthday, and I brought you a cake. Is the promise kept? Better question - has the promise been broken? The condition the promise is based on - that today is your birthday - is not satisfied, so we say that the promise is not broken. The implication is true 51 | 1. Today is not your birthday, and I did not bring you a cake. Again, the condition of the promise is not satisfied, so the promise is not broken. The implication is true 52 | 53 | In the last two cases, where the condition of the promise is not satisfied, we sometimes say that the implication is _vacuously true_. 54 | 55 | This definition of implication might be a bit counter-intuitive to the way we use it in day to day speaking. When we say `If it rains, then the ground is wet` we usually mean both that `If the ground is wet, then it rains` and `If it rains, then the ground is wet`. This is known as biconditional and is denoted as {$$}a \leftrightarrow b{/$$}, or simply {$$}a \ \text{iff} \ b{/$$}. 56 | 57 | | {$$}\textbf{a}{/$$} | {$$}\textbf{b}{/$$} | {$$}a \to b{/$$} | 58 | | ------------------- | ------------------- | ---------------- | 59 | | {$$}\top{/$$} | {$$}\top{/$$} | {$$}\top{/$$} | 60 | | {$$}\top{/$$} | {$$}\bot{/$$} | {$$}\bot{/$$} | 61 | | {$$}\bot{/$$} | {$$}\top{/$$} | {$$}\top{/$$} | 62 | | {$$}\bot{/$$} | {$$}\bot{/$$} | {$$}\top{/$$} | 63 | 64 | As stated in Definition 1, propositions can also be defined (or combined) in terms of other propositions. For example, we can choose {$$}a{/$$} to be `I like milk` and {$$}b{/$$} to be `I like sugar`. So {$$}a \land b{/$$} means that `I like both milk and sugar`. If we let {$$}c{/$$} be `I am cool` then with {$$}a \land b \to c{/$$} we say: `If I like milk and sugar, then I am cool`. Note how we took a proposition {$$}a \land b{/$$} and modified it with another connective to form a new proposition. 65 | 66 | X> ### Exercise 1 67 | X> 68 | X> Come up with a few propositions and combine them using: 69 | X> 70 | X> 1. The "and" connective 71 | X> 1. The "or" connective 72 | X> 1. Negation connective 73 | X> 1. Implication connective 74 | X> 75 | X> Try to come up with a sensible statement in English for each derived proposition. 76 | 77 | ### 2.1.2. First-order logic 78 | 79 | I> ### Definition 2 80 | I> 81 | I> The first-order logical system extends propositional logic by additionally covering **predicates** and **quantifiers**. A predicate {$$}P(x){/$$} takes an input {$$}x{/$$}, and produces either true or false as an output. There are two quantifiers introduced: {$$}\forall{/$$} (universal quantifier) and {$$}\exists{/$$} (existential quantifier). 82 | 83 | One example of a predicate is `P(x) = x is organic`, with {$$}P(Salad) = \top{/$$}, but {$$}P(Rock) = \bot{/$$}. 84 | 85 | In the following example the universal quantifier says that the predicate will hold for **all** possible choices of {$$}x{/$$}: {$$}\forall x, P(x){/$$}. Alternatively, the existential quantifier says that the predicate will hold for **at least one** choice of {$$}x{/$$}: {$$}\exists x, P(x){/$$}. 86 | 87 | Another example of combining a predicate with the universal quantifier is `P(x) = x is a mammal`, then {$$}\forall x, P(x){/$$} is true, for all {$$}x{/$$} ranging over the set of humans. We can choose `P(x) = x understands Dependent Types` with {$$}\exists x, P(x){/$$} to say that there is at least one person that understands Dependent Types. 88 | 89 | The negation of the quantifiers is defined as follows: 90 | 91 | 1. Negation of universal quantifier: {$$}\lnot (\forall x, P(x)) \leftrightarrow \exists x, \lnot P(x){/$$} 92 | 1. Negation of existential quantifier: {$$}\lnot (\exists x, P(x)) \leftrightarrow \forall x, \lnot P(x){/$$} 93 | 94 | As an example, for `P(x) = x understands Dependent Types` the negation of {$$}\exists x, P(x){/$$} is {$$}\forall x, \lnot P(x){/$$}. That is, the negation of `there is at least one person that understands Dependent Types` is `for all persons x, x does not understand Dependent Types`, or simply put `nobody understands Dependent Types`. 95 | 96 | X> ### Exercise 2 97 | X> 98 | X> Think of a real-world predicate and express its truthiness using the {$$}\forall{/$$} and {$$}\exists{/$$} symbols. Afterward, negate both the universal and existential quantifier. 99 | 100 | ### 2.1.3. Higher-order logic 101 | 102 | In first-order logic, predicates act like functions that take an input value and produce a proposition. A predicate can't be true or false until a specific value is substituted for the variables, and the quantifiers {$$}\forall{/$$} and {$$}\exists{/$$} "close" over a predicate to give a statement which can be either true or false. 103 | 104 | Likewise, we can define a "meta-predicate" that acts as a function on predicates. For example, let {$$}\Gamma(P){/$$} be the statement `there exists a person x such that P(x) is true`. Note that it doesn't make sense to ask if {$$}\Gamma(P){/$$} is true or false until we plug in a specific _predicate_ {$$}P{/$$}. But we can quantify over {$$}P{/$$}, and construct a statement like {$$}\forall P, \Gamma(P){/$$}. In English, this statement translates to `For any given property P, there exists a person satisfying that property`. 105 | 106 | Meta-predicates like {$$}\Gamma{/$$} are called _second-order_ because they range over first-order predicates. And there's no reason to stop there; we could define third-order predicates that range over second-order predicates, and fourth-order predicates that range over third-order predicates, and so on. 107 | 108 | I> ### Definition 3 109 | I> 110 | I> The higher-order logical system [second-order logic, third-order-logic, ..., higher-order (nth-order) logic] extends the quantifiers that range over individuals[^ch2n1] to range over predicates. 111 | 112 | For example, the second-order logic quantifies over sets. Third-order logic quantifies over sets of sets, and so on. 113 | 114 | Moving up the hierarchy of logical systems brings power, at a price. Propositional (zeroth-order) logic is completely decidable[^ch2n2]. Predicate (first-order) logic is no longer decidable, and by Gödel's incompleteness theorem we have to choose between completeness and consistency, but at least there is still an algorithm that can determine whether a proof is valid or not. For second-order and higher logic we lose even this - we have to choose between completeness, consistency, and a proof detection algorithm. 115 | 116 | The good news is that in practice, second-order predicates are used in a very limited capacity, and third- and higher-order predicates are never needed. One important example of a second-order predicate appears in the Peano axioms of the natural numbers. 117 | 118 | I> ### Definition 4 119 | I> 120 | I> Peano's axioms is a system of axioms that describes the natural numbers. It consists of 9 axioms, but we will name only a few: 121 | I> 122 | I> 1. 0 (zero) is a natural number 123 | I> 1. For every number {$$}x{/$$}, we have that {$$}S(x){/$$} is a natural number, namely the successor function 124 | I> 1. For every number {$$}x{/$$}, we have that {$$}x = x{/$$}, namely that equality is reflexive 125 | 126 | I> ### Definition 5 127 | I> 128 | I> The ninth axiom in Peano's axioms is the induction axiom. It states the following: if {$$}P{/$$} is a predicate where {$$}P(0){/$$} is true, and for every natural number {$$}n{/$$} if {$$}P(n){/$$} is true then we can prove that {$$}P(n+1){/$$}, then {$$}P(n){/$$} is true for all natural numbers. 129 | 130 | Peano's axioms are expressed using a combination of first-order and second-order logic. This concept consists of a set of axioms for the natural numbers, and all of them are statements in first-order logic. An exception of this is the induction axiom, which is in second-order since it quantifies over predicates. The base axioms can be augmented with arithmetical operations of addition, multiplication and the order relation, which can also be defined using first-order axioms. 131 | 132 | ## 2.2. Set theory abstractions 133 | 134 | I> ### Definition 6 135 | I> 136 | I> Set theory is a type of a formal system, which is the most common **foundation of mathematics**. It is a branch of mathematical logic that works with **sets**, which are collections of objects. 137 | 138 | Like in programming, building abstractions in mathematics is of equal importance. However, the best way to understand something is to get to the bottom of it. We'll start by working from the lowest level to the top. We will start with the most basic object (the unordered collection) and work our way up to defining functions. Functions are an important core concept of Idris, however, as we will see in the theory that Idris relies on, functions are used as a primitive notion (an axiom) instead of being built on top of something else. 139 | 140 | I> ### Definition 7 141 | I> 142 | I> A set is an **unordered** collection of objects. The objects can be anything. 143 | 144 | Finite sets can be denoted by _roster notation_; we write out a list of objects in the set, separated by commas, and enclose them using curly braces. For example, one set of fruits is {$$}\{ \text{apple}, \text{banana} \}{/$$}. Since it is an unordered collection we have that {$$}\{ \text{apple}, \text{banana} \} = \{ \text{banana}, \text{apple} \}{/$$}. 145 | 146 | I> ### Definition 8 147 | I> 148 | I> Set membership states that a given object belongs to a set. It is denoted using the {$$}\in{/$$} operator. 149 | 150 | For example, {$$}\text{apple} \in \{ \text{apple}, \text{banana} \}{/$$} says that {$$}\text{apple}{/$$} is in that set. 151 | 152 | Roster notation is inconvenient for large sets, and not possible for infinite sets. Another way to define a set is with _set-builder notation_. With this notation, we specify a set by giving a predicate that all of its members satisfy. A typical set in set-builder notation has the form {$$}\{x \mid P(x)\},{/$$} where {$$}P{/$$} is a predicate. If {$$}a{/$$} is a specific object, then {$$}a \in \{ x \mid P(x) \}{/$$} precisely when {$$}P(a){/$$} is true. 153 | 154 | I> ### Definition 9 155 | I> 156 | I> An {$$}n{/$$}-tuple is an **ordered collection** of {$$}n{/$$} objects. As with sets, the objects can be anything. Tuples are usually denoted by comma separating the list of objects and enclosing them using parentheses. 157 | 158 | For example, we can use the set {$$}\{ \{ 1, \{ a_1 \} \}, \{ 2, \{ a_2 \} \}, \ldots, \{ n, \{ a_n \} \} \}{/$$} to represent the ordered collection {$$}(a_1, a_2, ..., a_n){/$$}. This will now allow us to extract the {$$}k{/$$}-th element of the tuple, by picking {$$}x{/$$} such that {$$}\{ k, \{ x \} \} \in A{/$$}. Having done that, now we have that {$$}(a, b) = (c, d) \equiv a = c \land b = d{/$$}, that is, two tuples are equal iff their first and second elements respectively are equal. This is what makes them ordered. 159 | 160 | One valid tuple is {$$}(\text{1 pm}, \text{2 pm}, \text{3 pm}){/$$} which represents 3 hours of a day sequentially. 161 | 162 | I> ### Definition 10 163 | I> 164 | I> An {$$}n{/$$}-ary relation is just a set of {$$}n{/$$}-tuples. 165 | 166 | For example, the `is bigger than` relation represents a 2-tuple (pair), for the following set: {$$}\{ (\text{cat}, \text{mouse}), (\text{mouse}, \text{cheese}), (\text{cat}, \text{cheese}) \}{/$$}. 167 | 168 | I> ### Definition 11 169 | I> 170 | I> {$$}A{/$$} is a subset of {$$}B{/$$} if all elements of {$$}A{/$$} are found in {$$}B{/$$} (but not necessarily vice-versa). We denote it as such: {$$}A \subseteq B{/$$}. 171 | 172 | For example, the expressions {$$}\{ 1, 2 \} \subseteq \{ 1, 2, 3 \}{/$$} and {$$}\{ 1, 2, 3 \} \subseteq \{ 1, 2, 3 \}{/$$} are both true. But this expression is not true: {$$}\{ 1, 2, 3 \} \subseteq \{ 1, 2 \}{/$$}. 173 | 174 | I> ### Definition 12 175 | I> 176 | I> A Cartesian product is defined as the set {$$}\{ (a, b) \mid a \in A \land b \in B \}{/$$}. It is denoted as {$$}A \times B{/$$}. 177 | 178 | For example if {$$}A = \{ a, b \}{/$$} and {$$}B = \{ 1, 2, 3 \}{/$$} then the combinations are: {$$}A \times B = \{ (a, 1), (a, 2), (a, 3), (b, 1), (b, 2), (b, 3) \}{/$$}. 179 | 180 | I> ### Definition 13 181 | I> 182 | I> **Functions** are defined in terms of relations[^ch2n3]. A binary (2-tuple) set {$$}F{/$$} represents a mapping[^ch2n4] from some set {$$}A{/$$} to some set {$$}B{/$$}, where {$$}F{/$$} is a subset of the Cartesian product of {$$}A{/$$} and {$$}B{/$$}. That is, a function {$$}f{/$$} from {$$}A{/$$} to {$$}B{/$$} is denoted {$$}f : A \to B{/$$} and is a subset of {$$}F{/$$}, i.e. {$$}f \subseteq F{/$$}. There is one more constraint that functions have, namely, that they cannot produce 2 or more different values for a single input. 183 | 184 | For example, the function {$$}f(x) = x + 1{/$$} is a function that, given a number, returns it increased by one. We have that {$$}f(1) = 2{/$$}, {$$}f(2) = 3{/$$}, etc. Another way to represent this function is using the 2-tuple set: {$$}f = \{ (1, 2), (2, 3), (3, 4), \ldots \}{/$$}. 185 | 186 | One simple way to think of functions is in the form of tables. For a function {$$}f(x){/$$} accepting a single parameter {$$}x{/$$}, we have a two-column table where the first column is the input, and the second column is the output. For a function {$$}f(x, y){/$$} accepting two parameters {$$}x{/$$} and {$$}y{/$$} we have a three-column table where the first and second columns represent the input, and the third column is the output. Thus, to display the function discussed above in the form of a table, it would look like this: 187 | 188 | | {$$}\textbf{x}{/$$} | {$$}f(x){/$$} | 189 | |-------------------- | ------------- | 190 | | 1 | 2 | 191 | | 2 | 3 | 192 | | ... | ... | 193 | 194 | X> ### Exercise 3 195 | X> 196 | X> Think of a set of objects and express that some object belongs to that set. 197 | 198 | X> ### Exercise 4 199 | X> 200 | X> Think of a set of objects whose order matters and express it in terms of an ordered collection. 201 | 202 | X> ### Exercise 5 203 | X> 204 | X> Think of a relation (for example, a relation between two persons in a family tree) and express it using the notation described. 205 | 206 | X> ### Exercise 6 207 | X> 208 | X> Come up with two subset expressions, one that is true and another one that is false. 209 | 210 | X> ### Exercise 7 211 | X> 212 | X> Think of two sets and combine them using the definition of the Cartesian product. Afterward, think of two subset expressions, one that is true and another one that is false. 213 | 214 | X> ### Exercise 8 215 | X> 216 | X> Think of a function and represent it using the table approach. 217 | 218 | X> ### Exercise 9 219 | X> 220 | X> Write down the corresponding input and output sets for the function you implemented in Exercise 8. 221 | 222 | ## 2.3. Substitution and mathematical proofs 223 | 224 | Substitution lies at the heart of mathematics[^ch2n5]. 225 | 226 | I> ### Definition 14 227 | I> 228 | I> Substitution consists of systematically replacing occurrences of some symbol with a given value. It can be applied in different contexts involving formal objects containing symbols. 229 | 230 | For example, let's assume that we have the following: 231 | 232 | 1. An inference rule that states: If {$$}a = b{/$$} and {$$}b = c{/$$}, then {$$}a = c{/$$} 233 | 1. Two axioms that state: {$$}1 = 2{/$$} and {$$}2 = 3{/$$} 234 | 235 | We can use the following "proof" to claim that {$$}1 = 3{/$$}: 236 | 237 | 1. {$$}1 = 2{/$$} (axiom) 238 | 1. {$$}2 = 3{/$$} (axiom) 239 | 1. {$$}1 = 2{/$$} and {$$}2 = 3{/$$} (from 1 and 2 combined) 240 | 1. {$$}1 = 3{/$$}, from 3 and the inference rule 241 | 242 | We know that in general, {$$}1 = 3{/$$} does not make any sense. But, in the context of the givens above, this proof is valid. 243 | 244 | I> ### Definition 15 245 | I> 246 | I> A mathematical argument consists of a list of propositions. Mathematical arguments are used in order to demonstrate that a claim is true or false. 247 | 248 | I> ### Definition 16 249 | I> 250 | I> A proof is defined as an inferential **argument** for a list of given mathematical propositions. To prove a mathematical fact, we need to show that the conclusion (the goal that we want to prove) logically follows from the hypothesis (list of given propositions). 251 | 252 | For example, to prove that a goal {$$}G{/$$} follows from a set of given propositions {$$}\{ g_1, g_2, \ldots, g_n \}{/$$}, we need to show {$$}(g_1 \land g_2 \land \ldots \land g_n) \to G{/$$}. Note the relation between the implication connective[^ch2n6] (conditional statement) and proofs. 253 | 254 | X> ### Exercise 10 255 | X> 256 | X> With the given axioms of Peano, prove that {$$}1 = S(0){/$$} and {$$}2 = S(S(0)){/$$} are natural numbers. 257 | 258 | X> ### Exercise 11 259 | X> 260 | X> Come up with several axioms and inference rules and do a proof similar to the example above. 261 | 262 | ### 2.3.1. Proofs by truth tables 263 | 264 | Here's one claim: The proposition {$$}A \land B \to B{/$$} is true for **any** values of {$$}A{/$$} and {$$}B{/$$}. 265 | 266 | Q> How do you convince someone that this proposition is really true? 267 | Q> 268 | Q> We can use one proof technique which is to construct a truth table. The way truth tables are constructed for a given statement is to break it down into atoms and then include every subset of the expression. 269 | 270 | For example, to prove the statement {$$}A \land B \to B{/$$}, we can approach as follows: 271 | 272 | | {$$}\textbf{A}{/$$} | {$$}\textbf{B}{/$$} | {$$}A \land B{/$$} | {$$}A \land B \to B{/$$} | 273 | | ------------------- | ------------------- | ------------------ | ------------------------ | 274 | | {$$}\top{/$$} | {$$}\top{/$$} | {$$}\top{/$$} | {$$}\top{/$$} | 275 | | {$$}\top{/$$} | {$$}\bot{/$$} | {$$}\bot{/$$} | {$$}\top{/$$} | 276 | | {$$}\bot{/$$} | {$$}\top{/$$} | {$$}\bot{/$$} | {$$}\top{/$$} | 277 | | {$$}\bot{/$$} | {$$}\bot{/$$} | {$$}\bot{/$$} | {$$}\top{/$$} | 278 | 279 | I> ### Definition 17 280 | I> 281 | I> A mathematical argument is valid iff in the case where all of the propositions are true, the conclusion is also true. 282 | 283 | Note that wherever {$$}A \land B{/$$} is true (the list of given propositions, or premises, or hypothesis) then so is {$$}A \land B \to B{/$$} (the conclusion), which means that this is a valid logical argument according to Definition 17. 284 | 285 | At the lowest level (formal systems), proofs were just a transformation (substitution) from one expression to another. However, at a higher level (logic), the way proofs are done is closely related to the symbols defined in the system. For example, in logic, there's {$$}\land{/$$} so there are specific rules about introducing/eliminating it. 286 | 287 | X> ### Exercise 12 288 | X> 289 | X> Given the two propositions {$$}A \lor B{/$$} and {$$}\lnot B{/$$}, prove (or conclude) {$$}A{/$$} by means of a truth table. 290 | X> 291 | X> Hint: The statement to prove is {$$}((A \lor B) \land \lnot B) \to A{/$$}. 292 | 293 | ### 2.3.2. Three-column proofs 294 | 295 | As we've defined before, an argument is a list of statements. There are several ways to do mathematical proofs. Another one of them is by using the so-called three-column proofs. For this technique, we construct a table with three columns: number of step, step (or expression derived), and reasoning (explanation of how we got to the particular step). 296 | 297 | I> ### Definition 18 298 | I> 299 | I> Modus ponens (method of affirming) and modus tollens (method of denying) are two inference rules in logic. Their definition is as follows: 300 | I> 301 | I> 1. Modus ponens states: If we are given {$$}p \to q{/$$} and {$$}p{/$$}, then we can conclude {$$}q{/$$} 302 | I> 1. Modus tollens states: If we are given {$$}p \to q{/$$} and {$$}\lnot q{/$$}, then we can conclude {$$}\lnot p{/$$} 303 | 304 | For example, given {$$}A \lor B{/$$}, {$$}B \to C{/$$}, {$$}\lnot C{/$$}, prove {$$}A{/$$}. We can approach the proof as follows: 305 | 306 | | No. | Step | Reasoning | 307 | | --- | --------------------------------- | --------- | 308 | | 1 | {$$}A \lor B{/$$} | Given | 309 | | 2 | {$$}B \to C{/$$} | Given | 310 | | 3 | {$$}\lnot C{/$$} | Given | 311 | | 4 | {$$}(B \to C) \land \lnot C{/$$} | 2 and 3 | 312 | | 5 | {$$}\lnot B{/$$} | Modus tollens rule on 4, i.e. {$$}(p \to q \land \lnot q) \to \lnot p{/$$} | 313 | | 6 | {$$}(A \lor B) \land \lnot B{/$$} | 1 and 5 | 314 | | 7 | {$$}A{/$$} | 6, where {$$}p \land \lnot p{/$$} is a contradiction, i.e. invalid argument | 315 | 316 | Q> Proofs with truth tables look a lot easier than column proofs. You just plug in truth values and simplify, where column proofs require planning ahead. Why would we bother with column proofs? 317 | Q> 318 | Q> Proofs with truth tables only work for propositional (zeroth order) theorems - the table method is essentially the decidability algorithm for zeroth-order logic. That's why they are easy (if verbose) and always work, and why column proofs become necessary once we're using quantifiers. 319 | 320 | X> ### Exercise 13 321 | X> 322 | X> Prove {$$}((A \lor B) \land \lnot B) \to A{/$$} using the three-column proof technique. 323 | 324 | ### 2.3.3. Formal proofs 325 | 326 | We've seen how we can construct proofs with truth tables. However, if our statements involve the use of quantifiers, then doing proofs with truth tables is impossible. Three-column proofs, in contrast, contain many details. Ideally, the proof should be short, clear and concise about what we want to prove. Therefore, we will try to prove a statement by means of a formal proof. 327 | 328 | To prove {$$}A \land B \to B{/$$}, we start by assuming that {$$}A \land B{/$$} is true since otherwise, the statement is vacuously true by definition for implication. If {$$}A \land B{/$$} is true, then both {$$}A{/$$} and {$$}B{/$$} are true by definition of `and`, that is, we can conclude {$$}B{/$$}. 329 | 330 | Do not worry if the previous paragraph sounded too magical. There is not much magic involved. Usually, it comes down to using a few rules (or "tricks", if you will) for how we can use given information and achieve our goal. We will summarize these proof techniques next. 331 | 332 | In order to **prove** a goal of form: 333 | 334 | | Goal form | Technique | 335 | | --- | --- | 336 | | {$$}P \to Q{/$$} | Assume that {$$}P{/$$} is true and prove {$$}Q{/$$} | 337 | | {$$}\lnot P{/$$} | Assume that {$$}P{/$$} is true and arrive at a contradiction | 338 | | {$$}P_1 \land P_2 \land \ldots \land P_n{/$$} | Prove each one of {$$}P_1, P_2, \ldots, P_n{/$$} separately | 339 | | {$$}P_1 \lor P_2 \lor \ldots \lor P_n{/$$} | Prove that at least one of {$$}P_1, P_2, \ldots, P_n{/$$} | 340 | | {$$}P \leftrightarrow Q{/$$} | Prove both {$$}P \to Q{/$$} and {$$}Q \to P{/$$} | 341 | | {$$}\forall x, P(x){/$$} | Assume that {$$}x{/$$} is an arbitrary object and prove that {$$}P(x){/$$} | 342 | | {$$}\exists x, P(x){/$$} | Find an {$$}x{/$$} such that {$$}P(x){/$$} is true | 343 | | {$$}\exists! x, P(x){/$$}[^ch2n7] | Prove {$$}\exists x, P(x){/$$} (existence) and | 344 | | | {$$}\forall x \forall y, (P(x) \land P(y) \to x = y){/$$} (uniqueness) separately | 345 | 346 | In order to **use** a given of form: 347 | 348 | | Given form | Technique | 349 | | --- | --- | 350 | | {$$}P \to Q{/$$} | If {$$}P{/$$} is also given, then conclude that {$$}Q{/$$} (by modus ponens) | 351 | | {$$}\lnot P{/$$} | If {$$}P{/$$} can be proven true, then conclude a contradiction | 352 | | {$$}P_1 \land P_2 \land \ldots \land P_n{/$$} | Treat each one of {$$}P_1, P_2, \ldots, P_n{/$$} as a given | 353 | | {$$}P_1 \lor P_2 \lor \ldots \lor P_n{/$$} | Use proof by cases, where in each case you assume one of {$$}P_1, P_2, \ldots, P_n{/$$} | 354 | | {$$}P \leftrightarrow Q{/$$} | Conclude both {$$}P \to Q{/$$} and {$$}Q \to P{/$$} | 355 | | {$$}\forall x, P(x){/$$} | For any {$$}x{/$$}, conclude that {$$}P(x){/$$} | 356 | | {$$}\exists x, P(x){/$$} | Introduce a new variable, say {$$}x_0{/$$} so that {$$}P(x_0){/$$} is true | 357 | | {$$}\exists! x, P(x){/$$} | Introduce a new variable, say {$$}x_1{/$$} so that {$$}P(x_1){/$$} is true. | 358 | | | Can also use that {$$}\forall x \forall y, (P(x) \land P(y) \to x = y){/$$} | 359 | 360 | For example, we can use these techniques to do the following proofs: 361 | 362 | 1. {$$}A \land B \to A \lor B{/$$} - To prove this goal, we will assume {$$}A \land B{/$$} and use proof by cases: 363 | 1. Proof for {$$}A{/$$}: Since we're given {$$}A \land B{/$$}, we are also given {$$}A{/$$}. Thus, {$$}A{/$$} 364 | 1. Proof for {$$}B{/$$}: Since we're given {$$}A \land B{/$$}, we are also given {$$}B{/$$}. Thus, {$$}B{/$$} 365 | 1. Thus, {$$}A \lor B{/$$} 366 | 1. {$$}A \land B \leftrightarrow B \land A{/$$} - To prove this goal, we will prove both sides for the implications: 367 | 1. Proof for {$$}A \land B \to B \land A{/$$}: We can assume that {$$}A \land B{/$$}, thus we have both {$$}A{/$$} and {$$}B{/$$}. To prove the goal of {$$}B \land A{/$$}, we need to prove {$$}B{/$$} and {$$}A{/$$} separately, which we already have as given. 368 | 1. Proof for {$$}B \land A \to A \land B{/$$}: We can assume that {$$}B \land A{/$$}, thus we have both {$$}B{/$$} and {$$}A{/$$}. To prove the goal of {$$}A \land B{/$$}, we need to prove {$$}A{/$$} and {$$}B{/$$} separately, which we already have as given. 369 | 1. Thus, {$$}A \land B \leftrightarrow B \land A{/$$} 370 | 1. {$$}\forall x, x = x{/$$} - We know that for any number {$$}x{/$$}, this number is equal to itself. Thus, {$$}\forall x, x = x{/$$}. 371 | 1. {$$}\exists x, x > 0{/$$} - To prove this, we only need to find an {$$}x{/$$} such that it is greater than 0. One valid example is 1. Thus, {$$}\exists x, x > 0{/$$}. 372 | 373 | X> ### Exercise 14 374 | X> 375 | X> Prove {$$}((A \lor B) \land \lnot B) \to A{/$$} by means of a formal proof. 376 | 377 | X> ### Exercise 15 378 | X> 379 | X> We've used the rules modus tollens and modus ponens without giving an actual proof for them. Try to prove by yourself that these two rules hold, both by constructing a truth table and a three-column proof: 380 | X> 381 | X> 1. Modus tollens: {$$}((p \to q) \land \lnot q) \to \lnot p{/$$} 382 | X> 1. Modus ponens: {$$}((p \to q) \land p) \to q{/$$} 383 | 384 | X> ### Exercise 16 385 | X> 386 | X> Prove the formal proofs 1 and 2 from the examples above using both truth tables and three-column proofs techniques. 387 | 388 | X> ### Exercise 17 389 | X> 390 | X> Think of a proposition and try to prove it by means of a formal proof. 391 | 392 | ### 2.3.4. Mathematical induction 393 | 394 | I> ### Definition 19 395 | I> 396 | I> Recursive functions are functions that refer to themselves. We have the following properties for such functions: 397 | I> 398 | I> 1. A simple base case (or cases) - a terminating case that returns a value without using recursion 399 | I> 1. A set of rules that reduce the other cases towards the base case 400 | 401 | I> ### Definition 20 402 | I> 403 | I> Mathematical induction is a proof method that is used to prove that a predicate {$$}P(n){/$$} is true for all natural numbers {$$}n{/$$}. It consists of proving two parts: a base case and an inductive step. 404 | I> 405 | I> 1. For the **base** case we need to show that what we want to prove {$$}P(n){/$$} is true for some starting value {$$}k{/$$}, which is usually zero. 406 | I> 1. For the **inductive** step, we need to prove that {$$}P(n) \to P(n+1){/$$}, that is, if we assume that {$$}P(n){/$$} is true, then {$$}P(n+1){/$$} must follow as a consequence. 407 | I> 408 | I> After proving the two parts, we can conclude that {$$}P(n){/$$} holds for all natural numbers. The formula that we need to prove is {$$}P(0) \land ( P(n) \to P(n+1) ){/$$}. 409 | 410 | To understand why mathematical induction works, as an example it is best to visualize dominoes arranged in a sequence. If we push the first domino, it will push the second, which will push the third, and so on to infinity. That is, if we position the dominoes such that if one falls it will push the next one, i.e. {$$}P(n){/$$} implies {$$}P(n+1){/$$}, and we push the first one {$$}P(0){/$$}, then all the dominoes will fall, i.e. {$$}P(n){/$$} is true in general. 411 | 412 | I> ### Definition 21 413 | I> 414 | I> We are given this recursive definition for adding numbers: 415 | I> 416 | I> 1. Zero is a left identity for addition, that is {$$}n = 0 + n{/$$} 417 | I> 1. {$$}S(m) + n = S(m + n){/$$}, where {$$}S{/$$} is the successor function, that is {$$}S(0) = 1, S(1) = 2{/$$}, etc. 418 | 419 | For example, in order to prove that {$$}\forall n, n + 0 = n{/$$} in the system of Peano's axioms, we can proceed by induction (which is an axiom in this system). For the base case, we have that {$$}0 + 0 = 0{/$$}, which is true (by definition of adding numbers, for {$$}n = 0{/$$}). For the inductive step, we first assume that {$$}n + 0 = n{/$$} is true, and prove that {$$}S(n) + 0 = S(n){/$$}. By definition of addition, we have {$$}S(n) + 0 = S(n + 0){/$$}. If we use the inductive hypothesis we have {$$}S(n + 0) = S(n){/$$}, which is what we needed to show. 420 | 421 | [^ch2n1]: Since unrestricted quantification leads to inconsistency, higher-order logic is an attempt to avoid this. We will look into Russell's paradox later as an example. 422 | 423 | [^ch2n2]: This means that there is a decidability algorithm - an algorithm that will always return a correct value (e.g. true or false), instead of looping infinitely or producing a wrong answer. 424 | 425 | [^ch2n3]: It is worth noting that in set theory, {$$}P{/$$} would be a subset of a relation, i.e. {$$}P \subseteq A \times \{ T, F \}{/$$}, where {$$}A{/$$} is a set of some inputs, for example `Salad` and `Rock`. When working with other systems we need to be careful, as this is not the case with first-order logic. In the case of first-order logic, we have {$$}P(Salad) = \top{/$$}, {$$}P(Rock) = \bot{/$$}, etc as atomic statements, not mathematical functions (i.e. they cannot be broken down into smaller statements). This is what makes first-order logic independent of set theory. In addition, functions have a nice characterization that is dual to the concepts of "one-to-one" (total) and "onto" (well-defined). 426 | 427 | [^ch2n4]: In other words, a function is a subset of all combinations of ordered pairs whose first element is an element of {$$}A{/$$} and the second element is an element of {$$}B{/$$}. 428 | 429 | [^ch2n5]: A similar statement can be made about programming, but we will cover an interesting case in Appendix C related to **pure** and **impure** functions. 430 | 431 | [^ch2n6]: The turnstile symbol is similar to implication. It is denoted as {$$}\Gamma \vdash A{/$$}, where {$$}\Gamma{/$$} is a set of statements and {$$}A{/$$} is a conclusion. It is {$$}\top{/$$} iff it is impossible for all statements in {$$}\Gamma{/$$} to be {$$}\top{/$$}, and {$$}A{/$$} to be {$$}\bot{/$$}. The reason why we have both implication and entailment is that the former is a well-formed formula (that is, the expression belongs to the object language), while the latter is not a well-formed formula, rather an expression in the metalanguage and works upon proofs (instead of objects). 432 | 433 | [^ch2n7]: The notation {$$}\exists!{/$$} stands for unique existential quantifier. It means that **only one** object fulfills the predicate, as opposed to {$$}\exists{/$$}, which states that **at least one** object fulfills the predicate. 434 | -------------------------------------------------------------------------------- /manuscript/chapter3.md: -------------------------------------------------------------------------------- 1 | # 3. Type theory 2 | 3 | Some type theories can serve as an alternative foundation of mathematics, as opposed to standard set theory. One such well-known type theory is Martin-Löf's intuitionistic theory of types, which is an extension of Alonzo Church's simply-typed {$$}\lambda{/$$}-calculus. Before we begin working with Idris, we will get familiar with these theories, upon which Idris is built as a language. 4 | 5 | I> ### Definition 1 6 | I> 7 | I> Type theory is defined as a class of formal systems. In these theories, every object is joined with a type, and operations upon these objects are constrained by the joined types. In order to say that {$$}x{/$$} is of type {$$}\text{X}{/$$}, we denote {$$}x : \text{X}{/$$}. Functions are a primitive concept in type theory[^ch3n1]. 8 | 9 | For example, with {$$}1 : \text{Nat}, 2 : \text{Nat}{/$$} we can say that 1 and 2 are of type {$$}\text{Nat}{/$$}, that is natural numbers. An operation (function) {$$}+ : \text{Nat} \to \text{Nat} \to \text{Nat}{/$$} is interpreted as a function which takes two objects of type {$$}\text{Nat}{/$$} and returns an object of type {$$}\text{Nat}{/$$}. 10 | 11 | I> ### Definition 2 12 | I> 13 | I> In type theory, a type constructor is a function that builds new types from old ones. This function accepts types as parameters and as a result, it returns a new type. 14 | 15 | Idris supports algebraic data types. These data types are a kind of complex types, that is, types constructed by combining other types. Two classes of algebraic types are **product types** and **sum types**. 16 | 17 | I> ### Definition 3 18 | I> 19 | I> Algebraic data types are types where we can additionally specify the form for each of the elements. They are called "algebraic" in the sense that the types are constructed using algebraic operations. The algebra here is sum and product: 20 | I> 21 | I> 1. Sum (union) is an alternation. It is denoted as {$$}\text{A | B}{/$$} and it means that a constructed value is either of type A or B 22 | I> 1. Product is a combination. It is denoted as {$$}\text{A B}{/$$} and it means that a constructed value is a pair where the first element is of type A, and the second element is of type B 23 | I> 24 | I> To understand the algebra they capture, we denote with {$$}|\text{A}|{/$$} the number of possible values of type {$$}\text{A}{/$$}. When we create an algebraic sum we have {$$}|\text{A | B}| = |\text{A}| + |\text{B}|{/$$}. Similarly, for algebraic product we have {$$}|\text{A B}| = |\text{A}| * |\text{B}|{/$$}. 25 | 26 | As an example, we can assume that we have two types: {$$}\text{Nat}{/$$} for natural numbers, and {$$}\text{Real}{/$$} for real numbers. Using sum (union) we can construct a new type {$$}\text{Nat | Real}{/$$}. Valid values of this type are {$$}1 : \text{Nat | Real}{/$$}, {$$}3.14 : \text{Nat | Real}{/$$}, etc. Using product we can construct a new type {$$}\text{Nat Real}{/$$}. Valid values of this type are {$$}1 \ 1.5 : \text{Nat Real}{/$$}, {$$}2 \ 3.14 : \text{Nat Real}{/$$}, etc. With this, sums and products can be combined and thus more complex data structures can be defined. 27 | 28 | The type {$$}\text{Bool}{/$$} has two possible values: {$$}True{/$$} and {$$}False{/$$}. Thus, {$$}|\text{Bool}| = 2{/$$}. The type {$$}\text{Unit}{/$$} (equivalent to {$$}(){/$$}) has one possible value: {$$}Unit{/$$}. We can now form a sum type {$$}\text{Bool | Unit}{/$$} which has length 3 with values {$$}True, False, Unit{/$$}. Additionally, the product type {$$}\text{Bool Unit}{/$$} has length 2 with values {$$}True \ Unit, False \ Unit{/$$}. 29 | 30 | Besides the sum and product, there is another important operation called **exponentiation**. This corresponds to functions, so a type {$$}a \to b{/$$} has {$$}|b|^{|a|}{/$$} possible values. In section 3.3 we will see how this algebra can be generalized. 31 | 32 | Finally, Idris supports dependent types[^ch3n2]. These kinds of types are so powerful, they can encode most properties of programs and with their help, Idris can prove invariants at compile-time. As we will see in section 4.2 types also allow us to encode mathematical proofs, which brings computer programs closer to mathematical proofs. As a consequence, this allows us to prove properties (e.g. specifications) about our software[^ch3n3]. 33 | 34 | Q> Why are types useful? 35 | Q> 36 | Q> Russell's paradox (per the mathematician Bertrand Russell) states the following: In a village in which there is only one barber, there is a rule according to which the barber shaves everyone who don't shave themselves, and no-one else. Now, who shaves the barber? Suppose the barber shaves himself. Then, he's one of those who shave themselves, but the barber shaves only those who do not shave themselves, which is a contradiction. Alternatively, if we assume that the barber does not shave himself, then he is in the group of people whom which the barber shaves, which again is a contradiction. Apparently then the barber does not shave himself, but he also doesn't _not_ shave himself - a paradox. 37 | Q> 38 | Q> Some set theories are affected by Russell's paradox. As a response to this, between 1902 and 1908, Bertrand Russell himself proposed different type theories as an attempt to resolve the issue. By joining types to values, we avoid the paradox because in this theory every set is defined as having elements from a distinct type, for example, {$$}\text{Type 1}{/$$}. Elements from {$$}\text{Type 1}{/$$} can be included in a different set, say, elements of {$$}\text{Type 2}{/$$}, and so forth. Thus, the paradox is no longer an issue since the set of elements of {$$}\text{Type 1}{/$$} cannot be contained in their own set, since the types do not match. In a way, we're adding hierarchy to sets in order to resolve the issue of "self-referential" sets. This is also the case with Idris, where we have that {$$}\text{Type : Type 1 : Type 2}{/$$}, etc. 39 | Q> 40 | Q> Thus, for Russell's paradox specifically, if we set the type of a person to be {$$}\text{P}{/$$}, then the list of people would be of type {$$}\text{List P}{/$$}. However, there is no way to express {$$}\{ \text{P} \}{/$$} such that {$$}\text{P} \in \text{P}{/$$}, since {$$}\text{List P}{/$$} only contains elements of type {$$}\text{P}{/$$}, and not {$$}\text{List P}{/$$}. 41 | 42 | ## 3.1. Lambda calculus 43 | 44 | Lambda calculus is a formal system for expressing computation[^ch3n4]. The grammar rules are divided in two parts: function abstraction and function application. Function abstraction defines what a function does, and function application "computes" a function. For example {$$}f(x) = x + 1{/$$} is a function abstraction and {$$}f(3){/$$} is a function application. The equality sign {$$}={/$$} is replaced with a dot, and instead of writing {$$}f(x){/$$} we write {$$}\lambda x{/$$}. To represent {$$}f(x) = x + 1{/$$} we write {$$}\lambda x . x + 1{/$$}. Parentheses allow us to specify the order of evaluation. 45 | 46 | I> ### Definition 4 47 | I> 48 | I> The set of symbols for the lambda calculus is defined as: 49 | I> 50 | I> 1. There are variables {$$}v_1, v_2, \ldots{/$$} 51 | I> 1. There are only two abstract symbols: {$$}.{/$$} and {$$}\lambda{/$$} 52 | I> 1. There are parentheses: {$$}({/$$} and {$$}){/$$} 53 | I> 54 | I> The set of grammar rules {$$}\Lambda{/$$} for well-formed expressions is defined as: 55 | I> 56 | I> 1. If {$$}x{/$$} is a variable, then {$$}x \in \Lambda{/$$} 57 | I> 1. If {$$}x{/$$} is a variable and {$$}M \in \Lambda{/$$}, then {$$}(\lambda x.M) \in \Lambda{/$$} (rule of abstraction) 58 | I> 1. If {$$}M, N \in \Lambda{/$$}, then {$$}(M \ N) \in \Lambda{/$$} (rule of application) 59 | 60 | Some examples of well-formed expressions are {$$}\lambda f \ x . f \ x{/$$} and {$$}\lambda f \ x . f \ (f \ x){/$$}. In fact, we can encode numbers this way. The first expression can be thought of as the number one, and the second as the number two. In other words, the number 1 is defined roughly as {$$}f(x){/$$}, and 2 as {$$}f(f(x)){/$$}. Note that {$$}f{/$$} and {$$}x{/$$} do not have special definitions, they are abstract objects. This encoding is known as the Church encoding. Operations on numbers (plus, minus, etc) and other data such as Booleans and lists can also be encoded similarly. 61 | 62 | Note that {$$}\lambda f \ x . f \ x{/$$} is shorthand for {$$}\lambda f . \lambda x . f \ x{/$$}. 63 | 64 | X> ### Exercise 1 65 | X> 66 | X> Convince yourself that the expression {$$}\lambda f \ x . f \ x{/$$} is a well-formed expression by writing down each one of the grammar rules used. 67 | 68 | ### 3.1.1. Term reduction 69 | 70 | Every variable in a lambda expression can be characterized as either _free_ or _bound_ in that expression. 71 | 72 | I> ### Definition 5 73 | I> 74 | I> A variable in a lambda expression is called _free_ if it does not appear inside at least one lambda body where it is found in the abstraction. Alternatively, if it does appear inside at least one lambda body, then the variable is _bound_ at the innermost such lambda abstraction. 75 | 76 | This definition of "bound" corresponds roughly to the concept of _scope_ in many programming languages. Lambda expressions introduce a new scope in which their argument variables are bound. 77 | 78 | For example, in the expression {$$}\lambda y.x \ y{/$$} we have that {$$}y{/$$} is a bound variable, and {$$}x{/$$} is a free one. Variable binding in lambda calculus is subtle but important, so let's see some trickier examples. 79 | 80 | 1. In {$$}x(\lambda x.x){/$$}, the leftmost {$$}x{/$$} is free, while the rightmost {$$}x{/$$} is bound by the lambda 81 | 1. In {$$}(\lambda x.x)(\lambda x.x){/$$}, both occurrences of {$$}x{/$$} are bound; the first at the left lambda, and the second at the right lambda 82 | 1. In {$$}\lambda x.(\lambda x.x){/$$} the sole occurrence of {$$}x{/$$} is certainly bound. Now there are two potential "binding sites" - the inner lambda and the outer lambda. Given a choice like this, we always say the variable is bound at the innermost lambda 83 | 84 | The distinction between free and bound variables becomes important when we ask whether two different lambda expressions are "equal". For instance, consider the two expressions {$$}\lambda x.x{/$$} and {$$}\lambda y.y{/$$}. Syntactically these are not the same; they use different characters for the variable. But semantically they are identical because in lambda calculus variables bound by a lambda are "dummy" variables whose exact names are not important. When two lambda expressions differ only by a consistent renaming of the bound variables like this we say they are _alpha equivalent_. 85 | 86 | There are two other useful notions of semantic equivalence for lambda expressions: beta and eta equivalence. 87 | 88 | I> ### Definition 6 89 | I> 90 | I> The rules of terms reduction (inference rules) allow us to compute (simplify) lambda expressions. There are three types of reduction: 91 | I> 92 | I> 1. {$$}\alpha{/$$} - (alpha) reduction: Renaming bound variables 93 | I> 1. {$$}\beta{/$$} - (beta) reduction: Applying arguments to functions 94 | I> 1. {$$}\eta{/$$} - (eta) reduction: Two functions are "equal" iff they return the same result for all arguments 95 | 96 | For example, for the expression {$$}(\lambda x . f \ x) \ y{/$$}, we can use alpha reduction to get to {$$}(\lambda z . f \ z) \ y{/$$}, by changing {$$}x{/$$} to {$$}z{/$$}. Using beta reduction, the expression can further be reduced to just {$$}f \ y{/$$}, since we "consumed" the {$$}z{/$$} by removing it from the abstraction and wherever it occurred in the body we just replaced it with what was applied to it, that is {$$}y{/$$}. Finally, with eta reduction, we can rewrite {$$}(\lambda x . f \ x){/$$} to just {$$}f{/$$}, since they are equivalent. 97 | 98 | Given these rules, we can define the successor function as {$$}\text{SUCC} = \lambda n\ f\ x\ . f\ (n\ f\ x){/$$}. Now we can try to apply 1 to {$$}\text{SUCC}{/$$}: 99 | 100 | 1. Evaluating {$$}\text{SUCC} \ 1 ={/$$} 101 | 1. Substitute the very own definitions of {$$}\text{SUCC}{/$$} and 1: {$$}(\lambda n \ f \ x . f \ (n \ f \ x)) \ (\lambda f \ x . f \ x) ={/$$} 102 | 1. Apply 1 to {$$}\text{SUCC}{/$$} i.e. "consume" {$$}n{/$$} by beta reduction: {$$}\lambda f \ x . f \ ((\lambda f \ x . f \ x) \ f \ x) ={/$$} 103 | 1. Finally, apply {$$}f{/$$} and {$$}x{/$$} to a function that accepts {$$}f{/$$} and {$$}x{/$$} (which is just the body of the abstraction): {$$}\lambda f \ x . f \ (f \ x) = 2{/$$} 104 | 105 | I> ### Definition 7 106 | I> 107 | I> A fixed-point combinator is any function that satisfies the equation {$$}\text{fix} \ f = f \ (\text{fix} \ f){/$$}. 108 | 109 | One example of such a combinator is {$$}\text{Y} = \lambda f . (\lambda x . f\ (x\ x))\ (\lambda x . f\ (x\ x)){/$$}. This definition satisfies {$$}\text{Y} \ f = f \ (\text{Y} \ f){/$$}. This combinator allows for recursion in lambda calculus. Since it is impossible to refer to the function within its body, recursion can only be achieved by applying parameters to a function which is what this combinator does. 110 | 111 | X> ### Exercise 2 112 | X> 113 | X> Evaluate {$$}\text{SUCC} \ 2{/$$} to find out the definition of number 3. 114 | 115 | X> ### Exercise 3 116 | X> 117 | X> Come up with your own functions that operate on the Church numerals. It can be as simple as returning the same number, or a constant one. 118 | 119 | ## 3.2. Lambda calculus with types 120 | 121 | So far we've been discussing the _untyped_ lambda calculus, but it is possible to augment the rules of lambda calculus so that variables are _typed_. This makes it possible to add an additional statically checked layer of semantics to a lambda expression so we can ensure that values are used in a consistent and meaningful way. There are several ways to add types to lambda calculus, and our goal is to approach the full _dependent_ type system of Idris. As a stepping stone, we first consider _simple_ types. 122 | 123 | I> ### Definition 8 124 | I> 125 | I> Simply typed lambda calculus is a type theory that adds types to lambda calculus. It joins the system with a unique type constructor {$$}\to{/$$} which constructs types for functions. The formal definition and the set of lambda expressions are similar to that of lambda calculus, with the addition of types. 126 | I> 127 | I> The set of symbols for this system is defined as: 128 | I> 129 | I> 1. There are variables {$$}v_1, v_2, \ldots{/$$} 130 | I> 1. There are only two abstract symbols: {$$}.{/$$} and {$$}\lambda{/$$} 131 | I> 1. There are parentheses: {$$}({/$$} and {$$}){/$$} 132 | I> 133 | I> The set of grammar rules {$$}\Lambda{/$$} for well-formed expressions is defined as: 134 | I> 135 | I> 1. If {$$}x{/$$} is a variable, then {$$}x \in \Lambda{/$$} 136 | I> 1. If {$$}x{/$$} is a variable and {$$}M \in \Lambda{/$$}, then {$$}(\lambda x.M) \in \Lambda{/$$} (rule of abstraction) 137 | I> 1. If {$$}M, N \in \Lambda{/$$}, then {$$}(M \ N) \in \Lambda{/$$} (rule of application) 138 | I> 1. If {$$}x{/$$} is a variable, {$$}\text{T}{/$$} is a type, and {$$}M \in \Lambda{/$$}, then {$$}(\lambda x: \text{T} .M) \in \Lambda{/$$} 139 | I> 1. If {$$}x{/$$} is a variable and {$$}\text{T}{/$$} is a type, then {$$}x: \text{T} \in \Lambda{/$$} 140 | I> 141 | I> There is a single type constructor: 142 | I> 143 | I> 1. For some type {$$}A{/$$}, the type constructor {$$}T{/$$} is defined as {$$}\text{A | T} \to \text{T}{/$$} 144 | 145 | That is, an expression in this system can additionally be an abstraction with {$$}x{/$$} having joined a type (rule 4) or an expression of a variable having joined a type {$$}\text{T}{/$$} (rule 5), where our type constructor is a sum type and it says that we either have primitive types or a way to form new types. In an attempt to re-define Church numerals and the successor function we have to be careful as the types of these definitions have to match. Let's recall the Church numerals: 146 | 147 | 1. Number 1, i.e. {$$}\lambda f \ x . f \ x{/$$} 148 | 1. Number 2, i.e. {$$}\lambda f \ x . f \ (f \ x){/$$} 149 | 150 | Given the definition of 1, its type must have the form {$$}(\text{a} \to \text{b}) \to \text{a} \to \text{b}{/$$} for some values {$$}\text{a}{/$$} and {$$}\text{b}{/$$}. We are expecting to be able to apply {$$}f{/$$} to {$$}x{/$$}, and so if {$$}x : \text{a}{/$$} then {$$}f : \text{a} \to \text{b}{/$$} in order for our types to match correctly. With similar reasoning, we have the same type for 2. At this point, we have the type of {$$}(\text{a} \to \text{b}) \to \text{a} \to \text{b}{/$$}. Finally, with the given definition of 2, we can note that expressions of type {$$}\text{b}{/$$} need to be able to be passed to functions of type {$$}\text{a} \to \text{b}{/$$}, since the result of applying {$$}f{/$$} to {$$}x{/$$} serves as the argument of {$$}f{/$$}. The most general way for that to be true is if {$$}a = b{/$$}. As a result we have the type {$$}(\text{a} \to \text{a}) \to \text{a} \to \text{a}{/$$}. We can denote this type definition to be {$$}\text{Nat}{/$$}. We have the following for our numbers: 151 | 152 | 1. Number 1 becomes {$$}\lambda [f:(\text{a} \to \text{a})] \ [x : \text{a}] . f \ x : \text{Nat}{/$$} 153 | 1. Number 2 becomes {$$}\lambda [f:(\text{a} \to \text{a})] \ [x : \text{a}] . f \ (f \ x) : \text{Nat}{/$$} 154 | 155 | The (typed) successor function is: {$$}\text{SUCC} = \lambda [n:\text{Nat}]\ [f:(\text{a} \to \text{a})] \ [x : \text{a}] . f\ (n\ f\ x) : \text{Nat} \to \text{Nat}{/$$}. 156 | 157 | Simply typed lambda calculus sits in a sweet spot on the spectrum of type systems. It is powerful enough to do useful work, but also simple enough to have strong properties. Simple types have limitations when compared to full dependent types, discussed in the next section, but their great trade-off is the existence of a full _inference algorithm_. The strategy we used above to determine the type of a lambda expression from the bottom up is the core of the widely used Hindley-Damas-Milner algorithm for type inference, which can automatically _infer_ the simple type of a lambda expression without requiring any explicit type annotations from the programmer. 158 | 159 | Fixed-point combinators do not exist in the simply-typed lambda calculus[^ch3n5]. To see why, consider the function {$$}\text{fix} = \lambda [f:\text{a} \to \text{a}] : \text{a}{/$$}. We can apply {$$}\text{fix}{/$$} to some element {$$}x : \text{a} \to \text{a}{/$$}. Thus, {$$}\text{fix} \ x : \text{a}{/$$}, but {$$}x = \text{fix} \ x{/$$} is a type error because the infinite type {$$}\text{a} \to \text{a} = \text{a}{/$$} cannot be matched. 160 | 161 | X> ### Exercise 4 162 | X> 163 | X> Come up with a definition of the typed number 3. 164 | 165 | X> ### Exercise 5 166 | X> 167 | X> Apply the typed 1 to {$$}\text{SUCC}{/$$} and confirm that the result is 2. Make sure you confirm that the types also match in the process of evaluation. 168 | 169 | X> ### Exercise 6 170 | X> 171 | X> In Exercise 3 you were asked to come up with a function. Try to figure out the type of this function or, if not applicable, come up with a new function and then figure out its type using the reasoning above. 172 | 173 | ## 3.3. Dependent types 174 | 175 | In the simply-typed lambda calculus, _values_ and _types_ are fundamentally different kinds of things that are related only by the "has type" predicate, {$$}:{/$$}. Values are allowed to depend on values - these are lambda abstractions. And types are allowed to depend on types - these are arrow types. But types are not allowed to depend on values. A _dependent typing_ system lifts this restriction. 176 | 177 | I> ### Definition 9 178 | I> 179 | I> Dependent types are types that depend on values. 180 | 181 | A list of numbers is a type ({$$}\text{List}\ \text{Nat}{/$$}, for example). However, a list of numbers whose length is bounded by some constant, or whose entries are increasing, is a dependent type. 182 | 183 | I> ### Definition 10 184 | I> 185 | I> A dependent product type is a collection of types {$$}B : \text{A} \to U{/$$} where for each element {$$}a : \text{A}{/$$}, there's an assigned type {$$}B(a) : U{/$$}, where {$$}U{/$$} is a universe of types[^ch3n6]. We say that {$$}B(a){/$$} varies with {$$}a{/$$}. It is denoted as {$$}\Pi(x : \text{A}), B(x){/$$} or {$$}\prod\limits_{x \ : \ \text{A} } B(x){/$$}. 186 | 187 | This definition might seem a bit scary and tricky to grasp, but it really is simple, and it is best to see it in action through the following example: 188 | 189 | 1. Our universe of types contains all possible types. For example, {$$}\text{Type}{/$$}, {$$}\text{Nat}{/$$}, etc, so {$$}U = \{ \text{Type}, \text{Nat}, \text{List n}, \ldots \}{/$$} 190 | 1. Our collection of types of interest is {$$}\text{List n}{/$$}, which represents a list of {$$}\text{n}{/$$} elements. That is, {$$}A = \{ \text{List n} \}{/$$} 191 | 192 | The definition states that in the universe {$$}U{/$$}, there exists a function {$$}B(\text{n}) = \text{List n}{/$$}. {$$}B{/$$} is the collection of functions which given a number {$$}n{/$$}, will return a list of {$$}n{/$$} numbers. For example, we have the following lists: 193 | 194 | 1. List of 1 element: {$$}[1] : B(1){/$$}, that is {$$}[1] : \text{List 1}{/$$} 195 | 1. List of 2 elements: {$$}[1, 2] : \text{List 2}{/$$} 196 | 1. List of n elements: {$$}[1, 2, \ldots, n] : \text{List n}{/$$} 197 | 198 | In general, we have a function that takes an {$$}\text{n}{/$$} and produces a {$$}\text{List n}{/$$}, that is, {$$}f : \Pi(x : \text{Nat}), \text{n} \to \text{List n}{/$$} or simply {$$}f : \text{n} \to \text{List n}{/$$}, where the possible types for it are {$$}f : 1 \to \text{List 1}{/$$} and {$$}f : 2 \to \text{List 2}{/$$}, etc. We've just constructed our first dependent type! 199 | 200 | I> ### Definition 11 201 | I> 202 | I> A dependent sum type can be used to represent indexed pairs, where the type of the second element depends on the type of the first element. That is, if we have {$$}a : \text{A}{/$$} and {$$}b : B(\text{a}){/$$}, then this makes a sum type. We denote it as {$$}\Sigma(x : \text{A}), B(x){/$$} or {$$}\sum\limits_{x \ : \ \text{A} } B(x){/$$}. 203 | 204 | For example, if we set {$$}A = \text{Nat}{/$$}, and {$$}B(\text{a}) = \text{List a}{/$$}, then we form the dependent sum type {$$}\Sigma(x : \text{Nat}), \text{List x}{/$$}. Possible types for it are {$$}(1, \text{List 1}){/$$} or {$$}(2, \text{List 2}){/$$}, etc. For example, we can construct the following pairs: {$$}(1, [1]), (2, [1, 2]), (3, [1, 2, 3]){/$$}, etc. 205 | 206 | Dependent types generalize product and exponentiation. Namely, {$$}\Sigma{/$$} (multiplication) is a generalization of the product type where the type of the second element depends on the first element, and {$$}\Pi{/$$} (exponentiation) is a generalization of the exponentiation type where the resulting type of a function depends on its input. 207 | 208 | X> ### Exercise 7 209 | X> 210 | X> Think of a way to construct a different dependent product type and express it by using the reasoning above. 211 | 212 | X> ### Exercise 8 213 | X> 214 | X> Think of a way to construct a different dependent sum type and express it using the reasoning above. 215 | 216 | ## 3.4. Intuitionistic theory of types 217 | 218 | The core "construct" in Idris are types. As we've seen, foundations are based on type theory. As we've also seen, in classical mathematical logic we have sets and propositions, according to set theory. 219 | 220 | The intuitionistic theory of types (or constructive type theory) offers an alternative foundation to mathematics. This theory was introduced by Martin-Löf, a Swedish mathematician in 1972. It is based on the isomorphism (or "equality") that propositions are types. 221 | 222 | Proving a theorem in this system consists of constructing[^ch3n7] (or providing evidence for) a particular object. If we want to prove something about a type {$$}\text{A}{/$$} and we know that {$$}a : \text{A}{/$$}, then {$$}a{/$$} is one proof for {$$}\text{A}{/$$}. Note how we say one proof, because there can be many other elements of type {$$}\text{A}{/$$}. 223 | 224 | Propositions can also be defined through types. For example in order to prove that {$$}4 = 4{/$$}, we need to find an object {$$}x{/$$} of type {$$}\text{4 = 4}{/$$}, that is {$$}x : \text{4 = 4}{/$$}. One such object is {$$}refl{/$$} (which can be thought of as an axiom), which stands for reflexivity, which states that {$$}x = x{/$$} for all {$$}x{/$$}. 225 | 226 | One thing worth noting is that in Idris there are "two" types of truths: {$$}\text{Bool}{/$$} and {$$}\text{Type}{/$$}. Even though there is some similarity (in terms of proofs), in Idris they are fundamentally different. The type {$$}\text{Bool}{/$$} can have a value of {$$}True{/$$} or {$$}False{/$$}, while the type {$$}\text{Type}{/$$} is either provable or not provable[^ch3n8]. 227 | 228 | This system is useful since with the use of computer algorithms we can find a constructive proof for some object (assuming it exists). As a consequence, this is why it can be considered as a way to make a programming language act like a proof-assistant. 229 | 230 | I> ### Definition 12 231 | I> 232 | I> The set of grammar rules {$$}\Lambda{/$$} for well-formed expressions is defined as: 233 | I> 234 | I> 1. {$$}s : \text{Type} \in \Lambda{/$$} means that {$$}s{/$$} is a well-formed type 235 | I> 1. {$$}t : \text{s} \in \Lambda{/$$} means that {$$}t{/$$} is a well-formed expression of type {$$}\text{s}{/$$} 236 | I> 1. {$$}\text{s} = \text{t} \in \Lambda{/$$} means that {$$}\text{s}{/$$} and {$$}\text{t}{/$$} are the same type 237 | I> 1. {$$}t = u : \text{s} \in \Lambda{/$$} means that {$$}t{/$$} and {$$}u{/$$} are equal expressions of type {$$}\text{s}{/$$} 238 | I> 239 | I> The type constructors are: 240 | I> 241 | I> 1. {$$}\Pi{/$$} types and {$$}\Sigma{/$$} types, as we've discussed them earlier 242 | I> 1. Finite types, for example the nullary (empty) type 0 or {$$}\bot{/$$}, the unary type 1 or {$$}\top{/$$}, and the boolean type 2 243 | I> 1. The equality type, where for given {$$}a, b : \text{A}{/$$}, the expression {$$}a = b{/$$} represents proof of equality. There is a canonical element {$$}a = a{/$$}, that is, an "axiom" for the reflexivity proof: {$$}refl : \Pi (a : \text{A}) \ a = a{/$$} 244 | I> 1. Inductive (or recursive) types. This way we can implement a special form of recursion - one that always terminates, and we will see the importance of this with total functions. Additionally we can implement product and sum types, which encode conjunction and disjunction respectively 245 | I> 246 | I> The inference rules are: 247 | I> 248 | I> 1. The rule of type equality which states that if an object is of a type {$$}\text{A}{/$$}, and there is another type {$$}\text{B}{/$$} equal to {$$}\text{A}{/$$}, then that object is of type {$$}\text{B}{/$$}: {$$}(a : \text{A}, \text{A} = \text{B}) \to (a : \text{B}){/$$} 249 | 250 | There are other inference rules, for example introduction and elimination. We will show an example of using these rules in section 4.2. 251 | 252 | As an example, for well-formed expressions rule 1 says that we can form an expression such that an object inhabits the type {$$}\text{Type}{/$$}, so an example of a well-formed expression is {$$}1 : \text{Nat}{/$$}, per rule 2, and {$$}\text{Nat} : \text{Type}{/$$} per rule 1. 253 | 254 | A valid type as per the fourth rule of type constructors is the definition of natural numbers {$$}\text{Nat = Z | S Nat}{/$$}. Some valid values are {$$}\text{Z : Nat, S Z : Nat}{/$$}, etc. 255 | 256 | X> ### Exercise 9 257 | X> 258 | X> We used rule 1 and rule 2 in the example earlier. Try to come up with different ways to use some of the rules. 259 | 260 | X> ### Exercise 10 261 | X> 262 | X> Combine the use of rules along with the connectives described earlier and try to come up with a recursive type and then construct some new objects from it. 263 | 264 | ### 3.4.1. Intuitionistic logic 265 | 266 | I> ### Definition 13 267 | I> 268 | I> A constructive proof proves the existence of a mathematical object by creating or constructing the object itself. This is contrary to non-constructive proofs which prove the existence of objects without giving a concrete example. 269 | 270 | I> ### Definition 14 271 | I> 272 | I> Intuitionistic logic, also known as constructive logic, is a type of logic that is different than classical logic in that it "works" with the notion of constructive proof. 273 | 274 | I> ### Definition 15 275 | I> 276 | I> The BHK (Brouwer-Heyting-Kolmogorov) interpretation is a mapping of intuitionistic logic to classical mathematical logic, namely: 277 | I> 278 | I> 1. A proof of {$$}P \land Q{/$$} is a product type {$$}\text{A B}{/$$}, where {$$}a{/$$} is a proof of (or, object of type) {$$}\text{P}{/$$} and {$$}b{/$$} is a proof of {$$}\text{Q}{/$$} 279 | I> 1. A proof of {$$}P \lor Q{/$$} is a product type {$$}\text{A B}{/$$}, where {$$}a{/$$} is 0 and {$$}b{/$$} is a proof of {$$}\text{P}{/$$}, or {$$}a{/$$} is 1 and {$$}b{/$$} is a proof of {$$}\text{Q}{/$$} 280 | I> 1. A proof of {$$}P \to Q{/$$} is a function {$$}f{/$$} that converts a proof of {$$}\text{P}{/$$} to a proof of {$$}\text{Q}{/$$} 281 | I> 1. A proof of {$$}\exists x \in S : f(x){/$$} is a pair {$$}\text{A B}{/$$} where {$$}a{/$$} is an element of {$$}\text{S}{/$$}, and {$$}b{/$$} is a proof of {$$}f(a){/$$} (dependent sum types) 282 | I> 1. A proof of {$$}\forall x \in S : f(x){/$$} is a function {$$}f{/$$} that converts an element {$$}a{/$$} from {$$}\text{S}{/$$} to a proof of {$$}f(a){/$$} (dependent product types) 283 | I> 1. A proof of {$$}\lnot P{/$$} is defined as {$$}P \to \bot{/$$}, that is, the proof is a function {$$}f{/$$} that converts a proof of {$$}\text{P}{/$$} to a proof of {$$}\bot{/$$} 284 | I> 1. There is no proof of {$$}\bot{/$$} 285 | 286 | For example, to prove distributivity of {$$}\land{/$$} with respect to {$$}\lor{/$$}, that is, {$$}P \land (Q \lor R) = (P \land Q) \lor (P \land R){/$$}, we need to construct a proof for the function of type {$$}f : \text{P (Q | R)} \to \text{P Q | P R}{/$$}. That is, a function that takes a product type of {$$}\text{P}{/$$} and sum type of {$$}\text{Q}{/$$} and {$$}\text{R}{/$$}, and returns a sum type of product {$$}\text{P}{/$$} and {$$}\text{Q}{/$$}, and product {$$}\text{P}{/$$} and {$$}\text{R}{/$$}. Here's the function that accomplishes that: 287 | 288 | ``` 289 | f (x, left y) = left (x, y) 290 | f (x, right y') = right (x, y') 291 | ``` 292 | 293 | This notation (which is pretty similar to how we would write it in Idris) uses `(x, y)` to denote product type, that is, extract values from a product-type pair, and `left` and `right` to denote value constructors for sum type in order to extract values from a sum-type pair. In the next chapter we will introduce Idris and its syntax. 294 | 295 | X> ### Exercise 11 296 | X> 297 | X> Try to use some of the proofs in the earlier chapters as motivation and work them out by using intuitionistic logic. 298 | 299 | [^ch3n1]: Unlike in set theory, where they are defined in terms of relations. 300 | 301 | [^ch3n2]: Dependent types allow proofs of statements involving first-order predicates, compared to simple types that correspond to propositional logic. While useful (since we can check whether an expression fulfills a given condition at compile-time), dependent types add complexity to a type system. In order to calculate type "equality" of dependent types, computations are necessary. If we allow any values for dependent types, then solving equality of a type may involve deciding whether two programs produce the same result. Thus, the check may become undecidable. 302 | 303 | [^ch3n3]: This is what makes Idris a so-called proof assistant. In general, Idris combines a lot of functionalities from mainstream languages (Java, C, C++) and some functionalities from proof assistants, which further blurs the line between these two kinds of software. 304 | 305 | [^ch3n4]: A _Turing machine_ is a very simple abstract machine designed to capture our intuitive understanding of _computation_ in the most general sense. Any formal system that can simulate a Turing machine, and thus also perform arbitrary computations, is called _Turing complete_. 306 | 307 | [^ch3n5]: For this reason, the typed lambda calculus is not Turing complete, while the untyped lambda calculus is. Fixed-point combinators provide flexibility, but that has its drawbacks. They can be non-terminating - loop indefinitely without producing an answer. While non-termination has its uses for software (e.g. a program keeps running until we choose to close it), termination is important for mathematical proofs as we will see in section 4.2. 308 | 309 | [^ch3n6]: Collections, in general, are considered to be subcollections of some large universal collection, also called the universe. Depending on the context, the definition of this universe will vary. 310 | 311 | [^ch3n7]: As a consequence that we need to construct an object as evidence in order to prove something, the law of excluded middle {$$}P \lor \lnot P{/$$} is not valid in this logic, whereas in classical mathematical logic this is taken as an axiom. For some propositions, for example, {$$}P{/$$} is an odd number or not, there are proofs that we can provide. However, for some propositions this is impossible, for example, {$$}P{/$$} is a program that halts or not. Unlike classical mathematical logic, in this logic, the law of excluded middle does not exist due to the undecidability problem. 312 | 313 | [^ch3n8]: It is provable in case we can construct an object of such type, and not provable otherwise. 314 | -------------------------------------------------------------------------------- /manuscript/chapter4.md: -------------------------------------------------------------------------------- 1 | # 4. Programming in Idris 2 | 3 | In this chapter, we will introduce Idris' syntax, by defining functions and types. 4 | 5 | Depending on what level of abstraction we are working on, types and proofs can give us a kind of security based on some truths we take for granted (axioms). In fact, this is how we develop code on a daily basis, as software engineers. We have a list of axioms, for example, a `foreach` loop in a programming language, and starting from it we build abstractions, expecting this `foreach` to behave in a certain way. However, this security is not always easy to achieve. For example, consider a scenario where we have a button that is supposed to download a PDF document. In order to prove that it works as expected, we must first pick the abstraction level we will be working on, and then proceed by defining software requirements (what is a PDF, what is a download). So, we first have to define our **specifications**, and then we can proceed with proving correctness. 6 | 7 | Idris, even though a research language, still has its own uses. It is a Turing complete language, which means that it has the same expressive power as other programming languages, for example, Java, or C++. 8 | 9 | To start with, Idris can be downloaded and installed via www.idris-lang.org/download. 10 | 11 | ## 4.1. Basic syntax and definitions 12 | 13 | There are two working modes in Idris: REPL (read-evaluate-print-loop) i.e. interactive mode, and compilation of code. We will mostly work in the interactive mode in this chapter. 14 | 15 | You can copy any of the example codes to some file, say, `test.idr` and launch Idris in REPL mode by writing `idris test.idr`. This will allow you to interact with Idris given the definitions in the file. If you change the contents of the file, that is, update the definitions, you can use the command `:r` while in REPL mode to reload the definitions. 16 | 17 | ### 4.1.1. Defining functions 18 | 19 | Let's begin by defining a simple function. 20 | 21 | ``` 22 | add_1 : Nat -> Nat 23 | add_1 x = x + 1 24 | ``` 25 | 26 | With the code above we're defining a function {$$}f(x) = x + 1{/$$}, where {$$}x{/$$} is natural number and {$$}f(x){/$$} (the result) is also a natural number[^ch4n1]. The first line `add_1 : Nat -> Nat` is called a _type signature_, and specifies the type of our function; in this case a function that takes a natural number and returns a natural number. The second line `add_1 x = x + 1` is the definition of the function, which states that if `add_1` is called with a number `x`, the result would be `x + 1`. As can be seen by the example, every function has to be provided a type definition. We can interact as follows in the REPL mode: 27 | 28 | ``` 29 | Idris> add_1 5 30 | 6 : Nat 31 | ``` 32 | 33 | Idris responds to us that as a result, we get 6, which is of type `Nat`. Constants are defined similarly; we can think of them as functions with no arguments. 34 | 35 | ``` 36 | number_1 : Nat 37 | number_1 = 1 38 | ``` 39 | 40 | As in Haskell, we can use pattern matching. What happens during this phase is Idris does a check (match) against the definitions of the function and uses the definition of the function that matches the value. 41 | 42 | ``` 43 | is_it_zero : Nat -> Bool 44 | is_it_zero Z = True 45 | is_it_zero x = False 46 | ``` 47 | 48 | We've just defined a function that accepts a natural number and returns a boolean value (`True` or `False`). On the first line, we specify its type. On the second line, we pattern match against the input `Z` and return `True` in that case. On the third line, we pattern match against the input `x` (which is all remaining inputs except `Z`). In this case, we return `False`. The code above, depending on the input, will branch the computation to the corresponding definition. Note that `Z` corresponds to 0 for the type `Nat`. 49 | 50 | X> ### Exercise 1 51 | X> 52 | X> Write a function `my_identity` which accepts a natural number and returns the same number. 53 | 54 | X> ### Exercise 2 55 | X> 56 | X> Write a function `five_if_zero` which accepts a natural number and returns 5 when called with an argument of 0, otherwise returns the same number. For example, `five_if_zero 0` should return 5. `five_if_zero 123` should return 123. 57 | 58 | ### 4.1.2. Defining and inferring types 59 | 60 | In Idris, types are first-class citizens. This means that types can be computed and passed to other functions. We define new types by using the keyword `data`. All concrete types (like `Nat`) and type constructors (like `List`) begin with an uppercase letter, while lowercase letters are reserved for polymorphic types[^ch4n2]. There are a couple of ways to define types. One example is by using the so-called `Haskell98` syntax: 61 | 62 | ``` 63 | data A a b = A_inst a b 64 | ``` 65 | 66 | This will create a polymorphic type that accepts two type arguments, `a` and `b`. Valid constructed types are `A Nat Bool`, `A Nat Nat`, etc. `A` is a type constructor (a function that returns a type) and `A_inst`[^ch4n3] is a value constructor (a function that returns a value of type `A a b`). 67 | 68 | ``` 69 | Idris> A_inst True True 70 | A_inst True True : A Bool Bool 71 | Idris> A_inst Z True 72 | A_inst 0 True : A Nat Bool 73 | ``` 74 | 75 | Note how the type changes depending on what values we pass to the constructor, due to polymorphism. 76 | 77 | Here's an equivalent definition by using the `GADT` (Generalized Algebraic Data Types) syntax: 78 | 79 | ``` 80 | data A : Type -> Type -> Type where 81 | A_inst : a -> b -> A a b 82 | ``` 83 | 84 | This is equivalent to the following definition, where we define an empty data structure along with an axiom for the value constructor: 85 | 86 | ``` 87 | data A : Type -> Type -> Type 88 | postulate A_inst : a -> b -> A a b 89 | ``` 90 | 91 | With the `postulate` keyword we can define axioms which are functions that satisfy the types without giving an actual argument for construction. With the command `:t` we can check the type of an expression, so as a result, we get: 92 | 93 | ``` 94 | Idris> :t A 95 | A : Type -> Type -> Type 96 | Idris> :t A_inst 97 | A_inst : a -> b -> A a b 98 | ``` 99 | 100 | That is, we show the type definitions for both the newly-defined type and its value constructor. Note how we created a product type here. Idris has a built-in[^ch4n4] notion of pairs, which is a data type that can be defined in terms of products. For example, `(1, 2)` is one pair. We can also define tuples with `(1, "Hi", True)`, which is equivalent to `(1, ("Hi", True))`, i.e. a pair where the first element is a number, and the second element is a pair. Note that the types `(a, b) -> c` (curried) and `a -> b -> c` (uncurried) represent the same thing. 101 | 102 | Analogously, if we want to create a sum type, we could do the following: 103 | 104 | ``` 105 | data B a b = B_inst_left a | B_inst_right b 106 | ``` 107 | 108 | This is equivalent to: 109 | 110 | ``` 111 | data B : Type -> Type -> Type where 112 | B_inst_left : a -> B a b 113 | B_inst_right : b -> B a b 114 | ``` 115 | 116 | With either of these definitions in scope, we get: 117 | 118 | ``` 119 | Idris> :t B 120 | B : Type -> Type -> Type 121 | Idris> :t B_inst_left 122 | B_inst_left : a -> B a b 123 | Idris> :t B_inst_right 124 | B_inst_right : b -> B a b 125 | ``` 126 | 127 | The functions `B_inst_left` and `B_inst_right` are also known as _introduction rules_. For extracting values from data types such as `B a b`, also known as _elimination rules_, we can use pattern matching[^ch4n5]. As an example, to extract `a` from `B a b`, we can use the following function: 128 | 129 | ``` 130 | f : B a b -> a 131 | f (B_inst_left a) = a 132 | ``` 133 | 134 | Note how we used the data type at the function type level and the value constructor in the function definition to pattern match against. 135 | 136 | Natural numbers can be defined as `data Nat = Z | S Nat`, where we either have a zero or a successor of a natural number. Note how this type is not polymorphic (it doesn't accept any variables after the type name). Natural numbers are built-in as a type in Idris. 137 | 138 | We can use the operator `==` to compare two numbers. Note that `==` is still a function, but it's an infix one. This means that unlike other functions that we define which are prefix, i.e. start at the beginning of the expression, `==` needs to appear between the parameters. For example: 139 | 140 | ``` 141 | Idris> 1 == 1 142 | True : Bool 143 | ``` 144 | 145 | Idris has several built-in primitive types, including: `Bool`, `Char`, `List`, `String` (list of characters), `Integer`, etc. The only value constructors for the type `Bool` are `True` and `False`. The difference between a `Nat` and an `Integer` is that `Integer` can also contain negative values. Here are some examples of interacting with these types: 146 | 147 | ``` 148 | Idris> "Test" 149 | "Test" : String 150 | Idris> '!' 151 | '!' : Char 152 | Idris> 1 153 | 1 : Integer 154 | Idris> '1' 155 | '1' : Char 156 | Idris> [1, 2, 3] 157 | [1, 2, 3] : List Integer 158 | ``` 159 | 160 | By using the `:doc` command, we can get detailed information about a data type: 161 | 162 | ``` 163 | Idris> :doc Nat 164 | Data type Prelude.Nat.Nat : Type 165 | Natural numbers: unbounded, unsigned integers which can be pattern 166 | matched. 167 | 168 | Constructors: 169 | Z : Nat 170 | Zero 171 | 172 | S : Nat -> Nat 173 | Successor 174 | ``` 175 | 176 | In order to make Idris infer the necessary type of the function that needs to be built, we can take advantage of holes. A hole is any variable that starts with a question mark. For example, if we have the following definition: 177 | 178 | ``` 179 | test : Bool -> Bool 180 | test p = ?hole1 181 | ``` 182 | 183 | We can now ask Idris to tell us the type of `hole1`, that is, with `:t hole1` we can see that Idris inferred that the specific result is expected to be of type `Bool`. This is useful because it allows us to write programs incrementally (piece by piece) instead of constructing the program all at once. 184 | 185 | X> ### Exercise 3 186 | X> 187 | X> Define your own custom type. One example is `data Person = PersonInst String Nat`, where `String` represents the Person's name and `Nat` represents the Person's age. Use the constructor to generate some objects of that type. Afterward, use `:t` to check the types of the type and value constructors. 188 | 189 | X> ### Exercise 4 190 | X> 191 | X> Come up with a function that works with your custom type (for example, it extracts some value) by pattern matching against its value constructor(s). 192 | 193 | X> ### Exercise 5 194 | X> 195 | X> Repeat Exercise 4 and use holes to check the types of the expressions used in your function definition. 196 | 197 | ### 4.1.3. Anonymous lambda functions 198 | 199 | With the syntax `let X in Y` we're defining a set of variables `X` which are only visible in the body of `Y`. As an example, here is one way to use this syntax: 200 | 201 | ``` 202 | Idris> let f = 1 in f 203 | 1 : Integer 204 | ``` 205 | 206 | Alternatively, the REPL has a command `:let` that allows us to set a variable without evaluating it right away, that can be used at a later point: 207 | 208 | ``` 209 | Idris> :let f = 1 210 | Idris> f 211 | 1 : Integer 212 | ``` 213 | 214 | Q> What's the difference between `let` and `:let`? 215 | Q> 216 | Q> `let` is an extension of the lambda calculus syntax. The expression `let x = y in e` is equivalent to `(\x . e) y`. It is useful since it makes it easy to shorten a lambda expression by factoring out common subexpressions. 217 | Q> 218 | Q> `:let` is different in that it is just used to bind new names at the top level of an interactive Idris session. 219 | 220 | Lambda (anonymous) functions are defined with the syntax `\a, b, ..., n => ...`. For example: 221 | 222 | ``` 223 | Idris> let addThree = (\x, y, z => x + y + z) in addThree 1 2 3 224 | 6 : Integer 225 | ``` 226 | 227 | With the example above, we defined a function `addThree` that accepts three parameters and as a result it sums them. However, if we do not pass all parameters to a function, it will result in: 228 | 229 | ``` 230 | Idris> let addThree = (\x, y, z => x + y + z) in addThree 1 2 231 | \z => prim__addBigInt 3 z : Integer -> Integer 232 | ``` 233 | 234 | We can see (from the type) that as a result, we get another function. 235 | 236 | I> ### Definition 1 237 | I> 238 | I> Currying is a concept that allows us to evaluate a function with multiple parameters as a sequence of functions, each having a single parameter. 239 | 240 | Function application in Idris is left-associative (just like in lambda calculus), which means that if we try to evaluate `addThree 1 2 3`, then it will be evaluated as `(((addThree 1) 2) 3)`. A combination of left-associative functions and currying (i.e. partial evaluation of function) is what allows us to write `addThree 1 2 3`, which is much more readable. 241 | 242 | Arrow types are right-associative (just like in lambda calculus), which means that `addThree 1 : a -> a -> a` is equivalent to `addThree 1 : (a -> (a -> a))`. If we had written a type of `(a -> a) -> a` instead, then this function would accept as its first parameter a function that takes an `a` and returns an `a`, and then the original function also returns an `a`. This is how we can define higher-order functions which we will discuss later. Note how `a` starts with a lowercase letter, thus it is a polymorphic type. 243 | 244 | The `if...then...else` syntax is defined as follows: 245 | 246 | ``` 247 | Idris> if 1 == 1 then 'a' else 'b' 248 | 'a' : Char 249 | Idris> if 2 == 1 then 'a' else 'b' 250 | 'b' : Char 251 | ``` 252 | 253 | X> ### Exercise 6 254 | X> 255 | X> Write a lambda function that returns `True` if the parameter passed to it is 42 and `False` otherwise. 256 | 257 | ### 4.1.4. Recursive functions 258 | 259 | By Definition 19 of chapter 2, recursive functions are those functions that refer to themselves. One example is the function `even : Nat -> Bool`, a function that checks if a natural number is even or not. It can be defined as follows: 260 | 261 | ``` 262 | even : Nat -> Bool 263 | even Z = True 264 | even (S k) = not (even k) 265 | ``` 266 | 267 | The definition states that 0 is an even number, and that `n + 1` is even or not depending on the parity (evenness) of `n`. As a result, we get: 268 | 269 | ``` 270 | Idris> even 3 271 | False : Bool 272 | Idris> even 4 273 | True : Bool 274 | Idris> even 5 275 | False : Bool 276 | ``` 277 | 278 | The way `even 5` unfolds in Idris is as follows: 279 | 280 | ``` 281 | even 5 = 282 | not (even 4) = 283 | not (not (even 3)) = 284 | not (not (not (even 2))) = 285 | not (not (not (not (even 1)))) = 286 | not (not (not (not (not (even 0))))) = 287 | not (not (not (not (not True)))) = 288 | not (not (not (not False))) = 289 | not (not (not True)) = 290 | not (not False) = 291 | not True = 292 | False 293 | ``` 294 | 295 | We see how this exhibits a recursive behavior since the recursive cases were reduced to the base case in an attempt to get a result. With this example, we can see the power of recursion and how it allows us to process values in a repeating manner. 296 | 297 | A recursive function can generate an **iterative** or a **recursive** process: 298 | 299 | 1. An iterative process[^ch4n6] (tail recursion) is a process where the return value at any point in computation is captured completely by its parameters 300 | 1. A recursive one, in contrast, is one where the return value is not captured at any point in computation by the parameters, and so it relies on postponed evaluations 301 | 302 | In the example above, `even` generates a recursive process since it needs to go down to the base case, and then build its way back up to do the calculations that were postponed. Alternatively, we can rewrite `even` so that it captures the return value by introducing another variable, as such: 303 | 304 | ``` 305 | even : Nat -> Bool -> Bool 306 | even Z t = t 307 | even (S k) t = even k (not t) 308 | ``` 309 | 310 | In this case, we can refer to the return value of the function (second parameter) at any point in the computation. As a consequence, this function generates an iterative process, since the results are captured in the parameters. Note how we brought down the base case to refer to the parameter, instead of a constant `True`. Here is how Idris evaluates `even 5 True`: 311 | 312 | ``` 313 | even 5 True = 314 | even 4 False = 315 | even 3 True = 316 | even 2 False = 317 | even 1 True = 318 | even 0 False = 319 | False 320 | ``` 321 | 322 | To conclude, iterative processes take fewer calculation steps and are usually more performant than recursive processes. Recursive functions combined with pattern matching are one of the most powerful tools in Idris since they allow for computation. They are also useful for proving mathematical theorems with induction, as we will see in the examples later. 323 | 324 | X> ### Exercise 7 325 | X> 326 | X> Factorial is defined as: 327 | X> {$$}fact(n) = \left\{ \begin{array}{ll} 1\text{, if } n = 0 \\ n \cdot fact(n - 1)\text{, otherwise} \end{array} \right.{/$$} 328 | X> 329 | X> Unfold the evaluation of {$$}fact(5){/$$} on paper, and then implement it in Idris and confirm that Idris also computes the same value. 330 | X> 331 | X> Hint: The type is `fact : Nat -> Nat` and you should pattern match against `Z` (`Nat` value constructor for 0) and `(S n)` (successor). 332 | 333 | X> ### Exercise 8 334 | X> 335 | X> Re-write the factorial function to generate an iterative process. 336 | X> 337 | X> Hint: The type is `fact_iter : Nat -> Nat -> Nat` and you should pattern match against `Z acc` (number zero, and accumulator) and `(S n) acc` (successor, and accumulator). 338 | 339 | ### 4.1.5. Recursive data types 340 | 341 | We can think of type constructors as functions at the type level. Taking the concept of recursion to this context yields _recursive types_. 342 | 343 | I> ### Definition 2 344 | I> 345 | I> A recursive data type is a data type where some of its constructors have a reference to the same data type. 346 | 347 | We will start by defining a recursive data type, which is a data type that in the constructor refers to itself. In fact, earlier in this book we already gave a recursive definition - `Nat`. As a motivating example, we will try to define the representation of lists. For this data type, we'll use a combination of sum and product types. A list is defined as either `End` (end of the list) or `Cons` (construct), which is a value appended to another `MyList`: 348 | 349 | ``` 350 | data MyList a = Cons a (MyList a) | End 351 | ``` 352 | 353 | This means that the type `MyList` has two constructors, `End` and `Cons`. If it's `End`, then it's the end of the list (and does not accept any more values). However, if it's `Cons`, then we need to append another value (e.g. `Cons 3`), but afterward, we have to specify another value of type `MyList a` (which can be `End` or another `Cons`). This definition allows us to define a list. As an example, this is how we would represent {$$}(1, 2){/$$} using our `Cons End` representation: 354 | 355 | ``` 356 | Idris> :t Cons 1 (Cons 2 End) 357 | Cons 1 (Cons 2 End) : MyList Integer 358 | Idris> :t Cons 'a' (Cons 'b' End) 359 | Cons 'a' (Cons 'b' End) : MyList Char 360 | ``` 361 | 362 | Note how Idris automatically infers the polymorphic type to `MyList Integer` and `MyList Char`. In particular, these lists are _homogeneous_; all the items in a `MyList a` must have type `a`. 363 | 364 | Here is one way of implementing the concatenation function, which given two lists, should produce a list with the elements appended: 365 | 366 | ``` 367 | add' : MyList a -> MyList a -> MyList a 368 | add' End ys = ys 369 | add' (Cons x xs) ys = Cons x (add' xs ys) 370 | ``` 371 | 372 | The first line of the code says that `add'` is a function that accepts two polymorphic lists (`MyList Nat`, `MyList Char`, etc), and produces the same list as a result. The second line of the code pattern matches against the first list and when it's empty we just return the second list. The third line of the code also pattern matches against the first list, but this time it covers the `Cons` case. So whenever there is a `Cons` in the first list, as a result, we return this element `Cons x` appended recursively to `add' xs ys`, where `xs` is the remainder of the first list and `ys` is the second list. Example usage: 373 | 374 | ``` 375 | Idris> add' (Cons 1 (Cons 2 (Cons 3 End))) (Cons 4 End) 376 | Cons 1 (Cons 2 (Cons 3 (Cons 4 End))) : MyList Integer 377 | ``` 378 | 379 | X> ### Exercise 9 380 | X> 381 | X> Unfold `add' (Cons 1 (Cons 2 (Cons 3 End))) (Cons 4 End)` on paper to get a better understanding of how this definition appends two lists. 382 | 383 | X> ### Exercise 10 384 | X> 385 | X> Come up with a definition for `length'`, which should return the number of elements, given a list. 386 | X> 387 | X> Hint: The type is `length' : MyList a -> Nat` 388 | 389 | X> ### Exercise 11 390 | X> 391 | X> Come up with a definition for `even_members`, which should return a new list with even natural numbers only. 392 | X> 393 | X> Hint: The type is `even_members : MyList Nat -> MyList Nat`. You can re-use the definition of `even` we've discussed earlier. 394 | 395 | X> ### Exercise 12 396 | X> 397 | X> Come up with a definition for `sum'`, which should return a number that will be the sum of all elements in a list of natural numbers. 398 | X> 399 | X> Hint: The type is `sum' : MyList Nat -> Nat`. 400 | 401 | ### 4.1.6. Total and partial functions 402 | 403 | I> ### Definition 3 404 | I> 405 | I> A total function is a function that terminates (or returns a value) for all possible inputs. 406 | 407 | A partial function is the opposite of a total function. If a function is total, its type can be understood as a precise description of what that function can do. Idris differentiates total from partial functions but allows defining both[^ch4n7]. As an example, if we assume that we have a function that returns a `String`, then: 408 | 409 | 1. If it's total, it will return a `String` in finite time 410 | 1. If it's partial, then unless it crashes or enters in an infinite loop, it will return a `String` 411 | 412 | In Idris, to define partial/total functions we just put the keyword `partial`/`total` in front of the function definition. For example, for the following program we define two functions `test` and `test2`, a partial and a total one respectively: 413 | 414 | ``` 415 | partial 416 | test : Nat -> String 417 | test Z = "Hi" 418 | 419 | total 420 | test2 : Nat -> String 421 | test2 Z = "Hi" 422 | test2 _ = "Hello" 423 | ``` 424 | 425 | If we try to interact with these functions, we get the following results: 426 | 427 | ``` 428 | Idris> test 0 429 | "Hi" : String 430 | Idris> test 1 431 | test 1 : String 432 | Idris> test2 0 433 | "Hi" : String 434 | Idris> test2 1 435 | "Hello" : String 436 | ``` 437 | 438 | We can note that the evaluation of `test 1` does not produce a computed value as a result. Note that at compile-time, Idris will **evaluate the types only for total functions**. 439 | 440 | X> ### Exercise 13 441 | X> 442 | X> Try to define a function to be `total`, and at the same time make sure you are not covering all input cases. Note what errors will Idris return in this case. 443 | 444 | ### 4.1.7. Higher-order functions 445 | 446 | I> ### Definition 4 447 | I> 448 | I> A higher-order function is a function that takes one or more functions as parameters or returns a function as a result. 449 | 450 | There are three built-in higher-order functions that are generally useful: `map`, `filter`, `fold` (left and right). Here's the description of each: 451 | 452 | 1. `map` is a function that takes as input a function with a single parameter and a list and returns a list where all members of the list have this function applied to them 453 | 1. `filter` is a function that takes as input a function (predicate) with a single parameter (that returns a `Bool`) and a list and only returns those members in the list whose predicate evaluates to `True` 454 | 1. `fold` is a function that takes as input a combining function that accepts two parameters (current value and accumulator), an initial value and a list and returns a value combined with this function. There are two types of folds, a left and a right one, which combines from the left and the right respectively 455 | 456 | As an example usage: 457 | 458 | ``` 459 | Idris> map (\x => x + 1) [1, 2, 3] 460 | [2, 3, 4] : List Integer 461 | Idris> filter (\x => x > 1) [1, 2, 3] 462 | [2, 3] : List Integer 463 | Idris> foldl (\x, y => x + y) 0 [1, 2, 3] 464 | 6 : Integer 465 | Idris> foldr (\x, y => x + y) 0 [1, 2, 3] 466 | 6 : Integer 467 | ``` 468 | 469 | We can actually implement the `map` function ourselves: 470 | 471 | ``` 472 | mymap : (a -> a) -> List a -> List a 473 | mymap _ [] = [] 474 | mymap f (x::xs) = (f x) :: (mymap f xs) 475 | ``` 476 | 477 | Note that `::` is used by the built-in `List` type, and is equivalent to `Cons` we've used earlier. However, since `::` is an infix operator, it has to go between the two arguments. The value `[]` represents the empty list and is equivalent to `End`. In addition, the built-in `List` type is polymorphic. 478 | 479 | X> ### Exercise 14 480 | X> 481 | X> Do a few different calculations with `mymap` in order to get a deeper understanding of how it works. 482 | 483 | X> ### Exercise 15 484 | X> 485 | X> Implement a function `myfilter` that acts just like the `filter` function. 486 | X> 487 | X> Hint: Use `:t filter` to get its type. 488 | 489 | X> ### Exercise 16 490 | X> 491 | X> Given `foldl (\x, y => [y] ++ x) [] [1, 2, 3]` and `foldr (\x, y => y ++ [x]) [] [1, 2, 3]`: 492 | X> 493 | X> 1. Evaluate both of them in Idris to see the values produced. 494 | X> 2. Try to understand the differences between the two expressions. 495 | X> 3. Remove the square brackets `[` and `]` in the lambda body to see what errors Idris produces. 496 | X> 4. Evaluate them on paper to figure out why they produce the given results. 497 | 498 | ### 4.1.8. Dependent types 499 | 500 | We will implement the `List n` data type that we discussed in section 3.3, which should limit the length of a list at the type level. Idris already has a built-in list like this called `Vect`, so to not conflict we'll name it `MyVect`. We can implement it as follows: 501 | 502 | ``` 503 | data MyVect : (n : Nat) -> Type where 504 | Empty : MyVect 0 505 | Cons : (x : Nat) -> (xs : MyVect len) -> MyVect (S len) 506 | ``` 507 | 508 | We created a new type called `MyVect` which accepts a natural number `n` and returns a `Type`, that is joined with two value constructors: 509 | 510 | 1. `Empty` - which is just the empty vector (list) 511 | 1. `Cons : (x : Nat) -> (xs : MyVect len) -> MyVect (S len)` - which, given a natural number `x` and a vector `xs` of length `len`, will return a vector of length `S len`, that is, `len + 1`. 512 | 513 | Note how we additionally specified names to the parameters (`n : Nat`, `xs : MyVect len`, etc.). This can be useful if we want to reference those parameters elsewhere in the type definition. 514 | 515 | If we now use the following code snippet, it will pass the compile-time checks: 516 | 517 | ``` 518 | x : MyVect 2 519 | x = Cons 1 (Cons 2 Empty) 520 | ``` 521 | 522 | However, if we try to use this code snippet instead: 523 | 524 | ``` 525 | x : MyVect 3 526 | x = Cons 1 (Cons 2 Empty) 527 | ``` 528 | 529 | we will get the following error: 530 | 531 | ``` 532 | Type mismatch between 533 | MyVect 0 (Type of Empty) 534 | and 535 | MyVect 1 (Expected type) 536 | ``` 537 | 538 | Which is a way of Idris telling us that our types do not match and that it cannot verify the "proof" provided. 539 | 540 | In this example, we implemented a dependent type that puts the length of a list at the type level. In other programming languages that do not support dependent types, this is usually checked at the code level (run-time) and compile-time checks are not able to verify this. 541 | 542 | One example where such a guarantee might be useful is in preventing buffer overflows. We could encode the dimension of an array at the type level, and statically guarantee that array reads and writes only happen within bounds. 543 | 544 | X> ### Exercise 17 545 | X> 546 | X> Come up with a function `isSingleton` that accepts a `Bool` and returns a `Type`. This function should return an object of type `Nat` in the `True` case, and `MyVect Nat` otherwise. Further, implement a function `mkSingle` that accepts a `Bool`, and returns `isSingleton True` or `isSingleton False`, and as a computed value will either return `0` or `Empty`. 547 | X> 548 | X> Hint: The data definitions are `isSingleton : Bool -> Type` and `mkSingle : (x : Bool) -> isSingleton x` respectively. 549 | 550 | ### 4.1.9. Implicit parameters 551 | 552 | Implicit parameters (arguments) allow us to bring values from the type level to the program level. At the program level, by using curly braces we allow them to be used in the definition of the function. Let's take a look at the following example, which uses our dependent type `MyVect` that we defined earlier: 553 | 554 | ``` 555 | lengthMyVect : MyVect n -> Nat 556 | lengthMyVect {n = k} list = k 557 | ``` 558 | 559 | In this case, we defined a function `lengthMyVect` that takes a `MyVect` and returns a natural number. The value `n` in the definition of the function will be the same as the value of `n` at the type level. They are called implicit parameters because the caller of this function needn't pass these parameters. In the function definition, we define implicit parameters with curly braces and we also need to specify the list parameter which is of type `MyVect n` to pattern match against it. But, note how we don't refer to the list parameter in the computation part of this function and instead, we can use an underscore (which represents an unused parameter) to get to: 560 | 561 | ``` 562 | lengthMyVect : MyVect n -> Nat 563 | lengthMyVect {n = k} _ = k 564 | ``` 565 | 566 | We can also have implicit parameters at the type level. As a matter of fact, an equivalent type definition of that function is: 567 | 568 | ``` 569 | lengthMyVect : {n : Nat} -> MyVect n -> Nat 570 | ``` 571 | 572 | If we ask Idris to give us the type of this function, we will get the following for either of the type definitions above: 573 | 574 | ``` 575 | Idris> :t lengthMyVect 576 | lengthMyVect : MyVect n -> Nat 577 | ``` 578 | 579 | However, we can use the command `:set showimplicits` which will show the implicits on the type level. If we do that, we will get the following for either of the type definitions above: 580 | 581 | ``` 582 | Idris> :set showimplicits 583 | Idris> :t lengthMyVect 584 | lengthMyVect : {n : Nat} -> MyVect n -> Nat 585 | ``` 586 | 587 | To pass values for implicit arguments, we can use the following syntax: 588 | 589 | ``` 590 | Idris> lengthMyVect {n = 1} (Cons 1 Empty) 591 | 1 : Nat 592 | ``` 593 | 594 | X> ### Exercise 18 595 | X> 596 | X> Try to evaluate the following code and observe the results: 597 | X> 598 | X> ``` 599 | X> lengthMyVect {n = 2} (Cons 1 Empty) 600 | X> ``` 601 | 602 | ### 4.1.10. Pattern matching expressions 603 | 604 | We've seen how pattern matching is a powerful concept, in that it allows us to pattern match against value constructors. For example, we can write a filtering function that given a list of naturals produces a list of even naturals, by re-using the earlier definition of `even`: 605 | 606 | ``` 607 | total even_members : MyList Nat -> MyList Nat 608 | even_members End = End 609 | even_members (Cons x l') = if (even x) 610 | then (Cons x (even_members l')) 611 | else even_members l' 612 | ``` 613 | 614 | The function above is a recursive one, and depending on the value of `even x` it will branch the recursion. Since pattern matching works against value constructors, and `even x` is a function call, we can't easily pattern match against it. We used `even x` in the function body to do the check. Idris provides another additional keyword `with` that allows us to pattern match a value of some expression. The keyword `with` has the following syntax: 615 | 616 | ``` 617 | function (pattern_match_1) with (expression) 618 | pattern_match_1' | (value of expression to match) = ... 619 | pattern_match_2' | (value of expression to match) = ... 620 | ... 621 | ``` 622 | 623 | Note how we have to specify new pattern matching clauses after the line that uses the `with` keyword. This is so because we won't have the original pattern match in context. Given this, an alternative definition of the function above is: 624 | 625 | ``` 626 | total even_members' : MyList Nat -> MyList Nat 627 | even_members' End = End 628 | even_members' (Cons x l') with (even x) 629 | even_members' (Cons x l') | True = Cons x (even_members' l') 630 | even_members' (Cons _ l') | False = (even_members' l') 631 | ``` 632 | 633 | In this function, we defined two new pattern matches after the line that uses the `with` keyword. Since we don't have `x` and `l'` in this new pattern matching context, we have to rewrite them on the left side of the pipe, and on the right side of the pipe we pattern match against the value of `even x`, and then branch the recursion (computation). 634 | 635 | ### 4.1.11. Interfaces and implementations 636 | 637 | Interfaces are defined using the `interface` keyword and they allow us to add constraints to types that implement them[^ch4n8]. As an example, we'll take a look at the `Eq` interface: 638 | 639 | ``` 640 | interface Eq a where 641 | (==) : a -> a -> Bool 642 | (/=) : a -> a -> Bool 643 | -- Minimal complete definition: 644 | -- (==) or (/=) 645 | x /= y = not (x == y) 646 | x == y = not (x /= y) 647 | ``` 648 | 649 | Note how we can specify comments in the code by using two dashes. Comments are ignored by the Idris compiler and are only useful to the reader of the code. 650 | 651 | The definition says that for a type to implement the `Eq` interface, there must be an implementation of the functions `==` and `/=` for that specific type. Additionally, the interface also contains definitions for the functions, but this is optional. Since the definition of `==` depends on `/=` (and vice-versa), it will be sufficient to provide only one of them in the implementation, and the other one will be automatically generated. 652 | 653 | As an example, let's assume that we have a data type: 654 | 655 | ``` 656 | data Foo : Type where 657 | Fooinst : Nat -> String -> Foo 658 | ``` 659 | 660 | To implement `Eq` for `Foo`, we can use the following code: 661 | 662 | ``` 663 | implementation Eq Foo where 664 | (Fooinst x1 str1) == 665 | (Fooinst x2 str2) = (x1 == x2) && (str1 == str2) 666 | ``` 667 | 668 | We use `==` for `Nat` and `String` since this is already defined in Idris itself. With this, we can easily use `==` and `/=` on `Fooinst`: 669 | 670 | ``` 671 | Idris> Fooinst 3 "orange" == Fooinst 6 "apple" 672 | False : Bool 673 | Idris> Fooinst 3 "orange" /= Fooinst 6 "apple" 674 | True : Bool 675 | ``` 676 | 677 | `Nat`s implement the built-in `Num` interface, which is what allows us to use 0 and Z interchangeably. 678 | 679 | X> ### Exercise 19 680 | X> 681 | X> Implement your own data type `Person` that accepts a person's name and age, and implement an interface for comparing `Person`s. 682 | X> 683 | X> Hint: One valid data type is: 684 | X> 685 | X> ``` 686 | X> data Person = Personinst String Int 687 | X> ``` 688 | 689 | ## 4.2. Curry-Howard isomorphism 690 | 691 | The Curry-Howard isomorphism (also known as Curry-Howard correspondence) is the direct relationship between computer programs and mathematical proofs. It is named after the mathematician Haskell Curry and logician William Howard. In other words, a mathematical proof is represented by a computer program and the formula that we're proving is the type of that program. As an example, we can take a look at the function `swap` that is defined as follows: 692 | 693 | ``` 694 | swap : (a, b) -> (b, a) 695 | swap (a, b) = (b, a) 696 | ``` 697 | 698 | The isomorphism says that this function has an equivalent form of mathematical proof. Although it may not be immediately obvious, let's consider the following proof: Given {$$}P \land Q{/$$}, prove that {$$}Q \land P{/$$}. In order to prove it, we have to use the inference rules and-introduction and and-elimination, which are defined as follows: 699 | 700 | 1. And-introduction means that if we are given {$$}P{/$$}, {$$}Q{/$$}, then we can construct a proof for {$$}P \land Q{/$$} 701 | 1. Left and-elimination means that if we are given {$$}P \land Q{/$$}, we can conclude {$$}P{/$$} 702 | 1. Right and-elimination means that if we are given {$$}P \land Q{/$$}, we can conclude {$$}Q{/$$} 703 | 704 | If we want to implement this proof in Idris, we can represent it as a product type as follows: 705 | 706 | ``` 707 | data And a b = And_intro a b 708 | 709 | and_comm : And a b -> And b a 710 | and_comm (And_intro a b) = And_intro b a 711 | ``` 712 | 713 | As we've discussed, we can use product types to encode pairs. We can note the following similarities with our earlier definition of `swap`: 714 | 715 | 1. `And_intro x y` is equivalent to constructing a product type `(x, y)` 716 | 1. Left-elimination, which is a pattern match of `And_intro a _` is equivalent to the first element of the product type 717 | 1. Right-elimination, which is a pattern match of `And_intro _ b` is equivalent to the second element of the product type 718 | 719 | As long as Idris' type checker terminates, we can be certain that the program provides a mathematical proof of its type. This is why Idris' type checker only evaluates total functions, to keep the type checking decidable. 720 | 721 | ## 4.3. Quantitative Type Theory 722 | 723 | This book was written using Idris 1, however, in 2020 a new version, Idris 2 was released which is based on **Quantitative Type Theory**. Most of the examples in this book should be compatible with Idris 2. However, some Idris 2 code will not be compatible with Idris 1. Idris 2 is recommended as it also might contain some bug fixes. 724 | 725 | Quantitative type theory gives more computational power at the type level. It allows us to specify a quantity for each variable: 726 | 727 | - 0 - which means that the variable is not used at run-time 728 | - 1 - which means that the variable is used only once at run-time 729 | - Unrestricted (default) - which is the same behavior as Idris 1 730 | 731 | Consider the `lengthMyVect` example from before. If we change its definition from `k` to `k + k`, the type checker will not complain: 732 | 733 | ``` 734 | lengthMyVect : {n : Nat} -> MyVect n -> Nat 735 | lengthMyVect {n = k} _ = k + k 736 | ``` 737 | 738 | However, if we specify that the variable `n` can be used only once: 739 | 740 | ``` 741 | lengthMyVect : {1 n : Nat} -> MyVect n -> Nat 742 | ``` 743 | 744 | Then the type checker will throw an error for `k + k`, but not for `k`. 745 | 746 | [^ch4n1]: It is worth noting that in Haskell we have types and kinds. Kinds are similar to types, that is, they are defined as one level above types in simply-typed lambda calculus. For example, types such as `Nat` have a kind `Nat :: *` and it's stated that `Nat` is of kind `*`. Types such as `Nat -> Nat` have a kind of `* -> *`. Since in Idris the types are first-class citizens, there is no distinction between types and kinds. 747 | 748 | [^ch4n2]: A polymorphic type can accept additional types as arguments, which are either defined by the programmer or primitive ones. 749 | 750 | [^ch4n3]: The value and type constructors must be named differently since types and values are at the same level in Idris. 751 | 752 | [^ch4n4]: By built-in, we usually mean it's part of Idris' library. We can always implement it ourselves if we need to. 753 | 754 | [^ch4n5]: Although product and sum types are very general, due to polymorphism, we can say something very specific about the structure of their values. For instance, suppose we've defined a type like so: `data C a b c = C_left a | C_right (b,c)`. A value of type `C` can only come into existence in one of two ways: as a value of the form `C_left x` for a value `x : a`, or as a value of the form `C_right (y,z)` for values `y : b` and `z : c`. 755 | 756 | [^ch4n6]: The key idea is not that a tail-recursive function _is_ an iterative loop, but that a smart enough compiler can _pretend_ that it is and evaluate it using constant function stack space. 757 | 758 | [^ch4n7]: Partial (non-terminating) functions are what make Idris Turing complete. 759 | 760 | [^ch4n8]: They are equivalent to Haskell's `class` keyword. Interfaces in Idris are very similar to OOP's interfaces. 761 | -------------------------------------------------------------------------------- /manuscript/chapter5.md: -------------------------------------------------------------------------------- 1 | # 5. Proving in Idris 2 | 3 | In this chapter, we will provide several examples to demonstrate the power of Idris. We will do mathematical proofs. There are a lot of Idris built-ins that will help us achieve our goals and in each section, we will introduce the relevant definitions. 4 | 5 | I> ### Definition 1 6 | I> 7 | I> The equality data type is roughly defined as follows: 8 | I> 9 | I> ``` 10 | I> data (=) : a -> b -> Type where 11 | I> Refl : x = x 12 | I> ``` 13 | I> 14 | I> We can use the value constructor `Refl` to prove equalities. 15 | 16 | I> ### Definition 2 17 | I> 18 | I> The function `the` accepts `A : Type` and `value : A` and then returns `value : A`. We can use it to manually assign a type to an expression. 19 | 20 | X> ### Exercise 1 21 | X> 22 | X> Check the documentation of the equality type with `:doc (=)`. 23 | 24 | X> ### Exercise 2 25 | X> 26 | X> Evaluate `the Nat 3` and `the Integer 3` and note the differences. Afterward, try to implement `the'` that will act just like `the` and test the previous evaluations again. 27 | 28 | X> ### Exercise 3 29 | X> 30 | X> Given `data Or a b = Or_introl a | Or_intror b`, show that {$$}a \to (a \lor b){/$$} and {$$}b \to (a \lor b){/$$}. Afterwards, check the documentation of the built-in `Either` and compare it to our `Or`. 31 | X> 32 | X> Hint: 33 | X> 34 | X> ``` 35 | X> proof_1 : a -> Or a b 36 | X> proof_1 a = Or_introl ?prf 37 | X> ``` 38 | 39 | X> ### Exercise 4 40 | X> 41 | X> In section 4.2 we implemented `And`. How does it compare to the built-in `Pair`? 42 | 43 | ## 5.1. Weekdays 44 | 45 | In this section, we will introduce a way to represent weekdays and then do some proofs with them. We start with the following data structure: 46 | 47 | ``` 48 | data Weekday = Mon | Tue | Wed | Thu | Fri | Sat | Sun 49 | ``` 50 | 51 | The `Weekday` type has 7 value constructors, one for each weekday. The data type and its constructors do not accept any parameters. 52 | 53 | ### 5.1.1. First proof (auto-inference) 54 | 55 | We will prove that after Monday comes Tuesday. We start by implementing a function that, given a weekday, returns the next weekday: 56 | 57 | ``` 58 | total next_day : Weekday -> Weekday 59 | next_day Mon = Tue 60 | next_day Tue = Wed 61 | next_day Wed = Thu 62 | next_day Thu = Fri 63 | next_day Fri = Sat 64 | next_day Sat = Sun 65 | next_day Sun = Mon 66 | ``` 67 | 68 | Given these definitions, we can write the proof as follows: 69 | 70 | ``` 71 | our_first_proof : next_day Mon = Tue 72 | our_first_proof = ?prf 73 | ``` 74 | 75 | Note how we used the function `next_day Mon = Tue` at the type level, for the type of `our_first_proof`. Since `next_day` is total, Idris will be able to evaluate `next_day Mon` at compile-time. We used a hole for the definition since we still don't know what the proof will look like. If we run this code in Idris, it will tell us we have a hole `prf` to fill: 76 | 77 | ``` 78 | Type checking ./first_proof.idr 79 | Holes: Main.prf 80 | Idris> :t prf 81 | -------------------------------------- 82 | prf : Tue = Tue 83 | ``` 84 | 85 | Checking the type of `prf` we notice how Idris evaluated the left part of the equation at compile-time. In order to prove that `Tue = Tue`, we can just use `Refl`: 86 | 87 | ``` 88 | our_first_proof : next_day Mon = Tue 89 | our_first_proof = Refl 90 | ``` 91 | 92 | Reloading the file in Idris: 93 | 94 | ``` 95 | Idris> :r 96 | Type checking ./first_proof.idr 97 | ``` 98 | 99 | The type check was successful. Per the Curry-Howard isomorphism, this means that we've successfully proven that `next_day Mon = Tue`. So, after Monday comes Tuesday! 100 | 101 | X> ### Exercise 5 102 | X> 103 | X> Remove one or more pattern match definitions of `next_day` and observe the error that Idris will produce. Afterward, alter the function so that it is not total anymore, and observe the results. 104 | 105 | X> ### Exercise 6 106 | X> 107 | X> Implement `prev_day` and prove that Sunday is before Monday. 108 | 109 | ### 5.1.2. Second proof (rewrite) 110 | 111 | In addition to the previous proof, we'll implement a function that accepts a `Weekday` and returns `True` if it's Monday and `False` otherwise. 112 | 113 | ``` 114 | is_it_monday : Weekday -> Bool 115 | is_it_monday Mon = True 116 | is_it_monday _ = False 117 | ``` 118 | 119 | For the sake of example, we will prove that for any given day, if it's Monday then `is_it_monday` will return `True`. It's obvious from the definition of `is_it_monday`, but proving that is a whole different story. The type definition that we need to prove is: 120 | 121 | ``` 122 | our_second_proof : (day : Weekday) -> day = Mon -> 123 | is_it_monday day = True 124 | ``` 125 | 126 | We gave a name to the first parameter `day : Weekday` so that we can refer to it in the rest of the type definition. The second parameter says that `day = Mon` and the return value is `is_it_monday day = True`. We can treat the first and the second parameter as givens since we are allowed to assume them (per definition of implication). With that, we proceed to the function definition: 127 | 128 | ``` 129 | our_second_proof day day_eq_Mon = Refl 130 | ``` 131 | 132 | In this definition, `day` and `day_eq_Mon` are our assumptions (givens). If we run this code in Idris, it will produce an error at compile-time since it cannot deduce that `True` is equal to `is_it_monday day`. In the previous proof example, Idris was able to infer everything from the definitions at compile-time. However, at this point, we need to help Idris do the inference since it cannot derive the proof based only on the definitions. We can change the `Refl` to a hole `prf`: 133 | 134 | ``` 135 | day : Weekday 136 | day_eq_Mon : day = Mon 137 | -------------------------------------- 138 | prf : is_it_monday day = True 139 | ``` 140 | 141 | Note how checking the type of the hole lists the givens/premises (above the separator), and our goal(s) (below the separator). We see that along with `prf` we also get `day` and `day_eq_Mon` in the list of givens, per the left-hand side of the function definition of `our_second_proof`. 142 | 143 | Q> How do we replace something we have in the givens, with the goal? 144 | Q> 145 | Q> If we only had a way to tell Idris that it just needs to replace `day` with `day = Mon` to get to `is_it_monday Mon = True`, it will be able to infer the rest. 146 | 147 | I> ### Definition 3 148 | I> 149 | I> The `rewrite` keyword can be used to rewrite expressions. If we have `X : x = y`, then the syntax `rewrite X in Y` will replace all occurrences of `x` with `y` in `Y`. 150 | 151 | With the power to do rewrites, we can attempt the proof as follows: 152 | 153 | ``` 154 | our_second_proof : (day : Weekday) -> day = Mon -> 155 | is_it_monday day = True 156 | our_second_proof day day_eq_Mon = rewrite day_eq_Mon in ?prf 157 | ``` 158 | 159 | Idris produces: 160 | 161 | ``` 162 | Idris> :t prf 163 | day : Weekday 164 | day_eq_Mon : day = Mon 165 | _rewrite_rule : day = Mon 166 | -------------------------------------- 167 | prf : True = True 168 | ``` 169 | 170 | Changing `prf` to `Refl` completes the proof. We just proved that {$$}\forall x \in \text{Weekdays}, x = \text{Mon} \to IsItMonday(x){/$$}. We assumed {$$}x = \text{Mon}{/$$} is true (by pattern matching against `day_eq_Mon` in our definition), and then used rewriting to alter `x`. 171 | 172 | X> ### Exercise 7 173 | X> 174 | X> Implement the function `is_it_sunday` that returns `True` if the given day is Sunday, and `False` otherwise. 175 | 176 | X> ### Exercise 8 177 | X> 178 | X> Prove the following formula in Idris: {$$}\forall x \in \text{Weekdays}, x = \text{Sun} \to IsItSunday(x){/$$}. 179 | 180 | ### 5.1.3. Third proof (impossible) 181 | 182 | In this section, we will prove that `is_it_monday Tue = True` is a contradiction. 183 | 184 | Per intuitionistic logic, in order to prove that {$$}P{/$$} is a contradiction, we need to prove {$$}P \to \bot{/$$}. Idris provides an empty data type `Void`. This data type has no value constructors (proofs) for it. 185 | 186 | To prove that `is_it_monday Tue = True` is a contradiction, we will do the following: 187 | 188 | ``` 189 | our_third_proof : is_it_monday Tue = True -> Void 190 | our_third_proof mon_is_Tue = ?prf 191 | ``` 192 | 193 | Checking the type of the hole: 194 | 195 | ``` 196 | mon_is_Tue : False = True 197 | -------------------------------------- 198 | prf : Void 199 | ``` 200 | 201 | Q> How do we prove `prf`, given that there are no value constructors for `Void`? 202 | Q> 203 | Q> Seems that at this point we are stuck. We need to find a way to tell Idris that this proof is impossible. 204 | 205 | I> ### Definition 4 206 | I> 207 | I> The `impossible` keyword can be used to prove statements that are not true. With this keyword, we say that proof for a data type cannot be constructed since there does not exist a value constructor for that particular type. 208 | 209 | We will slightly rewrite the function: 210 | 211 | ``` 212 | our_third_proof : is_it_monday Tue = True -> Void 213 | our_third_proof Refl impossible 214 | ``` 215 | 216 | With this syntax, we're telling Idris that the reflexivity of `False = True` is impossible and thus the proof is complete. 217 | 218 | X> ### Exercise 9 219 | X> 220 | X> Check the documentation of `Void` and try to implement `Void'` yourself. Rewrite the proof above to use `Void'` instead of `Void`. 221 | 222 | X> ### Exercise 10 223 | X> 224 | X> Prove that `1 = 2` is a contradiction. 225 | X> 226 | X> Hint: The type is `1 = 2 -> Void` 227 | 228 | ## 5.2. Natural numbers 229 | 230 | In this section, we will prove facts about natural numbers and also do some induction. Recall that a natural number is defined either as zero or as the successor of a natural number. So, `0, S 0, S (S 0), ...` are the first natural numbers. We will start with the following definitions: 231 | 232 | ``` 233 | data MyNat = Zero | Succ MyNat 234 | 235 | total mynat_plus : MyNat -> MyNat -> MyNat 236 | mynat_plus Zero m = m 237 | mynat_plus (Succ k) m = Succ (mynat_plus k m) 238 | ``` 239 | 240 | Note how the definition of `MyNat` is recursive compared to `Weekday`. A consequence of that is that we may need to use induction for some proofs. 241 | 242 | X> ### Exercise 11 243 | X> 244 | X> Compare the addition definition to Definition 21 in chapter 2. 245 | 246 | ### 5.2.1. First proof (auto-inference and existence) 247 | 248 | We will prove that {$$}0 + a = a{/$$}, given the definitions for natural numbers and addition. 249 | 250 | For that, we need to implement a function that accepts a natural number `a` and returns the proposition that `mynat_plus Zero a = a`. 251 | 252 | ``` 253 | total our_first_proof_inf : (a : MyNat) -> mynat_plus Zero a = a 254 | our_first_proof_inf a = ?prf 255 | ``` 256 | 257 | If we check the type of the hole, we get that the goal is `prf : a = a`, so changing the hole to a `Refl` completes the proof. Idris was able to automatically infer the proof by directly substituting definitions. 258 | 259 | To prove the existence of a successor, i.e. `Succ x`, per intuitionistic logic we need to construct a pair where the first element is `x : MyNat` and the second element is `Succ x : MyNat`. Idris has a built-in data structure for constructing dependent pairs called `DPair`. 260 | 261 | ``` 262 | total our_first_proof_exist : MyNat -> DPair MyNat (\_ => MyNat) 263 | our_first_proof_exist x = MkDPair x (Succ x) 264 | ``` 265 | 266 | We just proved that {$$}\exists x \in \text{MyNat}, Succ(x){/$$}. 267 | 268 | X> ### Exercise 12 269 | X> 270 | X> Check the documentation of `DPair` and `MkDPair` and try to construct some dependent pairs. 271 | 272 | ### 5.2.2. Second proof (introduction of a new given) 273 | 274 | An alternative way to prove that a natural number exists is as follows: 275 | 276 | ``` 277 | total our_second_proof : MyNat 278 | our_second_proof = ?prf 279 | ``` 280 | 281 | If we check the type of the hole, we get: 282 | 283 | ``` 284 | Holes: Main.prf 285 | Idris> :t prf 286 | -------------------------------------- 287 | prf : MyNat 288 | ``` 289 | 290 | Q> By the definition of `MyNat` we are sure that there exists a value constructor of `MyNat`, but how do we tell Idris? 291 | 292 | I> ### Definition 5 293 | I> 294 | I> The `let` keyword that we introduced earlier allows us to add a new given to the list of hypotheses. 295 | 296 | We can slightly rewrite our code: 297 | 298 | ``` 299 | total our_second_proof : MyNat 300 | our_second_proof = let the_number = Zero in ?prf 301 | ``` 302 | 303 | Checking the type: 304 | 305 | ``` 306 | the_number : MyNat 307 | -------------------------------------- 308 | prf : MyNat 309 | ``` 310 | 311 | Changing `prf` to `the_number` concludes the proof. 312 | 313 | X> ### Exercise 13 314 | X> 315 | X> Simplify `our_second_proof` without the use of `let`. 316 | X> 317 | X> Hint: Providing a valid value constructor that satisfies (inhabits) the type is a constructive proof. 318 | 319 | X> ### Exercise 14 320 | X> 321 | X> Construct a proof similar to `our_second_proof` without defining a function for it and by using the function `the`. 322 | 323 | ### 5.2.3. Third proof (induction) 324 | 325 | We will prove that {$$}a + 0 = a{/$$}. We can try to use the same approach as in 5.2.1: 326 | 327 | ``` 328 | total our_third_proof : (a : MyNat) -> mynat_plus a Zero = a 329 | our_third_proof a = Refl 330 | ``` 331 | 332 | If we try to run the code above, Idris will produce an error saying that there is a type mismatch between `a` and `mynat_plus a Zero`. 333 | 334 | Q> It seems that we have just about all the definitions we need, but we're missing a piece. How do we re-use our definitions? 335 | Q> 336 | Q> To prove that {$$}a + 0 = a{/$$}, we can use mathematical induction starting with the definitions we already have as the base case and build on top of that until {$$}a + 0 = a{/$$}. That is, we need to prove that {$$}0 + 0 = 0 \to (a - a) + 0 = a - a \to ... \to (a - 1) + 0 = a - 1 \to a + 0 = a{/$$}. 337 | 338 | From here, we rewrite our function to contain a base case and an inductive step: 339 | 340 | ``` 341 | total our_third_proof : (a : MyNat) -> mynat_plus a Zero = a 342 | our_third_proof Zero = ?base 343 | our_third_proof (Succ k) = ?ind_hypothesis 344 | ``` 345 | 346 | Note how we used pattern matching against the definition of natural numbers. Pattern matching is similar to using proof by cases. Checking the types of the holes: 347 | 348 | ``` 349 | Holes: Main.ind_hypothesis, Main.base 350 | Idris> :t base 351 | -------------------------------------- 352 | base : Zero = Zero 353 | Idris> :t ind_hypothesis 354 | k : MyNat 355 | -------------------------------------- 356 | ind_hypothesis : Succ (mynat_plus k Zero) = Succ k 357 | ``` 358 | 359 | For the base case we can just use `Refl`, but for the inductive step we need to do something different. We need to find a way to assume (add to list of givens) {$$}a + 0 = a{/$$} and show that {$$}(a + 1) + 0 = a + 1{/$$} follows from that assumption. Since we pattern match on `Succ k`, we can use recursion on `k` along with `let` to generate the hypothesis: 360 | 361 | ``` 362 | total our_third_proof : (a : MyNat) -> mynat_plus a Zero = a 363 | our_third_proof Zero = Refl 364 | our_third_proof (Succ k) = let ind_hypothesis = our_third_proof k in 365 | ?conclusion 366 | ``` 367 | 368 | Our proof givens and goals become: 369 | 370 | ``` 371 | k : MyNat 372 | ind_hypothesis : mynat_plus k Zero = k 373 | -------------------------------------- 374 | conclusion : Succ (mynat_plus k Zero) = Succ k 375 | ``` 376 | 377 | To prove the conclusion, we can simply rewrite the inductive hypothesis in the goal and we are done. 378 | 379 | ``` 380 | total our_third_proof : (a : MyNat) -> mynat_plus a Zero = a 381 | our_third_proof Zero = Refl 382 | our_third_proof (Succ k) = let ind_hypothesis = our_third_proof k in 383 | rewrite ind_hypothesis in 384 | Refl 385 | ``` 386 | 387 | This concludes the proof. 388 | 389 | X> ### Exercise 15 390 | X> 391 | X> Observe the similarity between this proof and the proof in section 2.3.4. 392 | 393 | ### 5.2.4. Ordering 394 | 395 | Idris 1 has a built-in data type for the ordering of natural numbers `LTE`, which stands for less than or equal to. This data type has two constructors: 396 | 397 | 1. `LTEZero`, used to prove that zero is less than or equal to any natural number 398 | 1. `LTESucc`, used to prove that {$$}a \leq b \to S(a) \leq S(b){/$$} 399 | 400 | If we check the type of `LTEZero`, we will get the following: 401 | 402 | ``` 403 | Idris> :t LTEZero 404 | LTEZero : LTE 0 right 405 | ``` 406 | 407 | `LTEZero` does not accept any arguments, but we can pass `right` at the type level. With the use of implicits, we can construct a very simple proof to show that {$$}0 \leq 1{/$$}: 408 | 409 | ``` 410 | Idris> LTEZero {right = S Z} 411 | LTEZero : LTE 0 1 412 | ``` 413 | 414 | Similarly, with `LTESucc` we can do the same: 415 | 416 | ``` 417 | Idris> LTESucc {left = Z} {right = S Z} 418 | LTESucc : LTE 0 1 -> LTE 1 2 419 | ``` 420 | 421 | X> ### Exercise 16 422 | X> 423 | X> Check the documentation of `GTE`, and then evaluate `GTE 2 2`. Observe what Idris returns and think about how `GTE` can be implemented in terms of `LTE`. 424 | 425 | X> ### Exercise 17 426 | X> 427 | X> We used the built-in type `LTE` which is defined for `Nat`. Try to come up with a `LTE` definition for `MyNat`. 428 | 429 | ### 5.2.5. Safe division 430 | 431 | Idris 1 provides a function called `divNat` that divides two numbers. Checking the documentation: 432 | 433 | ``` 434 | Idris> :doc divNat 435 | Prelude.Nat.divNat : Nat -> Nat -> Nat 436 | 437 | The function is not total as there are missing cases 438 | ``` 439 | 440 | We can try to use it a couple of times: 441 | 442 | ``` 443 | Idris> divNat 4 2 444 | 2 : Nat 445 | Idris> divNat 4 1 446 | 4 : Nat 447 | Idris> divNat 4 0 448 | divNat 4 0 : Nat 449 | ``` 450 | 451 | As expected, partial functions do not cover all inputs and thus `divNat` does not return a computed value when we divide by zero. 452 | 453 | Q> How do we make a function like `divNat` total? 454 | Q> 455 | Q> The only way to make this work is to pass a proof to `divNat` that says that the divisor is not zero. Idris has a built-in function for that, called `divNatNZ`. 456 | 457 | We can check the documentation of this function as follows: 458 | 459 | ``` 460 | Idris> :doc divNatNZ 461 | Prelude.Nat.divNatNZ : Nat -> (y : Nat) -> Not (y = 0) -> Nat 462 | Division where the divisor is not zero. 463 | The function is Total 464 | ``` 465 | 466 | This function is total, but we need to also provide a parameter (proof) that the divisor is not zero. Fortunately, Idris 1 also provides a function called `SIsNotZ`, which accepts any natural number (through implicit argument `x`) and returns a proof that `x + 1` is not zero. 467 | 468 | We can try to construct a few proofs: 469 | 470 | ``` 471 | Idris> SIsNotZ {x = 0} 472 | SIsNotZ : (1 = 0) -> Void 473 | Idris> SIsNotZ {x = 1} 474 | SIsNotZ : (2 = 0) -> Void 475 | Idris> SIsNotZ {x = 2} 476 | SIsNotZ : (3 = 0) -> Void 477 | ``` 478 | 479 | Great. It seems we have everything we need. We can safely divide as follows: 480 | 481 | ``` 482 | Idris> divNatNZ 4 2 (SIsNotZ {x = 1}) 483 | 2 : Nat 484 | Idris> divNatNZ 4 1 (SIsNotZ {x = 0}) 485 | 4 : Nat 486 | Idris> divNatNZ 4 0 (SIsNotZ {x = ???}) 487 | 4 : Nat 488 | ``` 489 | 490 | We cannot construct a proof for the third case and so it will never be able to divide by zero, which is not allowed anyway. 491 | 492 | X> ### Exercise 18 493 | X> 494 | X> Implement `SuccIsNotZ` for `MyNat` that works similarly to `SIsNotZ`. 495 | 496 | X> ### Exercise 19 497 | X> 498 | X> Implement `minus` and `lte` (and optionally `div`) for `MyNat`. 499 | 500 | ### 5.2.6. Maximum of two numbers 501 | 502 | I> ### Definition 6 503 | I> 504 | I> The maximum of two numbers `a` and `b` is defined as: 505 | I> 506 | I> {$$}max(a, b) = \left\{ \begin{array}{ll} b\text{, if } a \leq b \\ a\text{, otherwise} \end{array} \right.{/$$} 507 | 508 | In this section we will try to prove that {$$}a \leq b \to b = max(a, b){/$$}. Idris 1 already has a built-in function `maximum`, so we can re-use that. Next, we need to figure out the type of the function to approach the proof. Intuitively, we can try the following: 509 | 510 | ``` 511 | our_proof : (a : Nat) -> (b : Nat) -> a <= b -> maximum a b = b 512 | our_proof a b a_lt_b = ?prf 513 | ``` 514 | 515 | However this won't work since `a <= b` is a `Bool` (per the function `<=`), not a `Type`. At the type level, we need to rely on `LTE` which is a `Type`. 516 | 517 | ``` 518 | our_proof : (a : Nat) -> (b : Nat) -> LTE a b -> maximum a b = b 519 | our_proof a b a_lt_b = ?prf 520 | ``` 521 | 522 | This compiles and we have to figure out the hole. If we check its type, we get: 523 | 524 | ``` 525 | a : Nat 526 | b : Nat 527 | a_lt_b : LTE a b 528 | -------------------------------------- 529 | prf : maximum a b = b 530 | ``` 531 | 532 | This looks a bit complicated, so we can further simplify by breaking the proof into several cases by adding pattern matching for all combinations of the parameters' value constructors: 533 | 534 | ``` 535 | our_proof : (a : Nat) -> (b : Nat) -> LTE a b -> maximum a b = b 536 | our_proof Z Z _ = Refl 537 | our_proof Z (S k) _ = Refl 538 | our_proof (S k) (S j) a_lt_b = ?prf 539 | ``` 540 | 541 | We get the following: 542 | 543 | ``` 544 | k : Nat 545 | j : Nat 546 | a_lt_b : LTE (S k) (S j) 547 | -------------------------------------- 548 | prf : S (maximum k j) = S j 549 | ``` 550 | 551 | It seems like we made progress, as this gives us something to work with. We can use induction on `k` and `j`, and use a hole for the third parameter to ask Idris what type we need to satisfy: 552 | 553 | ``` 554 | our_proof : (a : Nat) -> (b : Nat) -> LTE a b -> maximum a b = b 555 | our_proof Z Z _ = Refl 556 | our_proof Z (S k) _ = Refl 557 | our_proof (S k) (S j) a_lt_b = let IH = (our_proof k j ?prf) in 558 | rewrite IH in 559 | Refl 560 | ``` 561 | 562 | The hole produces the following: 563 | 564 | ``` 565 | Holes: Main.prf 566 | Idris> :t prf 567 | k : Nat 568 | j : Nat 569 | a_lt_b : LTE (S k) (S j) 570 | -------------------------------------- 571 | prf : LTE k j 572 | ``` 573 | 574 | Q> How do we go from {$$}S(a) \leq S(b) \to a \leq b{/$$}? 575 | Q> 576 | Q> It seems pretty obvious that if we know that {$$}1 \leq 2{/$$}, then also {$$}0 \leq 1{/$$}, but we still need to find out how to tell Idris that this is true. For this, Idris has a built-in function `fromLteSucc`: 577 | Q> 578 | Q> ``` 579 | Q> Idris> :doc fromLteSucc 580 | Q> Prelude.Nat.fromLteSucc : LTE (S m) (S n) -> LTE m n 581 | Q> If two numbers are ordered, their predecessors are too 582 | Q> ``` 583 | 584 | It seems we have everything we need to conclude our proof. We can proceed as follows: 585 | 586 | ``` 587 | total 588 | our_proof : (a : Nat) -> (b : Nat) -> LTE a b -> maximum a b = b 589 | our_proof Z Z _ = Refl 590 | our_proof Z (S k) _ = Refl 591 | our_proof (S k) (S j) a_lt_b = let fls = fromLteSucc a_lt_b in 592 | let IH = (our_proof k j fls) in 593 | rewrite IH in 594 | Refl 595 | ``` 596 | 597 | or a more simplified version: 598 | 599 | ``` 600 | total 601 | our_proof : (a : Nat) -> (b : Nat) -> LTE a b -> maximum a b = b 602 | our_proof Z Z _ = Refl 603 | our_proof Z (S k) _ = Refl 604 | our_proof (S k) (S j) a_lt_b = rewrite 605 | (our_proof k j (fromLteSucc a_lt_b)) in 606 | Refl 607 | ``` 608 | 609 | X> ### Exercise 20 610 | X> 611 | X> Use `fromLteSucc` with implicits to construct some proofs. 612 | 613 | ### 5.2.7. List of even naturals 614 | 615 | We will prove that a list of even numbers contains no odd numbers. We will re-use the functions `even` in 4.1.4 and `even_members` in 4.1.11. We will also need another function to check if a list has odd numbers: 616 | 617 | ``` 618 | total has_odd : MyList Nat -> Bool 619 | has_odd End = False 620 | has_odd (Cons x l') = if (even x) then has_odd l' else True 621 | ``` 622 | 623 | To prove that a list of even numbers contains no odd numbers, we can use the following type definition: 624 | 625 | ``` 626 | even_members_list_only_even : (l : MyList Nat) -> 627 | has_odd (even_members l) = False 628 | ``` 629 | 630 | Note that `has_odd` is branching computation depending on the value of `even x`, so we have to pattern match with the value of expressions by using the keyword `with`. The base case is simply `Refl`: 631 | 632 | ``` 633 | even_members_list_only_even End = Refl 634 | ``` 635 | 636 | However, for the inductive step, we will use `with` on `even n` and produce proof depending on the evenness of the number: 637 | 638 | ``` 639 | even_members_list_only_even (Cons n l') with (even n) proof even_n 640 | even_members_list_only_even (Cons n l') | False = 641 | let IH = even_members_list_only_even l' in ?a 642 | even_members_list_only_even (Cons n l') | True = 643 | let IH = even_members_list_only_even l' in ?b 644 | ``` 645 | 646 | Note how we specified `proof even_n` right after the expression in the `with` match. The `proof` keyword followed by a variable brings us the proof of the expression to the list of premises. The expression `with (even n) proof even_n` will pattern match on the results of `even n`, and will also bring the proof `even n` in the premises. If we now check the first hole: 647 | 648 | ``` 649 | n : Nat 650 | l' : MyList Nat 651 | even_n : False = even n 652 | IH : has_odd (even_members l') = False 653 | -------------------------------------- 654 | a : has_odd (even_members l') = False 655 | ``` 656 | 657 | That should be simple, we can just use `IH` to solve the goal. For the second hole, we have: 658 | 659 | ``` 660 | n : Nat 661 | l' : MyList Nat 662 | even_n : True = even n 663 | IH : has_odd (even_members l') = False 664 | -------------------------------------- 665 | b : ifThenElse (even n) (has_odd (even_members l')) 666 | True = False 667 | ``` 668 | 669 | Q> How do we rewrite the inductive hypothesis to the goal in this case? 670 | Q> 671 | Q> It seems that we can't just rewrite here since `even_n` has the order of the equality reversed. Idris provides a function called `sym` which takes an equality of `a = b` and converts it to `b = a`. 672 | 673 | We can try to rewrite `sym even_n` to the goal, and it now becomes: 674 | 675 | ``` 676 | n : Nat 677 | l' : MyList Nat 678 | even_n : True = even n 679 | IH : has_odd (even_members l') = False 680 | _rewrite_rule : even n = True 681 | -------------------------------------- 682 | b : has_odd (even_members l') = False 683 | ``` 684 | 685 | Similarly to before we will use `IH` to solve the goal. Thus, the complete proof: 686 | 687 | ``` 688 | even_members_list_only_even : (l : MyList Nat) -> 689 | has_odd (even_members l) = False 690 | even_members_list_only_even End = Refl 691 | even_members_list_only_even (Cons n l') with (even n) proof even_n 692 | even_members_list_only_even (Cons n l') | False = 693 | let IH = even_members_list_only_even l' in IH 694 | even_members_list_only_even (Cons n l') | True = 695 | let IH = even_members_list_only_even l' in 696 | rewrite sym even_n in IH 697 | ``` 698 | 699 | Q> How did mathematical induction work in this case? 700 | Q> 701 | Q> Mathematical induction is defined in terms of natural numbers, but in this case, we used induction to prove a fact about a list. This works because we used a more general induction called _structural induction_. Structural induction is used to prove that some proposition {$$}P(x){/$$} holds for all {$$}x{/$$} of some sort of recursively defined data structure. For example, for lists, we used `End` as the base case and `Cons` as the inductive step. Thus, mathematical induction is a special case of structural induction for the `Nat` type. 702 | 703 | X> ### Exercise 21 704 | X> 705 | X> Rewrite `has_odd` to use `with` in the recursive case, and then repeat the proof above. 706 | 707 | ### 5.2.8. Partial orders 708 | 709 | I> ### Definition 7 710 | I> 711 | I> A binary relation {$$}R{/$$} on some set {$$}S{/$$} is a partial order if the following properties are satisfied: 712 | I> 713 | I> 1. {$$}\forall a \in S, a R a{/$$}, i.e. reflexivity 714 | I> 1. {$$}\forall a, b, c \in S, a R b \land b R c \to a R c{/$$}, i.e. transitivity 715 | I> 1. {$$}\forall a, b \in S, a R b \land b R a \to a = b{/$$}, i.e. antisymmetry 716 | 717 | Let's abstract this in Idris as an `interface`: 718 | 719 | ``` 720 | interface Porder (a : Type) (Order : a -> a -> Type) | Order where 721 | total proofR : Order n n -- reflexivity 722 | total proofT : Order n m -> Order m p -> Order n p -- transitivity 723 | total proofA : Order n m -> Order m n -> n = m -- antisymmetry 724 | ``` 725 | 726 | The interface `Porder` accepts a `Type` and a relation `Order` which is a built-in binary function in Idris 1. Since the interface has more than two parameters, we specify that `Order` is a determining parameter, i.e. the parameter used to resolve the instance. 727 | 728 | Now that we have our abstract interface we can build a concrete implementation for it: 729 | 730 | ``` 731 | implementation Porder Nat LTE where 732 | proofR {n = Z} = 733 | LTEZero 734 | proofR {n = S _} = 735 | LTESucc proofR 736 | 737 | proofT LTEZero _ = 738 | LTEZero 739 | proofT (LTESucc n_lte_m) (LTESucc m_lte_p) = 740 | LTESucc (proofT n_lte_m m_lte_p) 741 | 742 | proofA LTEZero LTEZero = 743 | Refl 744 | proofA (LTESucc n_lte_m) (LTESucc m_lte_n) = 745 | let IH = proofA n_lte_m m_lte_n in rewrite IH in Refl 746 | ``` 747 | 748 | We proved that the binary operation "less than or equal to" for `Nat`s make a `Porder`. Interfaces allow us to group one or more functions, and implementation of a specific interface is guaranteed to implement all such functions. 749 | 750 | X> ### Exercise 22 751 | X> 752 | X> Convince yourself using pen and paper that {$$}\leq{/$$} on natural numbers makes a partial order, i.e. it satisfies all properties of Definition 7. Afterward, try to understand the proofs for reflexivity, transitivity, and antisymmetry by deducing them yourself in Idris using holes. 753 | 754 | ## 5.3. Computations as types 755 | 756 | As we stated earlier, types are first-class citizens in Idris. In this section, we will see how we can represent computation at the type level. 757 | 758 | ### 5.3.1. Same elements in a list (vector) 759 | 760 | Re-using the same definition of `MyVect`, we can write a function to test if all elements are the same in a given list: 761 | 762 | ``` 763 | allSame : (xs : MyVect n) -> Bool 764 | allSame Empty = True 765 | allSame (Cons x Empty) = True 766 | allSame (Cons x (Cons y xs)) = x == y && allSame xs 767 | ``` 768 | 769 | Idris will return `True` in case all elements are equal to each other, and `False` otherwise. Let's now think about how we can represent this function in terms of types. We want to have a type `AllSame` that has three constructors: 770 | 771 | 1. `AllSameZero` which is a proof for `AllSame` in case of an empty list 772 | 1. `AllSameOne` which is a proof for `AllSame` in case of a single-element list 773 | 1. `AllSameMany` which is a proof for `AllSame` in case of a list with multiple elements 774 | 775 | This is how the data type could look like: 776 | 777 | ``` 778 | data AllSame : MyVect n -> Type where 779 | AllSameZero : AllSame Empty 780 | AllSameOne : (x : Nat) -> AllSame (Cons x Empty) 781 | AllSameMany : (x : Nat) -> (y : Nat) -> (ys : MyVect _) -> 782 | True = (x == y) -> AllSame (Cons y ys) -> 783 | AllSame (Cons x (Cons y ys)) 784 | ``` 785 | 786 | The constructors `AllSameZero` and `AllSameOne` are easy. However, the recursive constructor `AllSameMany` is a bit trickier. It accepts two natural numbers `x` and `y`, a list `ys`, a proof that `x` and `y` are the same, and a proof that `y` concatenated to `ys` is a same-element list. Given this, it will produce a proof that `x` concatenated to `y` concatenated to `ys` is also a same-element list. This type definition captures exactly the definition of a list that would contain all the same elements. 787 | 788 | Interacting with the constructors: 789 | 790 | ``` 791 | Idris> AllSameZero 792 | AllSameZero : AllSame Empty 793 | Idris> AllSameOne 1 794 | AllSameOne 1 : AllSame (Cons 1 Empty) 795 | Idris> AllSameMany 1 1 Empty Refl (AllSameOne 1) 796 | AllSameMany 1 1 Empty Refl (AllSameOne 1) : AllSame (Cons 1 797 | (Cons 1 Empty)) 798 | ``` 799 | 800 | The third example is a proof that the list `[1, 1]` has the same elements. However, if we try to use the constructor with different elements: 801 | 802 | ``` 803 | Idris> AllSameMany 1 2 Empty 804 | AllSameMany 1 2 Empty : (True = False) -> AllSame (Cons 2 Empty) -> 805 | AllSame (Cons 1 (Cons 2 Empty)) 806 | ``` 807 | 808 | We see that Idris requires us to provide proof that `True = False`, which is impossible. So for some lists, the type `AllSame` cannot be constructed, but for some, it can. If we now want to make a function that given a list, it maybe produces a type `AllSame`, we need to consider the `Maybe` data type first which has the following definition: 809 | 810 | ``` 811 | data Maybe a = Just a | Nothing 812 | ``` 813 | 814 | Interacting with it: 815 | 816 | ``` 817 | Idris> the (Maybe Nat) (Just 3) 818 | Just 3 : Maybe Nat 819 | Idris> the (Maybe Nat) Nothing 820 | Nothing : Maybe Nat 821 | ``` 822 | 823 | We can now proceed to write our function: 824 | 825 | ``` 826 | mkAllSame : (xs : MyVect n) -> Maybe (AllSame xs) 827 | mkAllSame Empty = Just AllSameZero 828 | mkAllSame (Cons x Empty) = Just (AllSameOne x) 829 | mkAllSame (Cons x (Cons y xs)) with (x == y) proof x_eq_y 830 | mkAllSame (Cons x (Cons y xs)) | False = 831 | Nothing 832 | mkAllSame (Cons x (Cons y xs)) | True = 833 | case (mkAllSame (Cons y xs)) of 834 | Just y_eq_xs => Just (AllSameMany x y xs x_eq_y y_eq_xs) 835 | Nothing => Nothing 836 | ``` 837 | 838 | Interacting with it: 839 | 840 | ``` 841 | Idris> mkAllSame (Cons 1 Empty) 842 | Just (AllSameOne 1) : Maybe (AllSame (Cons 1 Empty)) 843 | Idris> mkAllSame (Cons 1 (Cons 1 Empty)) 844 | Just (AllSameMany 1 1 Empty Refl (AllSameOne 1)) : Maybe (AllSame 845 | (Cons 1 (Cons 1 Empty))) 846 | Idris> mkAllSame (Cons 1 (Cons 2 Empty)) 847 | Nothing : Maybe (AllSame (Cons 1 (Cons 2 Empty))) 848 | ``` 849 | 850 | For lists that contain the same elements it will use the `Just` constructor, and `Nothing` otherwise. Finally, we can rewrite our original `allSame` as follows: 851 | 852 | ``` 853 | allSame' : MyVect n -> Bool 854 | allSame' xs = case (mkAllSame xs) of 855 | Nothing => False 856 | Just _ => True 857 | ``` 858 | 859 | ### 5.3.2. Evenness of numbers 860 | 861 | Next, we will represent a data type using a natural deduction style and then do the same corresponding definitions in Idris. 862 | 863 | I> ### Definition 8 864 | I> 865 | I> In natural deduction style, propositions are represented with a line in the middle where everything above the line are the premises and everything below it is the conclusion. 866 | 867 | When there is an implication ({$$}\to{/$$}) within one of the rules in natural deduction style, then this implication is thought to be at the object level, while the actual line represents implication at the metalanguage level. We'll see an example of this in Appendix A. 868 | 869 | As an example, let the first inference rule be {$$}\frac{}{Ev \ 0}{/$$}. Note that there is nothing above the line, so we can think of this as an "axiom". Using the previous notation, this would just be {$$}Ev \ 0{/$$}. Further, let the second inference rule be {$$}\frac{Ev \ n}{Ev \ (S \ (S \ n))}{/$$}. Using the previous notation, this would be {$$}\forall n, Ev \ n \to Ev \ (S \ (S \ n)){/$$}. 870 | 871 | The same representation in Idris: 872 | 873 | ``` 874 | data Ev : Nat -> Type where 875 | EvZero : Ev Z 876 | EvSucc : (n : Nat) -> Ev n -> Ev (S (S n)) 877 | ``` 878 | 879 | Having this definition, we can now construct proofs of even numbers as follows: 880 | 881 | ``` 882 | Idris> EvSucc 0 EvZero 883 | EvSucc 0 EvZero : Ev 2 884 | Idris> EvSucc 2 (EvSucc 0 EvZero) 885 | EvSucc 2 (EvSucc 0 EvZero) : Ev 4 886 | ``` 887 | 888 | We just represented even numbers at the type level, where in 4.1.4 we represented them at the value level with the function `even`. 889 | 890 | ## 5.4. Trees 891 | 892 | A tree structure is a way to represent hierarchical data. We will work with binary trees in this section, which are trees that contain exactly two sub-trees (nodes). We can define this tree structure using the following implementation: 893 | 894 | ``` 895 | data Tree = Leaf | Node Nat Tree Tree 896 | ``` 897 | 898 | This definition states that a tree is defined as one of: 899 | 900 | 1. `Leaf`, which has no values 901 | 1. `Node`, which holds a number and points to two other trees (which can be either `Node`s or `Leaf`s) 902 | 903 | For example, we can use the expression `Node 2 (Node 1 Leaf Leaf) (Node 3 Leaf Leaf)` to represent the following tree: 904 | 905 | ```text 906 | 2 907 | / \ 908 | 1 3 909 | ``` 910 | 911 | Edges can be thought of as the number of "links" from a node to its children. Node 2 in the tree above has two edges: {$$}(2, 1){/$$} and {$$}(2, 3){/$$}. 912 | 913 | X> ### Exercise 23 914 | X> 915 | X> Come up with a few trees by using the value constructors above. 916 | 917 | ### 5.4.1. Depth 918 | 919 | I> ### Definition 9 920 | I> 921 | I> The depth of a tree is defined as the number of edges from the node to the root. 922 | 923 | We can implement the recursive function `depth` as follows: 924 | 925 | ``` 926 | depth : Tree -> Nat 927 | depth Leaf = 0 928 | depth (Node n l r) = 1 + maximum (depth l) (depth r) 929 | ``` 930 | 931 | If we pass a `Tree` to the function `depth`, then Idris will pattern match the types and we can extract the values. For example, in the `Node` case, we pattern match to extract the sub-trees `l` and `r` for further processing. For the `Leaf` case, since it's just an empty leaf there are no links to it. 932 | 933 | In order to prove that the `depth` of a tree is greater than or equal to zero, we can approach the proof as follows: 934 | 935 | ``` 936 | depth_tree_gt_0 : (tr : Tree) -> GTE (depth tr) 0 937 | depth_tree_gt_0 tr = ?prf 938 | ``` 939 | 940 | For the hole, we get: 941 | 942 | ``` 943 | tr : Tree 944 | -------------------------------------- 945 | prf : LTE 0 (depth tr) 946 | ``` 947 | 948 | Doesn't seem like we have enough information. We can proceed with proof by cases: 949 | 950 | ``` 951 | depth_tree_gt_0 : (tr : Tree) -> GTE (depth tr) 0 952 | depth_tree_gt_0 Leaf = ?prf1 953 | depth_tree_gt_0 (Node v tr1 tr2) = ?prf2 954 | ``` 955 | 956 | Checking the types of the holes: 957 | 958 | ``` 959 | Holes: Main.prf2, Main.prf1 960 | Idris> :t prf1 961 | -------------------------------------- 962 | prf1 : LTE 0 0 963 | Idris> :t prf2 964 | tr1 : Tree 965 | tr2 : Tree 966 | _t : Nat 967 | -------------------------------------- 968 | prf2 : LTE 0 (S (maximum (depth tr1) (depth tr2))) 969 | ``` 970 | 971 | For the first case, it's pretty easy. We just use the constructor `LTEZero` with implicit `right = 0`: 972 | 973 | ``` 974 | depth_tree_gt_0 : (tr : Tree) -> GTE (depth tr) 0 975 | depth_tree_gt_0 Leaf = LTEZero {right = 0} 976 | depth_tree_gt_0 (Node v tr1 tr2) = ?prf2 977 | ``` 978 | 979 | For the second case, it also seems we need to use `LTEZero`, but the second argument is `(S (maximum (depth tr1) (depth tr2)))`. 980 | 981 | ``` 982 | depth_tree_gt_0 : (tr : Tree) -> GTE (depth tr) 0 983 | depth_tree_gt_0 Leaf = 984 | LTEZero {right = 0} 985 | depth_tree_gt_0 (Node v tr1 tr2) = 986 | LTEZero {right = 1 + maximum (depth tr1) (depth tr2)} 987 | ``` 988 | 989 | Thus, we have proven that the depth of any tree is greater or equal to zero. 990 | 991 | ### 5.4.2. Map and size 992 | 993 | We saw how we can use `map` with lists. It would be neat if we had a way to map trees as well. The following definition will allow us to do exactly that: 994 | 995 | ``` 996 | map_tree : (Nat -> Nat) -> Tree -> Tree 997 | map_tree _ Leaf = Leaf 998 | map_tree f (Node v tr1 tr2) = (Node (f v) 999 | (map_tree f tr1) 1000 | (map_tree f tr2)) 1001 | ``` 1002 | 1003 | The function `map_tree` accepts a function and a `Tree` and then returns a modified `Tree` where the function is applied to all values of the nodes. In the case of `Leaf`, it just returns `Leaf`, because there's nothing to map to. In the case of a `Node`, we return a new `Node` whose value is applied to the function `f` and then recursively map over the left and right branches of the node. We can use it as follows: 1004 | 1005 | ``` 1006 | Idris> Node 2 (Node 1 Leaf Leaf) (Node 3 Leaf Leaf) 1007 | Node 2 (Node 1 Leaf Leaf) (Node 3 Leaf Leaf) : Tree 1008 | Idris> map_tree (\x => x + 1) (Node 2 (Node 1 Leaf Leaf) 1009 | (Node 3 Leaf Leaf)) 1010 | Node 3 (Node 2 Leaf Leaf) (Node 4 Leaf Leaf) : Tree 1011 | ``` 1012 | 1013 | I> ### Definition 10 1014 | I> 1015 | I> The size of a tree is defined as the sum of the levels of all nodes. 1016 | 1017 | We will now implement `size_tree` which is supposed to return the total count of all nodes contained in a tree: 1018 | 1019 | ``` 1020 | size_tree : Tree -> Nat 1021 | size_tree Leaf = 0 1022 | size_tree (Node n l r) = 1 + (size_tree l) + (size_tree r) 1023 | ``` 1024 | 1025 | Trying it with a few trees: 1026 | 1027 | ``` 1028 | Idris> size_tree Leaf 1029 | 0 : Nat 1030 | Idris> size_tree (Node 1 Leaf Leaf) 1031 | 1 : Nat 1032 | Idris> size_tree (Node 1 (Node 2 Leaf Leaf) Leaf) 1033 | 2 : Nat 1034 | ``` 1035 | 1036 | ### 5.4.3. Length of mapped trees 1037 | 1038 | We want to prove that for a given tree and _any_ function `f`, the size of that tree will be the same as the size of that tree mapped with the function `f`: 1039 | 1040 | ``` 1041 | proof_1 : (tr : Tree) -> (f : Nat -> Nat) -> 1042 | size_tree tr = size_tree (map_tree f tr) 1043 | ``` 1044 | 1045 | This type definition describes exactly that. We will use proof by cases and pattern match on `tr`: 1046 | 1047 | ``` 1048 | proof_1 Leaf _ = ?base 1049 | proof_1 (Node v tr1 tr2) f = ?i_h 1050 | ``` 1051 | 1052 | Checking the types of the holes: 1053 | 1054 | ``` 1055 | Holes: Main.i_h, Main.base 1056 | Idris> :t base 1057 | f : Nat -> Nat 1058 | -------------------------------------- 1059 | base : 0 = 0 1060 | Idris> :t i_h 1061 | v : Nat 1062 | tr1 : Tree 1063 | tr2 : Tree 1064 | f : Nat -> Nat 1065 | -------------------------------------- 1066 | i_h: S (plus (size_tree tr1) (size_tree tr2)) = 1067 | S (plus (size_tree (map_tree f tr1)) (size_tree (map_tree f tr2))) 1068 | ``` 1069 | 1070 | For the base case, we can just use `Refl`. However, for the inductive hypothesis, we need to do something different. We can try applying the proof recursively to `tr1` and `tr2` respectively: 1071 | 1072 | ``` 1073 | proof_1 : (tr : Tree) -> (f : Nat -> Nat) -> 1074 | size_tree tr = size_tree (map_tree f tr) 1075 | proof_1 Leaf _ = Refl 1076 | proof_1 (Node v tr1 tr2) f = let IH_1 = proof_1 tr1 f in 1077 | let IH_2 = proof_1 tr2 f in 1078 | ?conclusion 1079 | ``` 1080 | 1081 | We get to the following proof state at this point: 1082 | 1083 | ``` 1084 | Holes: Main.conclusion 1085 | Idris> :t conclusion 1086 | v : Nat 1087 | tr1 : Tree 1088 | tr2 : Tree 1089 | f : Nat -> Nat 1090 | IH_1 : size_tree tr1 = size_tree (map_tree f tr1) 1091 | IH_2 : size_tree tr2 = size_tree (map_tree f tr2) 1092 | -------------------------------------- 1093 | conclusion : S (plus (size_tree tr1) (size_tree tr2)) = 1094 | S (plus (size_tree (map_tree f tr1)) 1095 | (size_tree (map_tree f tr2))) 1096 | ``` 1097 | 1098 | From here, we can just rewrite the hypothesis: 1099 | 1100 | ``` 1101 | proof_1 (Node v tr1 tr2) f = let IH_1 = proof_1 tr1 f in 1102 | let IH_2 = proof_1 tr2 f in 1103 | rewrite IH_1 in 1104 | rewrite IH_2 in 1105 | ?conclusion 1106 | ``` 1107 | 1108 | At this point, if we check the type of `conclusion` we will note that we can just use `Refl` to finish the proof. 1109 | -------------------------------------------------------------------------------- /manuscript/conclusion.md: -------------------------------------------------------------------------------- 1 | # Conclusion 2 | 3 | We've seen how powerful types are. They allow us to put additional constraints on values. This helps with reasoning about our programs since a whole class of non-valid programs will not be accepted by the type checker. For example, if we look at the following function, we immediately know by its type that it returns a natural number: 4 | 5 | ``` 6 | f : Nat -> Nat 7 | f _ = 6 8 | ``` 9 | 10 | Note that there are many constructors (proofs) for `Nat`. For example, we also have that `f _ = 7`. Depending on whether we need 6 or 7 in practice has to be additionally checked. But we're certain that it's a natural number (by the type). 11 | 12 | Most programming languages that have a type system have the same expressive power as that of propositional logic (no quantifiers). Dependent types are powerful because they allow us to express quantifiers, which increases the power of expressiveness. As a result, we can write any kind of mathematical proofs. 13 | 14 | In mathematics, everything has a precise definition. Same is the case with Idris. We had to give clear definitions of our functions and types prior to proving any theorems. 15 | 16 | Q> If Idris proves software correctness, what proves the correctness of Idris? 17 | Q> 18 | Q> Trusted Computing Base (TCB) can be thought of as the "axioms" of Idris, that is, we choose to trust Idris and the way it rewrites, inserts a new given, etc. 19 | 20 | The most challenging part is to come up with the precise properties (specifications) that we should prove in order to claim correctness for our software. 21 | 22 | The beauty of all of this is that almost everything is just about types and finding inhabitants (values) of types. 23 | 24 | * * * 25 | -------------------------------------------------------------------------------- /manuscript/further-reading.md: -------------------------------------------------------------------------------- 1 | # Further reading 2 | 3 | Morris, D. W., Morris, J., _Proofs and Concepts_, 2016 4 | 5 | Velleman, J. D., _How to Prove It: A Structured Approach_, 1994 6 | 7 | Megill, N., _Metamath_, 1997 8 | 9 | Halmos, P., _Naive Set Theory_, 1960 10 | 11 | Lipovaca, M., _Learn You a Haskell for Great Good_, 2011 12 | 13 | The Idris Community, _Programming in Idris: A Tutorial_, 2015 14 | 15 | Wadler, P., _Propositions as Types_, 2014 16 | 17 | Pierce, B., _Logical Foundations_, 2011 18 | 19 | Pierce, B., _Types and Programming Languages_, 2002 20 | 21 | Löh, A., McBride C., Swierstra W., _A tutorial implementation of a dependently typed lambda calculus_, 2010 22 | 23 | Martin-Löf, P., _Intuitionistic Type Theory_, 1984 24 | 25 | Hofstadter, D., _Gödel, Escher, Bach_, 1979 26 | 27 | Sitnikovski, B., _Tutorial implementation of Hoare logic in Haskell_, 2021 28 | 29 | Smullyan, R., _The Gödelian Puzzle Book_, 2013 30 | -------------------------------------------------------------------------------- /manuscript/images/title_page.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bor0/gidti/bc66446911af47fc24d3930f8514b68c08164545/manuscript/images/title_page.jpg -------------------------------------------------------------------------------- /manuscript/introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | Writing correct code in software engineering is a complex and expensive task, and too often our written code produces inaccurate or unexpected results. There are several ways to deal with this problem. In practice, the most common approach is to write _tests_, which means that we are writing more code to test our original code. However, these tests can only ever detect problems in specific cases. As Edsger Dijkstra noted, "testing shows the presence, not the absence of bugs". A less common approach is to find a _proof of correctness_ for our code. A software proof of correctness is a logical proof that the software is functioning according to given specifications. With valid proofs, we can cover all possible cases and be more confident that the code does exactly what it is intended to do. 4 | 5 | Idris is a general-purpose functional[^intron1] programming language that supports dependent types. The features of Idris are influenced by Haskell, another general-purpose functional programming language. Thus, Idris shares many features with Haskell, especially in the part of syntax and types, where Idris has a more advanced type system. There are several other programming languages that support dependent types[^intron2], however, I chose Idris for its readable syntax. 6 | 7 | The first version of Idris was released in 2009. It is developed by The Idris Community and led by Dr. Edwin Brady. Seen as a programming language, it is a functional programming language implemented with dependent types. Seen as a logical system, it implements intuitionistic type theory, which we will cover later. Finally, we will show how these two views relate to each other in section 4.2. 8 | 9 | Idris allows us to express mathematical statements. By mechanically examining these statements, it helps us find a formal proof of a program's formal specification. 10 | 11 | To fully understand how proofs in Idris work we will start with the foundations by defining: formal systems, classical mathematical logic, lambda calculus, intuitionistic logic, and type theory (which is a more "up-to-date" version of classical mathematical logic). 12 | 13 | [^intron1]: The core concept of functional programming languages is a mathematical function. 14 | 15 | [^intron2]: Several other languages with dependent types support are Coq, Agda, Lean. 16 | -------------------------------------------------------------------------------- /manuscript/preface-and-acknowledgments.md: -------------------------------------------------------------------------------- 1 | # Preface and acknowledgments 2 | 3 | This book aims to be accessible to novices that have no prior experience beyond high school mathematics. Thus, this book is designed to be self-contained. No programming experience is assumed, however having some kind of programming experience with the functional programming paradigm could make things easier to grasp in the beginning. After you finish reading the book I recommend that you check the "Further reading" chapter in case you are interested in diving deeper into some of the topics discussed. 4 | 5 | I was always curious to understand how things work. As a result, I became very interested in mathematics while I was in high school. One of the reasons for writing this book is that I could not find a book that explained how things work, so I had to do a lot of research on the internet through white-papers, forums, and example code in order to come up with a complete picture of what dependent types are and what they are good for. 6 | 7 | I will consider this book successful if it provides you with some additional knowledge. I tried to write this book so that the definitions and examples provided in it show how different pieces of the puzzle are connected to each other. 8 | 9 | Feel free to contact me at buritomath@gmail.com for any questions you might have, and I will do my best to answer. You can also access my blog at bor0.wordpress.com to check out some of my latest work. 10 | 11 | Book reviewers: 12 | 13 | 1. Nathan Bloomfield did a Ph.D. in algebra at the University of Arkansas and taught mathematics before joining Automattic, Inc. as a programmer. He enjoys witnessing the Intuitionist renaissance and likes how the boring parts of abstract algebra transform into really interesting computer science. Nathan usually feels a little out of his depth and prefers it that way. 14 | 1. Vlad Riscutia is a software engineer at Microsoft working on Office. He is interested in programming languages and type systems, and how these can best be leveraged to write correct code. 15 | 1. Marin Nikolovski is working at Massive Entertainment | A Ubisoft Studio as a Senior Web Developer on UPlay PC. He has over 10 years of experience with designing, developing and testing software across a variety of platforms. He takes pride in coding to consistently high standards and constantly tries to keep up the pace with the latest developments in the IT industry. 16 | 1. Neil Mitchell is a Haskell programmer with a Ph.D. in Computer Science from York University, where he worked on making functional programs shorter, faster and safer. Neil is the author of popular Haskell tools such as Hoogle, HLint, and Ghcid - all designed to help developers write good code quickly. More recently Neil has been researching build systems with publications at ICFP and the Haskell Symposium, and a practical system based on those ideas named Shake. 17 | 18 | Thanks to: 19 | 20 | 1. Irena Jazeva for the book cover design 21 | 1. The Haskell community (#haskell@freenode) 22 | 1. The Idris community (#idris@freenode) 23 | 1. The Coq community (#coq@freenode) 24 | 25 | Thanks to my family, coworkers, and friends for all the support they give to me. 26 | 27 | Finally, thank you for purchasing this book! I hope that you will learn new techniques in reading this book and that it will spark up some more interest in logic, dependent types, and type theory. 28 | 29 | * * * 30 | --------------------------------------------------------------------------------