PS3: First-Class Fun
-
Changes/clarifications shown in magenta.
-
Dueish: Deadline extended to Mon Nov 16.
- Notes:
- This pset has a total of 105 points.
- You can do Problems 1 and 2 based on the material in Lec 12 First-class Functions and Problems 3 through 5 based on the material in Lec 13 Higher-Order List Functions
- The problems needn’t be done in order. Feel free to jump around.
-
Recorded Times from some previous semesters (in hours)
Note: Problem 1 is a new problem this year, so there are no times from previous semesters for this problem.
Times Problem 2 Problem 3 Problem 4 Problem 5 Total (w/o Problem 1) average time (hours) 1.6 3.1 1.4 0.9 7.0 median time (hours) 1.5 2.8 1.2 0.7 6.2 25% took more than 2.0 4.0 1.8 1 8.8 10% took more than 2.6 4.4 2.1 1.5 10.6 -
How to Start PS3
Follow the instructions in the GDoc CS251-F20-T2 Pset Submission Instructions for creating your PS3 Google Doc. Put all written answers, all derivations, and a copy of all code into this PS3 GDoc. If you are working with a partner, only one of you needs to create this document, but you should link it from both of your List Docs.
- Submission:
- For all parts of all problems, include all answers (derivations, code, etc.) in your PS3 google doc. Please format your evaluation derivations so that they’re easy to read. Format small-step derivations and Racket code using a fixed-width font, like Consolas or Courier New. You can use a small font size if that helps.
- For Problems 1 and 2, include all small-step derivations and the answers to any English questions in your PS3 GDoc.
- For Problems 3 through 5:
- Be sure that all function definitions in
yourAccountName-ps3-functions.rkt
also appear in your Google Doc (so that the graders can comment on them) - Drop a copy of your
yourAccountName-ps3-functions.rkt
in your~/cs251/drop/ps03
drop folder on cs.wellesley.edu. If you are working with a partner, only one of you should do this. At the top of your PS3 doc and in the PS3 entry in your List Doc, indicate the account name of the drop folder in which the.rkt
file has been dropped.
- Be sure that all function definitions in
1. Understanding Higher-order Functions (25 points)
In this problem, you will use small-step semantics to carefully show the evaluation of some expressions similar to those in Lec 12 First-class Functions. You will work with the following definitions from that lecture:
(define sub (λ (c d) (- c d)))
(define app-3-5 (λ (f) (f 3 5))
(define make-linear-function (λ (a b) (λ (x) (+ (* a x) b))))
(define flip2 (λ (binop) (λ (w z) (binop z w))))
(define test-10 (λ (g) (λ (p q) ((g p q) 10))))
(define curry2 (λ (binop) (λ (v1) (λ (v2) (binop v1 v2))))) ; From Lec 13
You must carefully follow the rules of small-step semantics in your derivations. Here are some examples:
({app-3-5} sub)
⇒ ( (λ (f) (f 3 5)) {sub}) [varref]
⇒ {( (λ (f) (f 3 5)) (λ (c d) (- c d)) )} [varref]
⇒ {((λ (c d) (- c d)) 3 5)} [funcall]
⇒ {(- 3 5)} [funcall]
⇒ -2 [subtraction]
Note in the above example that we do not substitute the variable name sub
for f
in the body expression (f 3 5)
, because variable names are not values. Rather, we must first use the [varref] rule to evaluate sub
to a value that is a λ expression. Since λ expressions are values, we can substitute the value (λ (c d) (- c d))
for f
in the body expression (f 3 5)
to yield ((λ (c d) (- c d)) 3 5)
, which is a new function application expression.
(define 4x+7 ({make-linear-function} 4 7))
⇒ (define 4x+7 ( (λ (a b) (λ (x) (+ (* a x) b)))) 4 7) ) [varref]
⇒ (define 4x+7 (λ (x) (+ (* 4 x) 7))) [funcall]
Note that since this is a definition, it does not return a value. Rather, it extends the global environment with a binding for the name 4x+7
to the function value (λ (x) (+ (* 4 x) 7))
. This means we can now use 4x+7
as a name in subsequent expressions, such as this one:
({4x+7} 2)
⇒ {((λ (x) (+ (* 4 x) 7)) 2)} [varref]
⇒ (+ {(* 4 2)} 7) [funcall]
⇒ {(+ 8 7)} [multiplication]
⇒ 15 [addition]
For each of the following expressions and definitions, use small-step semantics to show the step-by-step evaluation of that expression/definition. Show every step; do not abbreviate steps via ⇒*. In each step, explicitly (1) show the redex (in curly braces) and (2) give the name of the rule applied (in square brackets).
In order to show the correct evaluation it is essential that you carefully position and match all parentheses appropriately. In many cases, you might want to write an expression across multiple lines (using Racket’s pretty-printing conconventions) to make the structure of the expression easier to understand.
-
(3 points)
((app-3-5 make-linear-function) 4)
-
(4 points)
((flip2 sub) 4 7)
-
(6 points)
((app-3-5 (flip2 make-linear function)) 6)
-
(6 points)
(app-3-5 (test-10 make-linear-function))
-
(3 points)
(define 8x+? ((curry2 make-linear-function) 8))
-
(3 points)
((8x+? 2) 5)
2. Wacky Lists (15 points)
This problem shows that functions can be used to implement data structures like pairs and lists. This is something that mathematician Alonzo Church showed circa 1940, long before there were electronic computers! For this reason, these data structures are often called Church pairs and Church lists.
Consider the following alternatives to Racket’s usual built-in cons
, car
, cdr
, null
, and null?
:
(define kons (λ (x y) (λ (s) (s #f x y))))
(define kar (λ (k) (k (λ (b l r) l))))
(define kdr (λ (k) (k (λ (b l r) r))))
(define knil (λ (s) (s #t 0 0)))
(define knil? (λ (k) (k (λ (b l r) b))))
-
(2 points) Use the small-step substitution model (i.e., using ⇒) to show the evaluation of the value bound to the name
p
by the following declaration:(define p (kons 3 4))
Show every step; do not abbreviate steps via ⇒*. In each step, explicitly (1) show the redex (in curly braces) and (2) give the name of the rule applied (in square brackets).
- (9 points) Use the small-step substitution model (i.e., using ⇒) to show the evalulation of each of the following expressions. In these steps, you should use an environment env containing that contains the binding p↦λ_p, where λ_p is your value from part a. You should assume env also contains bindings for
kons
,kar
,kdr
,knil
, andknil?
.(kar p)
(kdr p)
(knil? p)
(knil? knil)
Show every step; do not abbreviate steps via ⇒*. In each step, explicitly (1) show the redex (in curly braces) and (2) give the name of the rule applied (in square brackets). Carefully review each step to make sure that it makes sense. If you’re not sure, consult Lyn or the tutors for help.
-
(2 points) The following
sum-to
function uses helper functionssum
anddown-from
defined in terms of the list-like entities involvingkons
and friends. Does it actually calculate the sum of the integers from 1 ton
(inclusive)? Explain why or why not.(define (sum-to n) (sum (down-from n))) (define (sum nums) (if (knil? nums) 0 (+ (kar nums) (sum (kdr nums))))) (define (down-from n) (if (<= n 0) knil (kons n (down-from (- n 1)))))
-
(2 points) Can we replace all instances of
cons
/car
/cdr
/null
/null?
in Racket bykons
/kar
/kdr
/knil
/knil?
? Are there any ways in whichkons
/kar
/kdr
/knil
/knil?
do not behave likecons
/car
/cdr
/null
/null?
. Some things to think about in this context:- What is the value of
(car null)?
(kar knil)`? - Can
cons
and friends interoperate withkons
and friends?
- What is the value of
3. Higher-order List Functions (35 points)
In this and the following problems you will revisit some functions from PS3 Problem 3, as well as see some new ones. However, rather than expressing them as recursions, you will express them in terms of higher-order-list-operators.
Notes:
-
For Problems 3 through 5, you should use Dr. Racket to create a single file named
yourAccountName-ps3-functions.rkt
that contains all the functions (including helper functions) that you define for these problems. -
In your definitions, you are not allowed to use recursion anywhere. (The one exception is the
inserts-rec
helper function you are given in Problem 3l.) - In your definitions, unless otherwise instructed, you should not introduce any new named helper functions, but you can (1) liberally use anonymous functions and (2) use functions you defined in previous parts in later parts.
- In Problem 3, all of your function bodies should have exactly one of the following forms:
(map mapping-function-goes-here list-argument-goes-here) (map predicate-function-goes-here list-argument-goes-here) (foldr binary-combiner-goes-here nullvalue-goes-here list-argument-goes-here)
-
(2 points) Using Racket’s
map
, define a functionmap-remainder
that takes two arguments (an integerdivisor
and a listints
of integers) and returns an integer list the same length asints
in which every element is remainder of dividing the corresponding element ofints
bydivisor
.> (map-remainder 2 '(16 23 42 57 64 100)) '(0 1 0 1 0 0) > (map-remainder 3 '(16 23 42 57 64 100)) '(1 2 0 0 1 1) > (map-remainder 5 '(16 23 42 57 64 100)) '(1 3 2 2 4 0) > (map-remainder 17 '(16 23 42 57 64 100)) '(16 6 8 6 13 15)
-
(2 points) Using Racket’s
filter
, define a functionfilter-divisible-by
that takes two arguments (an integerdivisor
and a listints
of integers) and returns a new integer list containing all the elements ofints
that are divisible bydivisor
.> (filter-divisible-by 2 '(16 23 42 57 64 100)) '(16 42 64 100) > (filter-divisible-by 3 '(16 23 42 57 64 100)) '(42 57) > (filter-divisible-by 4 '(16 23 42 57 64 100)) '(16 64 100) > (filter-divisible-by 5 '(16 23 42 57 64 100)) '(100) > (filter-divisible-by 17 '(16 23 42 57 64 100)) '()
Use the following helper function, which is helpful in this problem and some of the following ones.
(define divisible-by? (lambda (num divisor) (= (remainder num divisor) 0)))
-
(3 points) Using Racket’s
foldr
, define a functioncontains-multiple?
that takes an integerm
and a list of integersns
that returns#t
ifm
evenly divides at least one element of the integer listns
; otherwise it returns#f
. Usedivisible-by?
from above to determine divisibility.> (contains-multiple? 5 '(8 10 14)) #t > (contains-multiple? 3 '(8 10 14)) #f > (contains-multiple? 5 '()) #f
-
(3 points) Using Racket’s
foldr
, define a functionall-contain-multiple?
that takes an integern
and a list of lists of integersnss
(pronounced “enziz”) and returns#t
if each list of integers innss
contains at least one integer that is a multiple ofn
; otherwise it returns#f
. Usecontains-multiple?
in your definition ofall-contain-multiple?
.> (all-contain-multiple? 5 '((17 10 2) (25) (3 8 5))) #t > (all-contain-multiple? 2 '((17 10 2) (25) (3 8 5))) #f > (all-contain-multiple? 3 '()) #t ; said to be "vacuously true"; there is no counterexample!
-
(2 points) Using Racket’s
foldr
, define a functionsnoc
that takes a valuex
and a listys
and returns the new list that results from addingx
to the end ofys
.> (snoc 4 '(7 2 5)) '(7 2 5 4) > (snoc 4 '()) '(4)
-
(2 points) Using Racket’s
foldr
, define a functionmy-append
that takes two lists,xs
andys
, and returns the new list that contains all the elements ofxs
followed by all the elements ofys
.> (my-append '(7 2 5) '(4 6)) '(7 2 5 4 6) > (my-append '() '(4 6)) '(4 6) > (my-append '(7 2 5) '()) '(7 2 5) > (my-append '() '()) '()
Note: You may not use Racket’s
append
in your definition. -
(2 points) Using Racket’s
foldr
, define a functionappend-all
that takes a list of listsxss
and returns a new list that contains all the elements of the sublists ofxss
in their relative order.> (append-all '((1 2) (3) (4 5 6))) '(1 2 3 4 5 6) > (append-all '((1 2) (3))) '(1 2 3) > (append-all '((1 2))) '(1 2) > (append-all '()) '() > (append-all '(((1 2) (3 4 5)) ((6)) ((7 8) () (9)))) '((1 2) (3 4 5) (6) (7 8) () (9))
Note: You may use
append
ormy-append
in your definition. -
(2 points) Using Racket’s
map
, define a functionmap-cons
that takes any valuex
and an n-element listys
and returns an n-element list of all pairs'(x . y)
wherey
ranges over the elements ofys
. The pair'(x . y)
should have the same relative position in the resulting list asy
has inys
.> (map-cons 17 '(8 5 42 23)) '((17 . 8) (17 . 5) (17 . 42) (17 . 23)) > (map-cons 3 '((1 6 2) (4 5) () (9 6 8 7))) '((3 1 6 2) (3 4 5) (3) (3 9 6 8 7)) > (map-cons 42 '()) '()
-
(3 points) Using Racket’s
foldr
, define a functionmy-cartesian-product
that takes two listsxs
andys
and returns a list of all pairs'(x . y)
wherex
ranges over the elements ofxs
andy
ranges over the elements ofys
. The pairs should be sorted first by thex
entry (relative to the order inxs
) and then by they
entry (relative to the order inys
).> (my-cartesian-product '(1 2) '("a" "b" "c")) '((1 . "a") (1 . "b") (1 . "c") (2 . "a") (2 . "b") (2 . "c")) > (my-cartesian-product '(2 1) '("a" "b" "c")) '((2 . "a") (2 . "b") (2 . "c") (1 . "a") (1 . "b") (1 . "c")) > (my-cartesian-product '("c" "b" "a") '(2 1)) '(("c" . 2) ("c" . 1) ("b" . 2) ("b" . 1) ("a" . 2) ("a" . 1)) > (my-cartesian-product '("a" "b") '(2 1)) '(("a" . 2) ("a" . 1) ("b" . 2) ("b" . 1)) > (my-cartesian-product '(1) '("a")) '((1 . "a")) > (my-cartesian-product '() '("a" "b" "c")) '()
Note: You may use
map-cons
andappend
ormy-append
in your definition. -
(2 points) Using Racket’s
foldr
, define a functionmy-reverse
that takes a listxs
and returns a new list whose elements are the elements ofxs
in reverse order. You may not use the built-inreverse
function.> (my-reverse '(1 2 3 4)) '(4 3 2 1) > (my-reverse '(1)) '(1) > (my-reverse '()) '()
Note:
- We ask you to name your function
my-reverse
because Racket already provides the same function namedreverse
(which you cannot use, of course). - You may use
snoc
orappend
ormy-append
in your definition.
- We ask you to name your function
-
(3 points) Assume that the elements of a list are indexed starting with 0. Using Racket’s
foldr
, define a functionalts
that takes a listxs
and returns a two-element list of of lists, the first of which has all the even-indexed elements (in the same relative order as inxs
) and the second of which has all the odd-indexed elements (in the same relative order as inxs
).> (alts '(7 5 4 6 9 2 8 3)) '((7 4 9 8) (5 6 2 3)) > (alts '(5 4 6 9 2 8 3)) '((5 6 2 3) (4 9 8)) > (alts '(4 6 9 2 8 3)) '((4 9 8) (6 2 3)) > (alts '(3)) '((3) ()) > (alts '()) '(() ())
Notes:
-
There is no need to treat even-length and odd-length cases differently, nor is there any need to treat the singleton list specially.
-
Your definition should have exactly this pattern:
(define (alts xs) (foldr ; binary combiner goes here ; null value goes here xs))
-
-
(5 points) Using Racket’s
foldr
, define a functioninserts-foldr
that takes a valuex
and an n-element listys
and returns an n+1-element list of lists showing all ways to insert a single copy ofx
intoys
.> (inserts-foldr 3 '(5 7 1)) '((3 5 7 1) (5 3 7 1) (5 7 3 1) (5 7 1 3)) > (inserts-foldr 3 '(7 1)) '((3 7 1) (7 3 1) ( 7 1 3)) > (inserts-foldr 3 '(1)) '((3 1) (1 3)) > (inserts-foldr 3 '()) '((3)) > (inserts-foldr 3 '(5 3 1)) '((3 5 3 1) (5 3 3 1) (5 3 3 1) (5 3 1 3))
Notes:
- The
map-cons
function from above is useful here. - Think very carefully about the base case and the combination function for the recursive case.
-
Your definition should have exactly this pattern:
(define (inserts-foldr x ys) (foldr ; binary combiner goes here ; null value goes here ys))
- Unlike in the list-recursion version of
inserts
from PS2, in which the list argumentsys
is getting smaller with each recursive call, ininserts-foldr
theys
argument does not change and is always the original list. In the combiner, it’s easy to get the first element of the current list, but challenging to get the rest of the current list. Hint: The rest of the current list can be extracted from the subresult argument to the combiner. Where is it in the subresult? Note: In Problem 5a, is is much easier to expressinserts
usingfoldr-ternop
, because the ternop combiner takes the rest of the list as an explicit argument.
- The
-
(4 points) Using Racket’s
foldr
, define a functionmy-permutations
that takes as its single argument a listxs
of distinct elements (i.e., no duplicates) and returns a list of all the permutations of the elements ofxs
. The order of the permutations does not matter.> (my-permutations '()) '(()) > (my-permutations '(4)) '((4)) > (my-permutations '(3 4)) '((3 4) (4 3)) ; order doesn't matter > (my-permutations '(2 3 4)) '((2 3 4) (3 2 4) (3 4 2) (2 4 3) (4 2 3) (4 3 2)) > (my-permutations '(1 2 3 4)) '((1 2 3 4) (2 1 3 4) (2 3 1 4) (2 3 4 1) (1 3 2 4) (3 1 2 4) (3 2 1 4) (3 2 4 1) (1 3 4 2) (3 1 4 2) (3 4 1 2) (3 4 2 1) (1 2 4 3) (2 1 4 3) (2 4 1 3) (2 4 3 1) (1 4 2 3) (4 1 2 3) (4 2 1 3) (4 2 3 1) (1 4 3 2) (4 1 3 2) (4 3 1 2) (4 3 2 1))
Notes:
-
It is helpful to use
append-all
,map
, andinserts-foldr
in your solution. If you are unable to defineinserts-foldr
from the previous subproblem, you may use the following recursive version of theinserts
function from PS2 Problem 4:(define (inserts-rec x ys) (if (null? ys) (list (list x)) (cons (cons x ys) (map-cons (first ys) (inserts-rec x (rest ys))))))
-
Your definition should have exactly this pattern:
(define (my-permutations xs) (foldr ; binary combiner goes here ; null value goes here xs))
-
4. forall?
, exists?
, find
, and zip
(20 points)
Below are some list-processing functions that are not built in to Racket, but are handy in many situations:
(define (forall? pred xs)
(or (null? xs)
(and (pred (first xs))
(forall? pred (rest xs)))))
(define (exists? pred xs)
(and (not (null? xs))
(or (pred (first xs))
(exists? pred (rest xs)))))
(define (find pred not-found xs)
(if (null? xs)
not-found
(if (pred (first xs))
(first xs)
(find pred not-found (rest xs)))))
(define (zip xs ys)
(if (or (null? xs) (null? ys))
'()
(cons (cons (first xs) (first ys))
(zip (rest xs) (rest ys)))))
;; Can also define zip using Racket's mutlilist map
;; (but need to make both list args have same length;
;; otherwise Racket map will generate error if theyr don't)
;;
;; (define (zip xs ys)
;; (let ((minlen (min (length xs) (length ys))))
;; (map cons (take xs minlen) (take ys minlen))))
forall?
, exists?
, and find
are higher-order list functions involving a predicate.
-
forall?
returns#t
if the predicate is true on all elements of the list, and otherwise returns#f
.> (forall? (λ (x) (> x 0)) '(7 2 5 4 6)) #t > (forall? (λ (x) (> x 0)) '(7 2 -5 4 6)) #f
-
exists?
returns#t
if the predicate is true on at least one element of the list, and otherwise returns#f
.> (exists? (λ (x) (< x 0)) '(7 2 -5 4 6)) #t > (exists? (λ (x) (< x 0)) '(7 2 5 4 6)) #f
-
find
returns the first element of the list for which the predicate is true. If there is no such element, it returns the value supplied as thenot-found
argument.> (find (λ (x) (< x 0)) #f '(7 2 -5 4 -6)) -5 > (find (λ (x) (< x 0)) #f '(7 2 5 4 6)) #f
The zip
function is not higher order, but combines two lists by pairing (using cons
) the corresponding elements of the two lists. If the lists do not have the same length, zip
returns a list of pairs whose length is the length of the shorter of the two input lists:
> (zip '(1 2 3) '("a" "b" "c"))
'((1 . "a") (2 . "b") (3 . "c"))
> (zip '(1 2 3 4 5) '("a" "b" "c"))
'((1 . "a") (2 . "b") (3 . "c"))
> (zip '(1 2 3) '("a" "b" "c" "d" "e"))
'((1 . "a") (2 . "b") (3 . "c"))
In this problem, you will use the forall?
, exists?
, find
, and zip
functions to define other functions. Begin this problem by copying the definitions of these four functions into the top of your yourAccountName-ps3-functions.rkt
file.
-
(3 points) Using
exists?
, define a functionmember?
that determines if an elementx
appears in a listys
.> (member? 4 '(7 2 5 4 6)) #t > (member? 3 '(7 2 5 4 6)) #f > (member? '(7 8) '((1 2) (3 4 5) (6) (7 8) () (9))) #t > (member? '() '((1 2) (3 4 5) (6) (7 8) () (9))) #t > (member? '(5 6) '((1 2) (3 4 5) (6) (7 8) () (9))) #f
Note: Use
equal?
to compare the equality of two values. -
(5 points) Using
forall?
andexists?
, define a functionall-contain-multiple-alt?
that is an alternative implementation of theall-contain-multiple?
function from Problem 3.> (all-contain-multiple-alt? 5 '((17 10 2) (25) (3 8 5))) #t > (all-contain-multiple-alt? 2 '((17 10 2) (25) (3 8 5))) #f > (all-contain-multiple-alt? 3 '()) #t ; said to be "vacuously true"; there is no counterexample!
Note: You may use the
divisible_by
function from above, but not thecontains-multiple?
function, and you may not define any new helper functions. -
(4 points) An association list is a list of pairs that represents a mapping from key to value. Each pair of key and value is represented by a cons cell, with the key in the
car
and the value in thecdr
. For example, the association list:'((2 . 3) (5 . 1) ("mountain" . #t))
maps the key
2
to the value3
, the key5
to the value1
, and the key"mountain"
to the value#t
.Using
find
, define a functionlookup
that takes a keyk
and an association listas
and returns:#f
if no mapping with keyk
was not found in the list; and- a cons cell whose
car
isk
and whosecdr
is the corresponding value for the shallowest mapping ofk
in the association list.
For example:
> (lookup 5 '((2 . 3) (5 . 1) ("mountain" . #t))) '(5 . 1) > (lookup 1 '((2 . 3) (5 . 1) ("mountain" . #t))) #f > (lookup '(6 4) '((2 . 3) (5 . 1) ((6 4) . 8) (5 . 1) (17 23 42))) '((6 4) . 8) > (lookup 17 '((2 . 3) (5 . 1) ((6 4) . 8) (5 . 1) (17 23 42))) '(17 23 42) ; '(17 23 42) has a car of 17 and a cdr of '(23 42) > (lookup 23 '((2 . 3) (5 . 1) ((6 4) . 8) (5 . 1) (17 23 42))) #f
Note: Use
equal?
to test for equality of keys. This will support keys more interesting than just simple values. -
(5 points) Using
forall?
andzip
, define a functionsorted?
that determines if a list of numbersns
is in sorted order from low to high.> (sorted? '(7 4 2 5 4 6)) #f > (sorted? '(2 3 3 5 6 7)) #t > (sorted? '(2)) #t > (sorted? '()) #t > (sorted? (range 1000)) #t > (sorted? (append (range 1000) '(1001 1000))) #f > (sorted? (range 1000 0 -1)) #f
Notes:
-
You will need to have a special case for the empty list.
-
Hint: For the input
'(7 4 2 5 4 6)
, the list of pairs'((7 . 4) (4 .2) (2 . 5) (5 . 4) (4 . 6))
is very useful in this problem. How can you create this list of pairs?
-
-
(3 points) It is possible to define alternative versions of
forall?
andexists?
in terms offoldr
, as show below.(define (forall-alt? pred xs) (foldr (λ (x subres) (and (pred x) subres)) #t xs)) (define (exists-alt? pred xs) (foldr (λ (x subres) (or (pred x) subres)) #f xs)) > (forall-alt? (λ (x) (> x 0)) '(7 2 5 4 6)) #t > (forall-alt? (λ (x) (> x 0)) '(7 2 -5 4 6)) #f > (exists-alt? (λ (x) (< x 0)) '(7 2 -5 4 6)) #t > (exists-alt? (λ (x) (< x 0)) '(7 2 5 4 6))
However, just because it’s possible to define a function in terms of
foldr
does not mean its a good idea. Give a concrete example of a situation in whichforall?
is better thanforall-alt?
.Notes:
-
For this problem, it’s critical to understand that
(and e1 e2)
desugars to(if e1 e2 #f)
-
Hint: Consider a long list (say a million elements) in which the predicate is false for the first element. How much work does
forall
do to compute the result? How much work doesforall-alt
do to computer the result?
-
5. foldr-ternop
(10 points)
Sometimes it is difficult to express a recursive list accumulation in terms of foldr
because the binary combiner function needs more information from the list than its first element. The following foldr-ternop
higher-order list function solves this problem by having the combiner function be a ternary (i.e., three-argument) function that takes both the first and rest of the given list in addition to the result of recursively processing the list:
(define (foldr-ternop ternop null-value xs)
(if (null? xs)
null-value
(ternop (first xs)
(rest xs)
(foldr-ternop ternop null-value (rest xs)))))
In this problem, you will use foldr-ternop
to implement two list functions that are challenging to implement in terms of foldr
(you already did inserts-foldr
above; see the extra credit problem for sorted?-foldr
below). Begin this problem by copying the definition of foldr-terntop
into the top of your yourAccountName-ps3-functions.rkt
file.
-
(5 points) Using
foldr-ternop
, define a functioninserts-foldr-ternop
that takes a valuex
and an n-element listys
and returns an n+1-element list of lists showing all ways to insert a single copy ofx
intoys
.> (inserts-foldr-ternop 3 '(5 7 1)) '((3 5 7 1) (5 3 7 1) (5 7 3 1) (5 7 1 3)) > (inserts-foldr-ternop 3 '(7 1)) '((3 7 1) (7 3 1) ( 7 1 3)) > (inserts-foldr-ternop 3 '(1)) '((3 1) (1 3)) > (inserts-foldr-ternop 3 '()) '((3)) > (inserts-foldr-ternop 3 '(5 3 1)) '((3 5 3 1) (5 3 3 1) (5 3 3 1) (5 3 1 3))
Notes:
-
Your definition should have exactly this pattern:
(define (inserts-foldr-ternop x ys) (foldr-ternop {ternary-combiner} {null-value} ys))
-
You may use
map-cons
in your ternary-combiner function.
-
-
(5 points) Using
foldr-ternop
, define a functionsorted-alt?
that is an alternative implementation of thesorted?
function from Problem 4.> (sorted-alt? '(7 4 2 5 4 6)) #f > (sorted-alt? '(2 3 3 5 6 7)) #t > (sorted-alt? '(2)) #t > (sorted-alt? '()) #t > (sorted-alt? (range 1000)) #t > (sorted-alt? (append (range 1000) '(1001 1000))) #f > (sorted-alt? (range 1000 0 -1)) #f
Note:
-
Your definition should have exactly this pattern:
(define (sorted-alt? xs) (foldr-ternop ; ternary-combiner goes here ; null-value goes here xs))
-
Extra Credit1: Using foldr
to define sorted?
(6 points)
This problem is optional. You should only attempt it after completing all the other problems.
As noted in Problem 5, it is challenging to define sorted
in terms of foldr
, but it turns out that it is stil possible to do this.
Using foldr
, define a function sorted?-foldr
that is an alternative implementation of the sorted?
function from Problem 5.
> (sorted?-foldr (list 7 4 2 5 4 6))
#f
> (sorted?-foldr (list 2 3 3 5 6 7))
#t
> (sorted?-foldr (list 2))
#t
> (sorted?-foldr (list))
#t
Note:
-
Your definition should have exactly the following pattern:
(define (sorted?-foldr ys) (cdr (foldr ; binary combiner goes here (cons ; null-value1 ; null-value2 ) ys)))
The idea is to accumulate a pair of (1) the first element of the rest of the list (or
#f
if there is none) and (2) a boolean indicating whether the rest of the list is sorted.
Extra Credit 2: Recursion Via the Y Combinator (15 points)
This problem is optional. You should only attempt it after completing all the other problems.
Consider the following sumTo
function in Racket, which sums the numbers from 1 up to its argument.
(define sumTo (λ (n)
(if (<= n 0)
0
(+ n (sumTo (- n 1))))))
> (sumTo 3)
6
> (sumTo 10)
55
> (sumTo 100)
5050 ; 50 pairs of numbers like (+ 1 100), (+ 2 99), (+ 3 98), etc,, where each pair sums to 101.
We will use λ_sumTo
as an abbreviation for the lambda expression bound to the name sumTo
.
Small-step semantics explains how such recursions work. In all the recursion examples we’ve seen, define
is used to name the recursive function in an environment, and then this name is looked up in the environment when the recursive function is called. This allows the function to refer to itself, and is an important detail for ``tying the knot of recursion”.
It is a remarkable fact that recursion in Racket (and any other languages with first-class anoymouse functions) does not require environments (or even letrec
), and can be done purely via lambda
and the substitution model. This is shown by example in this problem.
Here is a very special Racket lambda
expression known as the Y combinator, Y operator, or fixed-point combinator.
(λ (funToFun)
((λ (z) (z z))
(λ (x) (funToFun (λ (v) ((x x) v)))))))
Let’s call this function λ_fix
. It takes as its argument a function funToFun
that maps an input function to an output function. For example, funToFun
might be the following function, which we’ll call λ_sumToGen
:
(λ (f)
(λ (n)
(if (<= n 0)
0
(+ n (f (- n 1))))))
Note when λ_sumToGen
is applied to some input function, the output function is almost always different from the input function. For example:
-
If
λ_sumToGen
it is applied to the function(λ (m) (+ m 1))
, it returns a function equivalent to(λ (n) (if (<= n 0) 0 (+ n n)))
, which behaves very differently from(λ (m) (+ m 1))
. -
If
λ_sumToGen
it is applied to the function(λ (m) (* m 2))
, it returns a function equivalent to(λ (n) (if (<= n 0) 0 (+ n (* 2 (- n 1)))))
, which is equivalent to(λ (n) (if (<= n 0) 0 (- (* 3 n) 2)))
. Again, this output function is very different from the input function(λ (m) (* m 2))
. -
If
λ_sumToGen
it is applied to a factorial function (call itλ_fact
), it returns a function equivalent to(λ (n) (if (<= n 0) 0 (+ n (
λ_fact(- n 1)))))
. Because this adds n to the factorial of (n-1) rather than multiplying n by the factorial of (n-1), this output function is not the same as the input factorial function.
However, there is one exception: If λ_sumToGen
it is applied to the sumTo
function (i.e. λ_sumTo
), it returns a function equivalent to (λ (n) (if (<= n 0) 0 (+ n (
λ_sumTo (- n 1)))))
. In this case, the output function is just another version of the sumTo
function! Because the input and output functions are the same in this case, we say that λ_sumTo
is the fixed point of λ_sumToGen
— i.e., it is a value V
such that (λ_sumToGen V)
is the same as V
.
Remarkably, when the fixed-point combinator λ_fix
is applied to λ_sumToGen
it returns a function equivalent to λ_sumTo
— that is, it returns the fixed point of λ_sumToGen
. This is why λ_fix
is called the fixed-point combinator.
Your task is to show that (λ_fix λ_sumToGen)
behaves like λ_sumTo
by showing the following derivations in small-step semantics:
((λ_fix λ_sumToGen) 0)
⇒* 0 (i.e., the sum of nonnegative integers up to and including 0)((λ_fix λ_sumToGen) 1)
⇒* 1 (i.e., the sum of nonnegative integers up to and including 1)((λ_fix λ_sumToGen) 2)
⇒* 3 (i.e., the sum of nonnegative integers up to and including 2)((λ_fix λ_sumToGen) 3)
⇒* 6 (i.e., the sum of nonnegative integers up to and including 3)
You can abbreviate certain sequences of ⇒ steps via ⇒* as long as you clearly show the essence of how these reductions work.
This is tedious, but it is amazing to see how the λ_fix
is able to perform recursion without using define
to name the recursive function in the global environment, or without using letrec
to name a local recursive function.
You can also verify that this works in Racket. For example, the sum of nonnegative integers up to and including 100 is 5050:
> (((λ (funToFun) ; this is λ_fix
((λ (z) (z z))
(λ (x) (funToFun (λ (v) ((x x) v))))))
(λ (f) ; this is λ_sumToGen
(λ (n)
(if (<= n 0)
0
(+ n (f (- n 1)))))))
100) ; this whole expression is ((λ_fix λ_sumToGen) 100)
5050