RU beehive logo ITEC dept promo banner
ITEC 380
2017fall
ibarland

homelecturesrecipeexamshwsD2Lbreeze (snow day; distance)

S5-and-prolog-lists
S5: environments
and, prolog lists

Due Dec.08 (Fri) 15:0009 (Sat) 17:00.
Submit: a hardcopy with your prolog code; bring hardcopy to the exam on Wednesday, or slip it under my office door earlier.
If doing S6 for extra credit, submit those files as well, with a “>>>S6” comment by your modified lines.

We continue to build on the S language implementation from previous homeworks (S2 sol'n posted; S4 sol'n discussed on Monday and full source by Mon 17:00. Note that S5 can be based off of the S2 solution for full credit). You may implement this homework in either Java or Racket (or another language, if you clear it with me). Label each section of lines you change with a comment “;>>>S5”. You don't need to turn in any hardcopy of unchanged-code (but do submit a fully-working copy in the drop-box, including all necessary files).

  1. (3pts)

    Write edb (“extended drinking buddies”): We say two people are extended drinking buddies if they are connected through a chain of drinksWith links (as defined in prolog_basics.pl). For example, alice and kris would be extended drinking buddies if drinksWith(alice,charlie), drinksWith(charlie,ethan), and drinksWith(ethan,kris). (That is: extended-drinking-buddies is the transitive closure of the drinksWith relation.)

    Note:Because of the nature of Prolog's search, and the fact that there are “cycles” of extended-drinking-buddies (unlike ancestors), it is possible that your query for people who aren't extended-drinking-buddies gets into an infinite loop. This is okay, for this homework. The general solution would be to either (a) add an “exclusion” list of people already tried, or (b) use datalog, a restricted prolog which is always guaranateed to terminate. (See #lang racklog.)
  2. (10pts) Prolog list

    Write the following Prolog predicates. Do not use append. For full credit, give idiomatic Prolog (no singleton variables, and no = on the right-hand-side).

    1. (3pts) last(List, Item), which succeeds exactly when Item is the last item in List. This rule should fail if List is empty, of course. (This happens to be the book's Chpt.16, programming exercise #6.)
    2. (2pts) nextToLast(List, Item) which succeeds exactly when Item is the next-to-last item in List. (This rule should fail if List has fewer than two items, of course.)
    3. (2pts) lastTwoReversed(List, ListOf2) which succeeds exactly when ListOf2 contains the last and the second-to-last item of List (in that order, and nothing else).
    4. (3pts) reverseLastTwo(List, NewList) succeeds exactly when NewList is likeexactly the same as List except that the last two items have been reversed. (This rule will fail if List has fewer than two items.)
    All of the predicates fail if the first argument is not a list. Some examples (test cases) are provided, below.

    Note that the Prolog standard library contains several list functions which you are NOT to use for this assignment (e.g. append and reverse). Also, for full credit, don't merely reverse the input list and operate on that result.

    As ever, your program should follow good style, including appropriate white space, meaningful variable names, and as well as a header comment with your name, the name of the assignment, etc.. (You can have comments describing how a predicate works if you want; however, you can also assume your program is being read by somebody who understands the fundamentals of Prolog.)

  3. S5 (10pts; This problem and the next are really the crux of the project.)
    Deferred evaluation: S5 doesn't add any new syntax, but it is a different evaluation model which will give us more expressive semantics.

    There are two problems1 with the substitution approach used in S2–S4: we can't make recursive functions, and it doesn't generalize to assigning to variables. We solve these problems with deferred substitution:

    Your test cases should include a recursive function, as well as the example below. Also, since eval now takes an extra argument, that suggests three to four check-expects with various environments (lists of bindings):

    Re-naming 'eval': Uh-oh, it'd be a pain to have to go and adapt all our existing test-cases for eval, to take an extra argument. I would advise something like3: search-replace all occurrences of eval in your tests to be instead calling “eval-helper”. Make that a function which simply calls eval with an empty-environment. Then, go change eval to have a second input, and add some two-argument test-cases as just mentioned.

    A step sideways: This algorithm as described lets us add recursive functions, but it also doesn't handle some expressions that S4 did! For example, let make-adder func m -> func n -> ?ad n m in call call make-adder(3)(4) gives an error "unbound identifier: m" if no substitution has been done. The problem will be fixed in S6: calling call make-adder(3) returns a function whose body still includes m and n, but lacks the fact that we'd like it's m to be bound to 3. One approach might be to have eval return both a value and a set of bindings to use that value with. S6 will take a slightly different approach, storing the necessary bindings inside the function-representation.)

    Note that this gives us dynamic scoping (which we'll mention in class):

    let m 100 
    in let addM func x -> ?ad x m
       in ?ad let m 5 in call addM(3)
              let m 4 in call addM(3) 
    
    evaluates to 15, not 206 as we might prefer.

  4. (extra-credit — 15pts total) S6: Implement static scope (closures). Copy your S0-S4 file/project to a new S55. You shouldn't need any additional test cases for S6; the tests for S0-S5 should suffice, although one or two S5 examples depending on dynamic binding should now have a new expected-result.
  5. Further extra-credit options (of varying difficulty):
  6. Extra credit (5pts)7 (Scope)
    In Advanced Student, the form set! changes the binding of a variable:

    (define x 5)
    (set! x (* 2 x))
    ; the variable x is now bound to 10.

     1. (define a 10)
     2. (define b 20)
     3. (define make-foo
     4.   (let {[b 30]}
     5.      (lambda ()            ; ← make-foo is bound to this function.
     6.         (let {[c 40]}
     7.            (lambda (cmd)   ; ← make-foo returns this function as its answer.
     8.              (let {[d 50]}
     9.                (cond [(symbol=? cmd 'incA) (set! a (+ a 1))]
    10.                      [(symbol=? cmd 'incB) (set! b (+ b 1))]
    11.                      [(symbol=? cmd 'incC) (set! c (+ c 1))]
    12.                      [(symbol=? cmd 'incD) (set! d (+ d 1))]
    13.                      [(symbol=? cmd 'get-all) (list a b c d)])))))))
          

    1. The scope of the a declared in line 1 is lines      through     .

    2. The scope of the b declared in line 2 is lines      through     .

    3. The scope of the b declared in line 4 is lines      through     .

    4. The scope of the c declared in line 6 is lines      through     .

    5. The scope of the d declared in line 8 is lines      through     .
    Suppose that make-foo is called exactly three times (but that a function returned by make-foo is not called).

    1. How many variables named a are created?     

    2. How many variables named b are created?     

    3. How many variables named c are created?     

    4. How many variables named d are created?     
    (Hint: Each of the above four answers are different.)
    (0pts) To help you figure out the above, try filling out the following (before you try running it):
    (set! a 500)
    (set! b 600)
    (define counter1 (make-foo))
    (define counter2 (make-foo))
    (define counter3 (make-foo))
    
    (counter1 'get-all)  ; >>> TODO:   (list                    )
    (counter1 'incA)
    (counter1 'incB)
    (counter1 'incC)
    (counter1 'incD)
    (counter1 'get-all)  ; >>> TODO:   (list                    )
    
    (counter2 'get-all)  ; >>> TODO:   (list                    )
    


Here are some examples of the list predicates, for the prolog list questions:

last([1,2,3], 3).
Yes

last([1,2,3], 4).
No

last([1,2,3], Y).
Y=3

last([], Y).
No

last(Y, 3).
Y=[3].

nextToLast([1,2,3], 2).
Yes

nextToLast([1,2,3], 3).
No

nextToLast([1,2,3], Y).
Y=2

nextToLast([1], Y).
No

nextToLast(Y, 3).
Y=[3, _h114],         % does not have to be 114, 'course.
Y=[_h116, 3, _h114].

lastTwoReversed([1,2,3], Y).
Y=[3,2]

lastTwoReversed([1], Y).
No

reverseLastTwo([1,2,3,4], Y).
Y=[1,2,4,3]

reverseLastTwo([1,2], Y).
Y=[2,1]

reverseLastTwo([1], Y).
No


Mutation in Racket

If you want to use mutation in your racket-implementation, use Advanced Student language. This language level includes set! (to assign to variables), and set-struct-field! (to assign to struct fields). Note that these two actions are very different, which is why racket gives them separate names; in Java assigning-to-field and assigning-to-local-variable can look identical (syntactically), despite giving rise to very different behavior.

Since the mutators return #void, and we want to return a (useful) value from every expression, we will use mutation inside a begin expression:

(define times-called 0)
(define (triplify-and-print n)
   (begin (printf "We use `begin` to call a function for a side-effect *and* return a value too.\n")
          (set! times-called (add1 times-called))
          (printf "This function has been called ~a time~a.\n"
                  times-called 
                  (if (= times-called 1) "" "s"))
          (* 3 n)))

(triplify-and-print 5)
(triplify-and-print 2)
     

Btw, it's worth noting that in full-racket, begin is implicit in almost all forms (e.g. function-bodies and cond-branches).


1 A third issue, pointed out in Programming Languages and Interpretation, is that evaluating deeply nested lets is an O(n) algorithm.      
2 Note that the list/map you recur with has the additional binding, but that recursive call shouldn't add/modify the list/map used at the top level. Since java.util.Map is inherently mutable, you'll want to make a new copy of that map recurring.      
3In full-racket, we'd certainly want to overload eval, making the environment be an optional argument with a default value. But we'll stick with intermediate-student, to better catch errors (including any errors about accidentally not including a 2nd-argument to eval in places that you really mean to, namely: each recursive call).      
4“What, racket uses references-to-structs instead of actual structs? Why didn't you tell us that earlier, Barland?” Because in the absence of mutation, no program's results can distinguish between having an object vs a reference-to-object-which-is-always-dereferenced. So it was like a religious-opinion difference, until getting mutation in Advanced Student.      
5 Presumably you do this for each new homework, but it's a particularly good idea for this one, since S5 is not just S4 plus new code, but instead replaces some of the S4 code (subst) with a different approach.      
6 You can think about which implementation you prefer, when using a language: when seeing the program's internal representation of your code (when debugging, or seeing the compiled result of your code).      
7We will talk about this in lecture (let-over-lambda, to implement O.O.), but only briefly.      

homelecturesrecipeexamshwsD2Lbreeze (snow day; distance)


©2017, Ian Barland, Radford University
Last modified 2017.Dec.09 (Sat)
Please mail any suggestions
(incl. typos, broken links)
to ibarlandradford.edu
Rendered by Racket.