Programming Languages

Problem Set: Macros

Objectives

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.

Activity Description

Individually or in pairs, solve the following set of meta-programming exercises using Clojure macros.

Place all your macros in a file called macros.clj.

  1. 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
    
  2. 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
    
  3. 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.

  4. 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"
    
  5. 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))
    

Deliverables

The program source file must include at the top the authors’ personal information (name and student id) within comments. For example:

;----------------------------------------------------------
; Activity: Problem Set: Macros
; Date: November 10, 2017.
; 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))

Upload Instructions

To deliver the macros.clj file, please provide the following information:

Request PIN

Only one team member needs to upload the file.

Due date is Friday, April 20.

Evaluation

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.