During this activity, students should be able to:
This activity helps the student develop the following skills, values and attitudes: ability to analyze and synthesize, capacity for identifying and solving problems, and efficient use of computer systems.
Individually or in pairs, solve the following set of meta-programming exercises using Clojure macros.
Place all your macros in a file called problemset5.clj
.
This is the documentation for Clojure's or
macro:
or
macro
Usage:
(or)
(or x)
(or x & next)
Evaluates its expressions one at a time, from left to right. If a form returns a logical true value, it returns that value and doesn't evaluate any of the other expressions, otherwise it returns the value of the last expression. (or) returns nil.
Write a macro called my-or
that works as described above. Make sure that any expression gets evaluated at most once. You may not use Clojure's or
macro. Usage examples:
(my-or) ⇒ nil (my-or false :one nil :two false :three) ⇒ :one (my-or false false nil) ⇒ nil (my-or nil nil false) ⇒ false
Write a macro called do-loop
that implements a post-test loop control statement. It must combine the functionality of C's do-while
statement and Pascal's repeat-until
statement.
This construct consists of a body (one or more expressions, presumably with side effects) and a final conditional form prefaced with a :while
or :until
keyword. First, the expressions in the body are evaluated sequentially, and then the condition is evaluated. If the final form uses a :while
keyword, the body of the loop is repeated while the condition holds true. On the other hand, if the final form uses an :until
keyword, the body of the loop is repeated while the condition holds false (or in other words, the loop terminates when the condition is true). Returns nil
.
Here are some examples:
(def i (atom 0)) (do-loop (println @i) (swap! i inc) (:until (= @i 5))) ;;; prints: ;;; 0 ;;; 1 ;;; 2 ;;; 3 ;;; 4 ⇒ nil (def j (atom 1)) (do-loop (println @j) (swap! j inc) (:while (<= @j 5))) ;;; prints: ;;; 1 ;;; 2 ;;; 3 ;;; 4 ;;; 5 ⇒ nil
Write a macro called def-pred
, that takes a name, an arg vector, and a body of one or more expressions. The macro should define two predicate functions: a regular one and its negated version. The name of the negated predicate should be the same as name but with a "not-
" prefix, and its result should be negated using the not
function (see the test code for an example).
Examples:
(macroexpand-1 '(def-pred less-than-one? [x] (< x 1))) ⇒ (do (clojure.core/defn less-than-one? [x] (< x 1)) (clojure.core/defn not-less-than-one? [x] (clojure.core/not (do (< x 1))))) (def-pred less-than-one? [x] (< x 1)) (less-than-one? 0) ⇒ true (less-than-one? 2) ⇒ false (not-less-than-one? 0) ⇒ false (not-less-than-one? 2) ⇒ true (macroexpand-1 '(def-pred plural? [s] (println "check s in" s) (= \s (last s)))) ⇒ (do (clojure.core/defn plural? [s] (println "check s in" s) (= \s (last s))) (clojure.core/defn not-plural? [s] (clojure.core/not (do (println "check s in" s) (= \s (last s)))))) (def-pred plural? [s] (println "check s in" s) (= \s (last s))) (plural? "boys") ;;; prints: ;;; check s in boys ⇒ true (plural? "girl") ;;; prints: ;;; check s in girl ⇒ false (not-plural? "boys") ;;; prints: ;;; check s in boys ⇒ false (not-plural? "girl") ;;; prints: ;;; check s in girl ⇒ true
NOTE: You will likely need to use the Clojure functions str
and symbol
when producing the negated predicate name.
Write a macro called defn-curry
, that performs a currying transformation to a function definition. It takes as parameters a name, an args vector, and a body of one or more expressions. The macro should define a function called name that takes only the first argument from args and returns a function that takes the second argument from args and returns a function that takes the third argument from args, and so on. The last function returned takes the last argument from args and evaluates all the expressions in body using a do
special form (see the test code for examples).
Usage examples:
(macroexpand-1 '(defn-curry sum [a b c d] (prn 'args a b c d) (+ a b c d))) ⇒ (clojure.core/defn sum [a] (clojure.core/fn [b] (clojure.core/fn [c] (clojure.core/fn [d] (do (prn (quote args) a b c d) (+ a b c d)))))) (defn-curry sum [a b c d] (prn 'args a b c d) (+ a b c d)) ((((sum 1) 2) 3) 4) ;;; prints: ;;; args 1 2 3 4 ⇒ 10 ((((sum 15) 8) 16) 42) ;;; prints: ;;; args 15 8 16 42 ⇒ 81 (macroexpand-1 '(defn-curry go [x y] (* x (+ y 1)))) ⇒ (clojure.core/defn go [x] (clojure.core/fn [y] (do (* x (+ y 1))))) (defn-curry go [x y] (* x (+ y 1))) ((go 2) 3) ⇒ 8 ((go 3) 2) ⇒ 9 (macroexpand-1 '(defn-curry add1 [x] (+ x 1))) ⇒ (clojure.core/defn add1 [x] (do (+ x 1))) (defn-curry add1 [x] (+ x 1)) (add1 0) ⇒ 1 (add1 41) ⇒ 42 (macroexpand-1 '(defn-curry hello [] "hello")) ⇒ (clojure.core/defn hello [] (do "hello")) (defn-curry hello [] "hello") (hello) ⇒ "hello"
Write a macro called IF
(note the uppercase letters). Its purpose is to provide a conditional statement that is syntactically a bit more similar to those found in languages like Pascal or Fortran. It has the following form:
(IF
condition :THEN
exp1 exp2 ... :ELSE
exp3 exp4 ...)
It should basically expand to the following conventional Clojure if
form:
(if
condition (do
exp1 exp2 ...)
(do
exp3 exp4 ...))
Almost everything is optional in the IF
macro, except condition. Also, the :ELSE
keyword may come before the :THEN
keyword. The following examples show some legal forms and their results:
(IF (> 3 1) :THEN 'ok :ELSE 'oops) ⇒ ok (IF (> 3 1) :THEN 'ok) ⇒ ok (IF (< 3 1) :ELSE 'ok) ⇒ ok (IF (> 3 1) :ELSE 'oops) ⇒ nil (IF (> 3 1) :THEN) ⇒ nil (IF (> 3 1)) ⇒ nil (macroexpand-1 '(IF (> 3 1) :ELSE (println "Else section.") 'oops :THEN (println "Then section.") 'ok)) ⇒ (if (> 3 1) (do (println "Then section.") (quote ok)) (do (println "Else section.") (quote oops))) (macroexpand-1 '(IF (< 3 1) :ELSE 'ok)) ⇒ (if (< 3 1) (do) (do (quote ok)))
Any expression in-between condition and the first occurrence of the :THEN
or :ELSE
keywords should just be ignored:
(macroexpand-1 '(IF (< 3 1) 1 2 3 :THEN 4 5 6 :ELSE 7 8 9)) ⇒ (if (< 3 1) (do 4 5 6) (do 7 8 9))
The program source file must include at the top the authors’ personal information (name and student id) within comments. For example:
;---------------------------------------------------------- ; Activity: Problem Set #5 ; Date: November 11, 2019. ; Authors: ; A01166611 Pepper Pots ; A01160611 Anthony Stark ;----------------------------------------------------------
Also, each function or macro should include a documentation string (docstring) with a brief description of its behavior. For example:
(defn max2 "Returns the largest of the two numbers x and y." [x y] (if (> x y) x y))
To deliver the problemset5.clj
file, please provide the following information:
Only one team member needs to upload the file.
Due date is Monday, November 11.
This activity will be evaluated using the following criteria:
-10 | The program doesn't contain within comments the author's personal information. |
---|---|
-30 | A docstring is missing in one or more functions. |
10 | The program contains syntax errors. |
1 | The program was plagiarized in whole or in part. |
10-100 | Depending on the amount of exercises that were solved correctly. |