-
Notifications
You must be signed in to change notification settings - Fork 3
/
One.agda
546 lines (418 loc) · 20.1 KB
/
One.agda
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
{-# OPTIONS --guardedness #-}
------------------------------------------------------------------------
-- CS410 Advanced Functional Programming 2022
--
-- Coursework 1
------------------------------------------------------------------------
module Coursework.One where
----------------------------------------------------------------------------
-- COURSEWORK 1 -- WARMING UP, LOGIC, AND GRAY SCALE IMAGE MANIPULATION
--
-- VALUE: 30% (divided over 60 marks, ie each mark is worth 0.5%)
-- DEADLINE: 5pm, Monday 10 October (Week 4)
--
-- SUBMISSION: Create your own clone of CS410 repo somewhere of your
-- choosing (e.g. Gitlab, Github, or Bitbucket), and let Fred know
-- where, so that you can invite the CS410 team to the project. The
-- last commit before the deadline is your submitted version. However
-- do get in touch if you want to negotiate about extensions.
----------------------------------------------------------------------------
-- HINT: These are all the imports from the standard library that you
-- should need, although you might of course have to define your own
-- auxiliary functions. When there is no explicit `using` list, you may
-- go hunting in the module for anything you think you might find useful.
open import Data.Bool using (Bool; true; false; not; _∧_; _∨_; if_then_else_)
open import Data.Nat using (ℕ; zero; suc; _<ᵇ_; _≤ᵇ_; _+_; ⌊_/2⌋; ⌈_/2⌉)
open import Data.Nat.Properties
open import Common.NatProperties using (n≡⌊n+n/2⌋; n≡⌈n+n/2⌉)
open import Data.List
using (List; []; _∷_; _++_; [_])
renaming (map to ListMap; splitAt to ListSplitAt)
open import Data.Vec
using (Vec; []; _∷_; lookup)
renaming (map to VecMap; toList to VecToList)
open import Data.Fin
using (Fin; zero; suc; toℕ; fromℕ; inject≤; pred)
renaming (splitAt to FinSplitAt)
open import Data.Product using (_×_; Σ; _,_; proj₁; proj₂; Σ-syntax)
open import Data.Sum using (_⊎_; inj₁; inj₂)
open import Data.Empty using (⊥; ⊥-elim)
open import Data.Unit.Polymorphic using (⊤)
import Data.Maybe -- we refrain from opening this module until we need it
open import Function using (_∘_; id; _$_; case_of_)
open import Relation.Nullary using (¬_; Dec; yes; no)
open import Relation.Binary.PropositionalEquality
using (_≡_; refl; cong; sym; trans; subst)
open import Data.String hiding (show) renaming (_++_ to _S++_)
open import Data.Char hiding (show; toℕ; fromℕ)
open import Data.Nat.Show renaming (readMaybe to readMaybeNat; show to showNat)
open import Data.Fin.Show renaming (readMaybe to readMaybeFin; show to showFin)
import Level
-- HINT: your tasks are labelled with the very searchable tag '???'
-- TIP: When you load this file, you will see lots of open goals. You
-- can focus on one at a time by using comments {- ... -} to switch
-- off the later parts of the file until you get there.
------------------------------------------------------------------------
-- SOME GOOD OLD FUNCTIONAL PROGRAMMING: TREESORT (10 MARKS in total)
------------------------------------------------------------------------
-- Here is a datatype of node-labelled binary trees:
data Tree (X : Set) : Set where
leaf : Tree X
_<[_]>_ : Tree X -> X -> Tree X -> Tree X
{- ??? 1.1 Implement the insertion of a number into a tree, ensuring that
the numbers in the tree are in increasing order from left to right;
make sure to retain duplicates.
(2 MARKS) -}
insertTree : ℕ -> Tree ℕ -> Tree ℕ
insertTree = {!!}
-- HINT: the import list for Data.Nat above might contain useful things
{- ??? 1.2 Implement the function which takes the elements of a list and
builds an ordered tree from them, using insertTree.
(1 MARK) -}
makeTree : List ℕ -> Tree ℕ
makeTree = {!!}
{- ??? 1.3 Implement the function which flattens a tree to a list,
and combine it with makeTree to implement a sorting function.
(1 MARKS) -}
flatten : {X : Set} -> Tree X -> List X
flatten = {!!}
treeSort : List ℕ -> List ℕ
treeSort = {!!}
-- TIP: You can uncomment the following test cases to check your work. They
-- should all typecheck if you got it right.
{-
_ : treeSort [ 1 ] ≡ [ 1 ]
_ = refl
_ : treeSort (1 ∷ 2 ∷ 3 ∷ []) ≡ (1 ∷ 2 ∷ 3 ∷ [])
_ = refl
_ : treeSort (3 ∷ 1 ∷ 2 ∷ []) ≡ (1 ∷ 2 ∷ 3 ∷ [])
_ = refl
_ : treeSort (3 ∷ 2 ∷ 3 ∷ []) ≡ (2 ∷ 3 ∷ 3 ∷ [])
_ = refl
-}
{- ??? 1.4 implement a fast version of flatten, taking an accumulating
parameter, never using ++. It should satisfy
fastFlatten t xs ≡ flatten t ++ xs
We can use fastFlatten to build a faster version of tree sort.
(2 MARKS) -}
fastFlatten : {X : Set} -> Tree X -> List X -> List X
fastFlatten leaf = id
fastFlatten (l <[ x ]> r) = fastFlatten l ∘ (x ∷_) ∘ fastFlatten r
fastTreeSort : List ℕ -> List ℕ
fastTreeSort xs = fastFlatten (makeTree xs) []
-- TIP: You can copy and modify the test cases above to check that
-- also fastTreeSort works as intended.
{- ??? 1.5 *Prove* that fastFlatten correctly implements it
specification. You will need to prove an additional fact about
concatenation separately, and use that in the proof.
(3 MARKS) -}
fastFlattenCorrect : {X : Set} -> (t : Tree X) -> (xs : List X) ->
fastFlatten t xs ≡ (flatten t ++ xs)
fastFlattenCorrect = {!!}
{- ??? 1.6 Use fastFlattenCorrect to prove that treeSort and
fastTreeSort agree. If you stop and think, you should see that
there is no need to pattern match here. But, again, you will need
to prove an additional fact about concatenation.
(1 MARK) -}
fastTreeSortCorrect : (xs : List ℕ) -> fastTreeSort xs ≡ treeSort xs
fastTreeSortCorrect = {!!}
------------------------------------------------------------------------
-- DOUBLEPLUSGOOD NEGATION (20 MARKS in total)
------------------------------------------------------------------------
module Logic where -- To avoid name clashes, we use a local module here
{- ??? 1.7 Implement the following operations:
(2 MARKS) -}
orAdjunctionFrom : {P Q R : Set} → (P ⊎ Q -> R) -> ((P -> R) × (Q -> R))
orAdjunctionFrom = {!!}
orAdjunctionTo : {P Q R : Set} → ((P -> R) × (Q -> R)) -> (P ⊎ Q -> R)
orAdjunctionTo = {!!}
{- ??? 1.8 Which of the following operations can be implemented?
For each operation, either give an implementation, or comment
it out, leaving a comment explaining why it cannot be
implemented.
For full marks, it is not enough to give a handwavy
explanation why something cannot be implemented -- instead,
show how implementing the operation would make a known
non-implementable operation implementable, such as the Law of
Excluded Middle, or Double Negation Elimination.
(4 MARKS) -}
contrapositive : {P Q : Set} → (P → Q) → (¬ Q → ¬ P)
contrapositive = {!!}
contrapositiveReverse : {P Q : Set} → (¬ Q → ¬ P) → (P → Q)
contrapositiveReverse = {!!}
variation1 : {P Q : Set} → (P → ¬ Q) → (Q → ¬ P)
variation1 = {!!}
variation2 : {P Q : Set} → (¬ P → Q) → (¬ Q → P)
variation2 = {!!}
{- ??? 1.9 Another principle of classical logic which is not provable in
Agda is Peirce's Law. However show that its double-negation is
provable.
(2 MARKS) -}
¬¬Peirce : {P Q : Set} → ¬ ¬ (((P -> Q) -> P) -> P)
¬¬Peirce = {!!}
{- ??? 1.10 For each of the following operations, either give an
implementation, or comment it out and leave a comment
explaining why it is impossible to implement.
(4 MARKS) -}
deMorgan-∀-from : {A : Set}{R : A -> Set} -> ¬ ((x : A) -> R x) -> Σ[ x ∈ A ] (¬ (R x))
deMorgan-∀-from = {!!}
deMorgan-∀-to : {A : Set}{R : A -> Set} -> Σ[ x ∈ A ] (¬ (R x)) -> ¬ ((x : A) -> R x)
deMorgan-∀-to = {!!}
deMorgan-∃-from : {A : Set}{R : A -> Set} -> ((x : A) -> (¬ (R x))) -> ¬ (Σ[ x ∈ A ] (R x))
deMorgan-∃-from = {!!}
deMorgan-∃-to : {A : Set}{R : A -> Set} -> ¬ (Σ[ x ∈ A ] (R x)) -> (x : A) → (¬ (R x))
deMorgan-∃-to = {!!}
{- ??? 1.11 Show that double negation is a /monad/; a concept you
might remember from Haskell (don't worry if you don't, we'll
get back to it later!). Concretely, you need to implement the
following two operations:
(1 MARK) -}
return : {P : Set} → P -> ¬ ¬ P
return = {!!}
_>>=_ : {P Q : Set} → ¬ ¬ P -> (P -> ¬ ¬ Q) -> ¬ ¬ Q
(¬¬p >>= f) = {!!}
-- TIP: if an operation with name _>>=_ is in scope, Agda allows us to
-- use do-notation (again possibly familiar from Haskell) to write
--
-- do
-- x <- mx
-- f
--
-- instead of mx >>= λ x → f. Here is an example (feel free to play
-- around and make a hole in the last line to see what is going on):
¬¬-map : {P Q : Set} → (P -> Q) -> ¬ ¬ P -> ¬ ¬ Q
¬¬-map f ¬¬p = do
p ← ¬¬p
return (f p)
{- ??? 1.12 Use do-notation and/or ¬¬-map to show that
double negation distributes over 'exists' and 'or' (what about
functions in the reverse directions?).
(2 MARKS) -}
¬¬-distributes-∃ : {A : Set}{S : A → Set} →
Σ[ x ∈ A ] (¬ ¬ (S x)) -> ¬ ¬ (Σ A S)
¬¬-distributes-∃ = {!!}
¬¬-distributes-⊎ : {P Q : Set} → ¬ ¬ P ⊎ ¬ ¬ Q -> ¬ ¬ (P ⊎ Q)
¬¬-distributes-⊎ = {!!}
{- ??? 1.13 The Fundamental Interconnectedness of All Things. Prove the
following counter-intuitive fact of classical logic: for any two
things, one must imply the other.
HINT: You might want to look up the phrase "material
implication", and maybe prove a lemma.
For full marks, also pay attention to style, e.g., layout,
no excessive brackets, choice of variable names, etc.
(5 MARKS) -}
dirkGently : (lem : (X : Set) -> X ⊎ ¬ X) -> {P Q : Set} → (P -> Q) ⊎ (Q -> P)
dirkGently lem {P} {Q} = {!!}
-----------------------------------------------------------------------
-- maxVal SHADES OF GRAY (30 MARKS in total)
------------------------------------------------------------------------
open Data.Maybe
{- Our final task is to write a commandline program for manipulating
and displaying grayscale images. This section is deliberately left
more open-ended for you to practice designing and writing slightly
larger programs. Most likely you will have to come up with your own
plan, and implement some auxiliary functions to get there. -}
{- ??? 1.23 Below, there are four marks available for good style,
sensible reusable supporting functions, and good comments.
(4 MARKS) -}
{- Let's get started!
A (plain) Portable GrayMap (PGM) format
[http://netpbm.sourceforge.net/doc/pgm.html] representation of a
grayscale image consists of the following ASCII text:
* The "magic number" P2;
* whitespace (space, tabs, line breaks, etc);
* A natural number w, formatted in decimal as ASCII;
* whitespace;
* A natural number h, formatted in decimal as ASCII;
* whitespace;
* A natural number maxVal, formatted in decimal as ASCII;
* h number of rows separated by line breaks, each row consisting of
w entries separated by whitespace, each entry being a natural
number smaller than or equal to maxVal.
Each entry in the raster represents a grayscale pixel value, ranging
from 0 = black to maxVal = white (note that 0 is black, not white!).
You can see some examples in the Coursework.Examples.One file, and in the
Examples directory.
-}
open import Coursework.Examples.One
{- Notice how data earlier in the file determines the format of the
following data; a dependent type!
-}
{- ??? 1.14 Your first task is to represent a PGM file as an Agda record,
by completing the following definition with the required fields.
Use vectors from Data.Vec to represent the rows and columns, and
finite numbers from Data.Fin to represent the bounded entries.
(1 MARK) -}
record PlainPGM : Set where
constructor P2 -- just a suggested name
field
-- ADD YOUR FIELDS HERE
{- ??? 1.15 To make sure that you understand the file format, write a function
that turns a PlainPGM back into a string.
(2 MARKS) -}
writePGM : PlainPGM -> String
writePGM = {!!}
-- HINT: You might find the modules Data.Nat.Show and Data.Fin.Show useful,
-- as well as Data.String.
{- ??? 1.16 Continuing our quest for understanding, write a function
which generates an "ASCII art" rendering of a PGM file; you
will need to decide on some cutoff points for translating
grayscale pixel intensity into symbols, e.g., you could choose
100% = '#'
75% = '%'
50% = '^'
25% = '.'
with even more distinctions if you want.
Hint: Looking back at the spec, what does 0 represent?
(3 MARKS) -}
viewPGM : PlainPGM -> String
viewPGM = {!!}
-- HINT: You can now look at a PGM image i by normalising viewPGM in
-- emacs; if you do C-u C-u C-c C-n, emacs will apply the following
-- trivial show function to the resulting string, which will print the
-- newlines correctly:
show : String -> String
show = id
{- ??? 1.17 We also want to be able to read images given to us. Since
we have no guarantees that the string we are given actually
represents a valid image, we will have to produce Maybe an
image. However for debugging ease of use, also implement an "unsafe"
version which should return an empty image if readPGM fails.
(4 MARKS) -}
readPGM : String -> Maybe PlainPGM
readPGM = {!!}
unsafeReadPGM : String -> PlainPGM
unsafeReadPGM = {!!}
-- HINT: Again Data.Nat.Show and Data.String are your friends.
-- HINT: It is polite to be forgiving in what you accept, and not
-- insist on newlines over other whitespace to separate lines, for
-- example. And if you look in Data.String, it is sometimes easy to be
-- polite.
-- HINT: Since Maybe also is a monad, Agda allows you to use do
-- notation here, which could be helpful.
{- !!! Congratulations, you have now implemented enough to have a
working running program! If you look at the bottom of this file,
you will see a main function I have prepared for you. You should be
able to compile this file by selecting "Compile" in the Agda menu
and then the "GHC" backend, and then run the produced binary on the
commandline, with the "-ascii" action working. Try it out on one of
the sample files in the One directory, or your favourite PGM file
from the Internet! You can use `convert -compress none` to convert
almost any image into plain PGM format if you have imagemagick
installed. -}
{- ??? 1.18 Implement "posterization", which literally treats the
world as black-and-white: if the pixel is <= 50%, make it 0%,
otherwise make it 100%. Note that we do not need to produce
Maybe an image here, because we know that the input we get is
correct.
After implementing this, the "-posterize" action should work if
you recompile the program.
(2 MARKS) -}
posterize : PlainPGM → PlainPGM
posterize = id -- CHANGE THIS!
-- TO PONDER: What happens if you posterize an already posterized image?
-- Can you prove it?
{- ??? 1.19 Implement rotation, which turns an image sideways (you can
decide if clockwise or anticlockwise).
(4 MARKS) -}
rotate : PlainPGM → PlainPGM
rotate = id -- CHANGE THIS!
-- HINT: It might be useful to be able to repeat an element to fill up
-- a vector of a given length, and to be able to "apply" a vector full
-- of functions to a vector full of arguments.
{- !!! You should now be able to use the "-rotate" action in your program -}
{- ??? 1.20 Implement shrinking, which halves the size of an image by
replacing each pixel with the average of its neighbours (you
don't have to consider the neighbours in all nine directions,
unless you want to; it is enough to consider for example the
previous neighbor in both directions).
(4 MARKS) -}
shrink : PlainPGM → PlainPGM
shrink = id -- CHANGE THIS!
-- HINT: it might be useful to give yourself "random access" to
-- the raster. The function `lookup : ∀ {n} → Vec A n → (Fin n → A)`
-- from Data.Vec makes it possible to see a vector as a function from
-- `Fin n` -- can you go the other way, i.e., implement
-- `tabulate : {n : ℕ} → (Fin n -> A) -> Vec A n`?
-- Another hint: Data.Nat.Properties contain many useful facts.
{- !!! You should now be able to use the "-shrink" action in your program.
Well done, you have now implemented the basic functionality of the program.
We now move on to some nice extras. -}
{- ??? 1.21 Allowing comments. The PGM file format actually allows
comments, in the sense that any content following a hash symbol
'#' is ignored until the end of the line. Implement this as a
preprocessing step, so that you can use images downloaded from
the Internet.
(1 MARK) -}
stripComments : String → String
stripComments = id -- CHANGE THIS!
{- ??? 1.22 Time for you to shine! Implement a further interesting
operation on PGM images by changing "-youraction" in the main
program below. You could e.g. implement inverting, tiling,
or merging different images, or...
(5 MARKS) -}
{- Your program here! -}
{- Here follows some stuff for doing IO via Haskell functions; you can
safely ignore until the main function below -}
open import IO using (IO)
import IO.Primitive
open import IO.Finite
open import Foreign.Haskell.Pair
{-# FOREIGN GHC import qualified System.Environment #-}
{-# FOREIGN GHC import qualified System.FilePath #-}
{-# FOREIGN GHC import qualified Data.Text #-}
{-# FOREIGN GHC import qualified Control.Arrow #-}
postulate
primGetArgs : IO.Primitive.IO (List String)
splitExtension : String → Pair String String
{-# COMPILE GHC primGetArgs = fmap Data.Text.pack <$> System.Environment.getArgs #-}
{-# COMPILE GHC splitExtension = (Data.Text.pack Control.Arrow.*** Data.Text.pack) . System.FilePath.splitExtension . Data.Text.unpack #-}
getArgs : IO (List String)
getArgs = IO.lift primGetArgs
addExtension : Pair String String → String
addExtension (f , ext) = f S++ ext
writeFile' : String → String → IO {Level.zero} ⊤
writeFile' file content = do
IO.putStrLn ("Writing output to " S++ file) IO.>>= \ _ → writeFile file content
{- Something interesting happening here again! -}
main : IO.Main
main = IO.run $
getArgs IO.>>= λ where
(action ∷ file ∷ []) →
readFile file IO.>>= λ contents →
case readPGM (stripComments contents)
of λ { nothing → IO.putStrLn "Error: malformed input file"
; (just pgm) → act action (splitExtension file) pgm }
_ → IO.putStrLn $ "Usage: One ACTION INPUTFILE" S++ available
where
available : String
available = "\n\nAvailable actions: -ascii -posterize -reflect -rotate"
act : (action : String) -> (file : Pair String String) -> PlainPGM -> IO ⊤
act "-ascii" out pgm = IO.putStrLn $ viewPGM pgm
act "-posterize" (out , ext) pgm =
writeFile' (addExtension (out S++ "-posterized", ext))
(writePGM (posterize pgm))
act "-shrink" (out , ext) pgm =
writeFile' (addExtension (out S++ "-shrunk", ext))
(writePGM (shrink pgm))
act "-rotate" (out , ext) pgm =
writeFile' (addExtension (out S++ "-rotated", ext))
(writePGM (rotate pgm))
act "-youridea" (out , ext) pgm = IO.putStrLn $ "YOUR IDEA HERE?"
act action out pgm =
IO.putStrLn $ "Error: unknown action " S++ action S++ available
-- NOTE: You might find that your program is quite inefficient on
-- larger images -- this is okay, for now, although it could be
-- interesting to think about why that is. In general, there is much
-- work to be done to enable efficient compilation of dependently
-- typed programs.
{-
To compile:
Either do C-c C-x C-c in Emacs (or select "Compile" from the Agda
menu), and choose the GHC backend, or run
agda -c One.agda
on the command line. If you have unfinished holes from further up
the file, it is easiest to comment them out before compiling.
To run the resulting program,
./One ACTION INPUTFILE
-}