;; The first three lines of this file were inserted by DrRacket. They record metadata ;; about the language level of this file in a form that our tools can easily process. #reader(lib "htdp-intermediate-lambda-reader.ss" "lang")((modname passing-functions) (read-case-sensitive #t) (teachpacks ()) (htdp-settings #(#t constructor repeating-decimal #f #t none #f () #f))) ;;;; In the video-game soln, we called 'big-bang'. ;;;; (a) What is the view? The controller? The model? ;;;; (b) Hey, we're calling big-bang, and passing it a *function(?!)*. ;;; *** Functions are values! *** ;;; You can pass them to a function ;;; (or even have a functions whose return-type is a function). ;;; Table of contents: ;;; 1. Passing a function (that already exists) ;;; racket, javascript/python; java ;;; https://youtu.be/80QpPnKnuSM duration="15m07s" ;;; 2. Passing a function (a new, anonymous function) ;;; racket, javascript/python; java ;;; https://youtu.be/t0j-XCgFQC8 duration="11m59s" ;;; 3. signatures for higher-order functions ;;; https://youtu.be/Bi0vH6cKEkU duration="15m23s" ;;; 4. Receiving a function ;;; a. racket, javascript/python ;;; https://youtu.be/RcJuktlZjts duration="15m58s" ;;; b. Java -- what's happening under the hood ;;; (no video yet) ;;; 5. Advanced: A function returning a new function. ;;; (no video currently planned; contact me if you want one!) ;;; >>> 1. Passing a function (that already exists) ;;; racket, javascript/python; java ;;; https://youtu.be/80QpPnKnuSM duration="15m07s" ; The standard-library function "sort" takes two inputs: ; the list to sort, and what comparison function to use. (sort (list 3 7 1) <=) ; N.B. full-racket has list-literals: (sort '(3 7 1) <=) ; call `sort` alphabetically: (sort (list "gamma" "alpha" "epsilon" "beta") string<=?) ; sort by length: (define (string-length<=? s1 s2) (<= (string-length s1) (string-length s2))) (sort (list "gamma" "alpha" "epsilon" "beta") string-length<=?) ;;;;;;;;;;;;;; javascript #| // Run this code at, e.g., http://math.chapman.edu/~jipsen/js/ writeln( (new Array(3,7,1).sort() ); // by default, `sort` uses <= // N.B. javascript has list-literals: writeln( [3,7,1].sort() ) writeln( (new Array( "gamma", "alpha", "epsilon," "beta").sort() ); // "<=" also works for strings. // We can also call `sort`, and pass in the comparison-function. // Note that we can't pass *operators* like "<=", so we might need to // wrap the operator in a function, annoyingly: // compareTwoNums : number, number -> number // returns negative, 0, or positive to mean ab resp. // function compareTwoNums(a,b) { return a-b; } //writeln( compareToNums(3,7) ); // returns negative, indicating 3<7 //writeln( compareToNums(7,3) ); // returns positive, indicating 7>3 // Btw: mnemonic for *which* number is smaller, based on `compareTo`s result: // think of subtraction: // When I see `if (compare(a,b) < 0) …` I think `if (a-b < 0) …` // and similarly for O.O.: `if (a.compareTo(b) < 0) …` I think `if (a-b < 0) …`. writeln( (new Array(3,7,1)).sort(compareTwoNums) ); // sort by length: function compareToLength(s1,s2) { return s1.length()-s2.length(); } writeln( (new Array( "gamma", "alpha", "epsilon," "beta").sort(compareToLength) ); |# ;;;;;;;;;;;;;;;; python #| sorted( [3,7,1] ) # python has no list-literals, and `[..]` *is* the constructor. (Cf. racket `quasiquote`). # Note: python function `sort` is a void method that mutates the list; # `sorted` returns a new, sorted collection (and it works on any collection, incl. immutable ones) # call `sort` alphabetically: sorted( ["gamma", "alpha", "epsilon", "beta"] ) # sort by length: (sorted ["gamma", "alpha", "epsilon", "beta"], key=len ) # python's `sort` doesn't have a way to accept a comparison-function directly; # instead you can pass a "key" function which which first applies that function # to each item-to-compare, and then uses the primitive `<=` on that result. # So you can't define a function `puppy<=?` to compare two `puppy` objects; # instead you need to write a `puppy->double` in a way that preserves your desired comparison. # This is equally powerful [I suspect], but not quite as convenient ... though # python's built-in `<=` on tuples (lexicographic) ameliorates this. # Fwiw: racket's `sort` also supports passing a `#:key` keyword-argument: # (sort '("gamma" "alpha" "epsilon" "beta") # <= # #:key string-length) |# ;;;;;;;;;;;;;;;;; Java 8 #| import java.util.List; import java.util.stream.*; List.of(3, 7, 1).stream().sorted().collect(Collectors.toList()) // uses `Integer.compareTo` // N.B. If you are content to have just a `stream` result instead of List, you can omit the `.collect(..)` // sort a list of strings: List.of("gamma", "alpha", "epsilon", "beta").stream().sorted().collect(Collectors.toList()) // uses `String.compareTo` // sort a list of strings by length: int compareToByLength(String a, String b) { return a.length() - b.length(); } List.of("gamma", "alpha", "epsilon", "beta").stream().sorted(compareToByLength).collect(Collectors.toList()) See also: https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html Reference to a static method ContainingClass::staticMethodName Reference to an instance method of a particular object containingObject::instanceMethodName Reference to an instance method of an arbitrary object of a particular type ContainingType::methodName Reference to a constructor ClassName::new |# ; There is a built-in `map`: ; Given a list, and a "mallet" function, ; hit each item of the lsit with the mallet, and collect the results. ; (Recall how we we wrote 'map-sqr' -- that was 'map' just for the ; *particular* function `sqr`, but `map` generalizes it to *any* function. ; We'll *write* the function later; for now let's consider the signature. (check-expect (map sqr (list 3 2 4)) (list 9 4 16)) (check-expect (map string-length (list "gamma" "alpha" "epsilon" "beta")) (list 5 5 7 4)) ; We could've used this to help w/ move-bullets, and move-astrs: ; #;(define (move-trucks ts) (map move-truck ts)) ; In fact, we wouldn't even bother writing this function -- we'd just inline the call to `map`! ; (D'oh Barland -- *now* you tell us!) ; We can write `map` ourselves (below). ; filtering: ; (define (filter-positives nums) (cond [(empty? nums) '()] [(cons? nums) (if (positive? (first nums)) (cons (first nums) (filter-positives (rest nums))) (filter-positives (rest nums)))])) ; OR: #;(define (filter-positives nums) (filter positive? nums)) ; And again: in practice, we'd just inline this function. #| maps, filters are in Java (and other langs) too: import java.util.List; import java.util.stream.*; List.of(16, 25, 9).stream().map(Math::sqrt).collect(Collectors.toList()) |# ; We wrote astrs-collide-bullet, along with a helper astr-collide-bullet ; ; astrs-collide-bullet : list-of-astr, bullet -> list-of-astr ; Return all the asteroids which are colliding with `b`. ; (define (astrs-collide-bullet astrs b) (cond [(empty? astrs) '()] [(cons? astrs) (if (astr-collide-bullet (first astrs) b) (cons (first astrs) (astrs-collide-bullet (rest astrs))) (astrs-collide-bullet (rest astrs)))])) ; just so we compile in this file: (define (astr-collide-bullet a b) #t) ; This is just a filter. ; However we *really* want anonymous functions, because ; the keep-function needs the *specific* `b` passed in: ; #;(define (astrs-collide-bullet astrs b) (filter (λ(b) (astr-collide-bullet (first astrs) b)) astrs)) ;;; >>> 2. Passing anonymous functions ;;; URL: https://youtu.be/t0j-XCgFQC8 duration="11m59s" ; sort by length, but don't bother naming the function as a separate define: (sort (list "gamma" "alpha" "epsilon" "beta") (λ (s1 s2) (> (string-length s1) (string-length s2)))) ; We call this an "anonymous function" -- ; just like: to pass the int 37 to a function, you *could* name a variable ; to hold 37, and then use that variable when calling the function -- ; but SO much easier to just pass the literal int 37, w/o giving it a name. ; Likewise for function-values: ; we *could* give a name to this function, but SO much easier ; to just pass the literal function, w/o giving it a name. ; Time for Truth: there is only one "real" version of `define`; ; it associates a name with a value. (define n (+ 35 2)) ; If you write ;(define (is-over-limit? speed) (> speed 85)) ;; racket actually re-writes it to: (define is-over-limit? (lambda (speed) (> speed 85))) ; ; (Aside, vocab: this re-writing is an example of "syntactic sugar" -- a ; language-syntax that is merely a convenient translation of other, existing forms. ; Another example: We saw earlier that `let*` is syntactic sugar for nested-`let`s.) ; ;;;;;;;;;;;;;; javascript #| // Run this code at, e.g., http://math.chapman.edu/~jipsen/js/ // sort by length: // don't bother with: function compareToLength(s1,s2) { return s1.length()-s2.length(); } // and instead pass it as an anonymous function: writeln( (new Array( "gamma", "alpha", "epsilon", "beta")).sort(function (s1,s2) { return s1.length-s2.length; }) ); // // Alternate, even-shorter syntax for the anonymous function: // Rather than the word "function" you can use "=>", and if you only // have one expression you can omit the `{...}`, and also the `return` statement. // writeln( ["gamma", "alpha", "epsilon", "beta"].sort( (s1,s2) => s1.length-s2.length )); // And, when *defining* a function: Instead of: // function compareToLength(s1,s2) { return s1.length()-s2.length(); } // // You can also write: // var compareToLength = function(s1,s2){ … } |# ;;;;;;;;;;;;;;;; python #| # sort by length: (sorted ["gamma", "alpha", "epsilon", "beta"], key=lambda s: len(s) ) # however, note that `lambda s: len(s)` is a roundabout way of writing `len`, # so IRL you'd write `sort(…, key=len)` like we did earlier. # but you might write `(sorted …, key= lambda s: -len(s) )` to sort by descending-length |# ;;;;;;;;;;;;;;;;; Java 8 #| import java.util.List; import java.util.stream.*; // sort by length: // don't bother with: int compareToByLength(String a, String b) { return a.length() - b.length(); } List.of("gamma", "alpha", "epsilon", "beta").stream().sorted((s1,s2) -> s1.length()-s2.length()).collect(Collectors.toList()) |# ;;; >>> 3. signatures of higher-order functions ;;; URL: https://youtu.be/Bi0vH6cKEkU duration="15m23s" ;;; ;;; sort: list-of-??, ?? -> list-of-?? ;;; ;;; ; Revisit: ; Consider "my-map" (works same as built-in "map"): ; Given a list, and a "mallet" function, ; hit each item of the lsit with the mallet, and collect the results. ; (Recall how we we wrote 'map-sqr' -- that was 'map' just for the ; *particular* function `sqr`, but `map` generalizes it to *any* function. ; We'll *write* the function later; for now let's consider the signature. (check-expect (my-map sqr (list 3 2 4)) (list 9 4 16)) ; we can, of course, pass an anonymous function to `my-map` as well: (check-expect (my-map (λ (n) (+ (* 3 n) 1)) (list 3 2 4)) (list 10 7 13)) (check-expect (my-map string-length (list "gamma" "alpha" "epsilon" "beta")) (list 5 5 7 4)) ;;; What is the signature for `my-map`? ;;; my-map : (α -> β), list-of-α -> list-of-β ;;; In a typed-language(*), we need to be more formal: ;;; //Java: ;;; List myMap( ???, List ) ;;; ;;; ;;; (*) rather: a language which requires/allows you to declare types ;;; ;;; // N.B. Java actually makes `map` a method of `Class stream`. ;;; Stream map( /* Stream this, */ Function mapper) ;;; // https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/function/Function.html ;;; // https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/stream/Stream.html ;;; ;;; ;;; >>> 4. Receiving a function: ;;; a. in racket (and languages truly supporting functions as values) ;;; URL: https://youtu.be/RcJuktlZjts duration="15m58s" ;;; b. in Java (how Java fakes it) ;;; Okay, so we saw how to *pass* a function to `sort` ;;; (both passing an existing-function, and an anonymous function.) ;;; ;;; Q: But what did the authors of `sort` do, to *invoke* that function they were handed? ;;; (And how did `big-bang` invoke the `to-draw` and `on-key` and `on-tick` handlers? ;;; ;;; A: easy -- the function passed in is bound to a parameter-name; just use *that* name to call. (check-expect (my-map sqr (list)) (list)) (check-expect (my-map sqr (list 4)) (list 16)) (check-expect (my-map sqr (list 2 4)) (list 4 16)) (check-expect (my-map sqr (list 3 2 4)) (list 9 4 16)) ; we can, of course, pass an anonymous function to `my-map` as well: (check-expect (my-map (λ (n) (+ (* 3 n) 1)) (list 3 2 4)) (list 10 7 13)) (check-expect (my-map string-length (list)) (list)) (check-expect (my-map string-length (list "gamma")) (list 5)) (check-expect (my-map string-length (list "gamma" "beta")) (list 5 4)) (check-expect (my-map string-length (list "gamma" "alpha" "epsilon" "beta")) (list 5 5 7 4)) ; my-map: SIGNATURE? ; Return the result of applying `f` to each element of `lst`. ; (define (my-map f lst) (cond [(empty? lst) ...] [(cons? lst) (... (first lst) ...(rest lst))])) ;;;;;;;;;;;;;; javascript #| // Run this code at, e.g., http://math.chapman.edu/~jipsen/js/ // myMap : (T -> R), Array -> Array // function myMap( mapper, itms ) { // Use the built-in, mutable arrays :-( var resultSoFar = []; for (var i=0; i resultSoFar.push(mapper(itm)) ); return resultSoFar; } writeln( myMap(Math.sqrt, [3,4,5]) ); |# ;;;;;;;;;;;;;;;; python #| def myMap(mapper, itms): # Use the built-in, mutable lists :-( resultSoFar = [] for itm in itms: resultSoFar += [ mapper(itm) ] return resultSoFar import math myMap( math.sqrt, [3,4,5] ) myMap( lambda n: 3*n+1, [3,4,5] ) |# ;;;;;;;;;;;;;;;;; Java 8 #| import java.util.List; import java.util.function.*; import java.lang.Math; class MyMapDemo { public static List myMap( Function mapper, List itms ) { // use built-in mutable lists :-( List resultSoFar = new java.util.ArrayList( itms.size() ); for ( T itm : itms ) { resultSoFar.add( mapper.apply(itm) ); } return resultSoFar; } public static void testMyMap() { System.out.println( myMap( Math::sqrt, List.of(3.0,4.0,5.0) )); System.out.println( myMap( Integer::toHexString, List.of(30,31,32,33) )); System.out.println( myMap( (n)->3*n+1, List.of(3,4,5) )); } public static void main( String... __ ) { testMyMap(); } } |# ;;;;;;;;;;;;;;;;; ;;;;; Passing functions (in racket, in Java). (sort (list "Modest Mouse" "The Police" "The Cars" "The Black Keys" "The The") string<=?) ;; Java: #| List bands = Arrays.asList( "Modest Mouse", "The Police", "The Cars", "The Black Keys", "The The" ); Collections.sort(bands); System.out.println( bands.toString() ); |# ;; Suppose we want to sort, ignoring any initial "The"? ; Really, the 'important' part of this is just being able to compare ; two individual bands, and see which should come earlier: ; (check-expect (band<=? "ABC" "XTC") true) (check-expect (band<=? "XTC" "ABC") false) (check-expect (band<=? "M" "M") true) (check-expect (band<=? "The Cars" "Lorde") true) (check-expect (band<=? "Lorde" "The Cars") false) (check-expect (band<=? "The Black Keys" "The Cars") true) (check-expect (band<=? "The Black Keys" "The The") true) (check-expect (band<=? "The Zombies" "The The") false) (check-expect (band<=? "The" "ABC") false) (check-expect (band<=? "The" "The The") true) (check-expect (band<=? "ABC" "The") true) (check-expect (band<=? "The The" "The") true) ; band<=? : string, string -> boolean ; Given two band-names (non-empty strings), sort them alphabetically ; but ignore any leading "The". ; (define (band<=? s1 s2) (string<=? (remove-prefix-if "The " s1) (remove-prefix-if "The " s2))) ; remove-prefix-if : string, string -> string ; If `str` starts with `pref` (followed by strictly more), ; then remove the prefix `pref`, otherwise just return `str`. ; (check-expect (remove-prefix-if "abc" "abcdef") "def") (check-expect (remove-prefix-if "abc" "xyzdef") "xyzdef") (check-expect (remove-prefix-if "abc" "abcabc") "abc") (check-expect (remove-prefix-if "abc" "abc") "abc") (check-expect (remove-prefix-if "abc" "dabc") "dabc") (check-expect (remove-prefix-if "" "xyz") "xyz") (check-expect (remove-prefix-if "xyz" "ab") "ab") (check-expect (remove-prefix-if "xyz" "") "") ; remove-prefix-if : string, string -> string ; Remove the `pref` from the front of `str` if it was there ; (*and* `str` had *more* stuff after the prefix). ; (define (remove-prefix-if pref str) (if (and (> (string-length str) (string-length pref)) (string=? pref (substring str 0 (string-length pref)))) (substring str (string-length pref)) str)) (sort (list "Modest Mouse" "The Police" "The Cars" "The Black Keys" "The The") band<=?) ;;; OR: Don't bother naming the function; use an anonymous function: ; (sort (list "Modest Mouse" "The Police" "The Cars" "The Black Keys" "The The") (lambda (s1 s2) (string<=? (remove-prefix-if "The " s1) (remove-prefix-if "The " s2)))) ; The keyword 'lambda' simply means "this next bit is a function, but ; we're not going to name it." ; In javascript they use the keyword `function`, and see below for Java 8. ; (The name stems from Alonzo Church and "the lambda calculus", in 1930s, ; a model of computation similar to the Turing Machine (but more math-based, ; and type-based.) Note that it pre-dates computers.) ; #| Java, pre-Java-8 (and, what Java8 compiles down to): // The interface 'Comparator' is for objects that have one method: // 'compare'. Make a whole class, to hold the specific // code we want: // class BandComparer implement Comparator { public int compare(String s1, String s2) { return s1.length() < s2.length(); } } class BandComparer2 implements Comparator { public int compare(String s1, String s2) { return removePrefixIf("The ", s1).compareTo(removePrefixIf("The ",s2)); } } // Now, use that class: bands = Arrays.asList( "Modest Mouse", "The Police", "The Cars", "The Black Keys", "The The" ); Collections.sort(bands, new BandComparer2() ); System.out.println( bands.toString() ); // If we don't want the overhead of the extra class (with its own file?), // we can also use an anonymous class: // bands = Arrays.asList( "Modest Mouse", "The Police", "The Cars", "The Black Keys", "The The" ); Collections.sort(bands, new Comparator() { public int compare(String s1, String s2) { return removePrefixIf("The ",s1).compareTo(removePrefixIf("The ",s2)); } } ); System.out.println( bands.toString() ); In Java 8: Yay, it's now easy to create the code that you pass to the function: Collection.sort( bands, (s1,s2)->removePrefixIf("The ",s1).compareTo(removePrefixIf("The ",s2)) ); If you want to write your own functions that *receive* a lambda as input, you still need to declare an interface with one method inside it. Fortunately, many run-of-the-mill such interfaces are provided for you in java.util.function : https://docs.oracle.com/javase/9/docs/api/java/util/function/Function.html https://docs.oracle.com/javase/9/docs/api/java/util/function/Predicate.html List myFilter( List data, Predicate keepP ) { if (data.isEmpty()) { return new LinkedList(); } else { if (keepP.test( data.get(0) )) { List resultRecur = myFilter( data.sublist(1,data.size()), keepP ).clone(); resultRecur.add(data.get(0),0); // push to front of list return resultRecur; } else { return myFilter( data.sublist(1,data.size()), keepP ); } } } |# #| Examples curated from: http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html Consider the following function-call, in Java 8, which take a list of People objects, gets their email, and then prints it: processPersonsWithFunction( roster, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25, p -> p.getEmailAddress(), email -> System.out.println(email) ); ; The equivalent racket: (process-persons-w-function roster (λ(p) (and (eq? (person-gender p) MALE) (>= (person-age p) 18) (<= (person-age p) 25)) person-email-address display) It's calling a rather-general-purpose function "give me any list, and a predicate for filtering the list, and then map an operation onto all the survivors, and use the result of that map to ". Here's how you'd write that function: public static void processPersonsWithFunction( List roster, Predicate tester, Function mapper, Consumer block) { for (Person p : roster) { if (tester.test(p)) { String data = mapper.apply(p); block.accept(data); } } } // SEE ALSO: // https://www.java67.com/2019/06/top-5-sorting-examples-of-comparator-and-comparable-in-java.html (define (process-persons-with-function roster tester mapper block) (block (map mapper (filter test roster))) Note that (unlike racket's map,filter) Java's functions are working on *streams*, which is an advantage. (racket2 will presumably generalize map and filter directly; for now you need to use stream-map, stream-filter, etc.) |# ;;; 5. (Not yet completed:) Functions that *return* functions. ;;; One example, btw: ;;; From Calculus: Signature for Differentiation (or, Integration): ;;; d/dx : ??? -> ??? ; an example-function for the next topic: ; string-middle : string -> string ; (define (string-middle str) (let* {[len (string-length str)] [start (round (/ len 3))] [end (- len start)] } (substring str start end))) (check-expect (string-middle "") "") (check-expect (string-middle "abc") "b") (check-expect (string-middle "abcdef") "cd") (check-expect (string-middle "a") "a") (check-expect (string-middle "ab") "") ; More advanced: A function *returning* a new function: (require "student-extras.rkt") ; just for `string-titlecase` ; string-func->symbol->func ; (define (symbol-upcase-v1 sym) (string->symbol (string-upcase (symbol->string sym)))) (define (symbol-titlecase-v1 sym) (string->symbol (string-titlecase (symbol->string sym)))) (define (symbol-middle-v1 sym) (string->symbol (string-middle (symbol->string sym)))) ; Yuck, the above are all very-redundant code! ; Can I make a general function that, *given* a string-function, ; converts it into a similar one working on symbols? ; (define (string-func->symbol-func str-fun) (lambda (x) 'stub)) (define (symbol-upcase-v2 sym) (string-func->symbol-func string-upcase)) (define (symbol-titlecase-v2 sym) (string-func->symbol-func string-upcase)) (define (symbol-middle-v2 sym) (string-func->symbol-func string-upcase)) (check-expect (symbol-upcase-v1 'abc) 'ABC) (check-expect (symbol-titlecase-v1 'abc) 'Abc) (check-expect (symbol-middle-v1 'abc) 'b) (check-expect (symbol-upcase-v2 'abc) 'ABC) (check-expect (symbol-titlecase-v2 'abc) 'Abc) (check-expect (symbol-middle-v2 'abc) 'b) ; suppose we want to add 1 to every element in a list, then square it. ; There are several options: ; () write special-purpose function, following list-template. ; (i) define a special-purpose-add1-then-sqr, then `map` it: ; (define (add1-then-sqr x) -99) (check-expect (map add1-then-sqr (list 1 2 3 4)) (list 4 9 16 25)) ; (ii) don't write `add1-then-sqr`; instead call `map` twice: ; #;(check-expect ;USE-TWO-CALLS-TO-MAP (list 1 2 3 4) (list 4 9 16 25)) ; (iii) function-composition: ; (check-expect (map (my-compose sqr add1) (list 1 2 3 4)) (list 4 9 16 25)) (check-expect (map (my-compose add1 sqr) (list 1 2 3 4)) (list 2 5 10 17)) ; my-compose : ; (define (my-compose f g) (lambda(i) i)) ; TASK: write: make-key-comparer ; which takes in a "key" function (that returns a number), ; and returns a function that: compares two items by comparing their (numeric) keys. ; For example: (check-expect (sort (list "abcd" "xy" "zzz") (make-key-comparer string-length <=)) (list "xy" "zzz" "abcd")) ; make-key-comparer : [SIGNATURE?] ; (define (make-key-comparer extract-key) <=)