Upload
others
View
10
Download
0
Embed Size (px)
Citation preview
CS 152 - Lecture 1
Cay S. Horstmann
Course Objectives
▪ Objectives:• To ensure that students gain an understanding of programming language
design and language translation.• To achieve competence in a functional programming language.
▪ What will you learn?• How a programming language is compiled or interpreted• How to program in paradigms other than OO
▪ Why should you care?• For most of your career, you will program in a language other than Java or
C...• ...and none of us knows today what that language is
Java.* is not the End of History
▪ 1960: Fortran, Algol, Lisp (Datesapproximate)
▪ 1980: C▪ 1990: C++▪ 1995: Java, JavaScript▪ Java is showing its age• Not “agile”• Weak support for concurrency
▪ Atwood's law: "Any application that canbe written in JavaScript, will eventuallybe written in JavaScript."
The “Cambrian Explosion” of Languages
▪ Lots of programming languages comeand go:
• Ruby, Groovy• Python, PHP, Perl• Lisp, Scheme, ML, Haskell• Smalltalk• C#, F#• Matlab, R, Julia• Swift, Kotlin• Rust• Go
▪ Some are optimized for different tasks(UI, scientific programming, scripting, ...)
▪ Some aim to be your next general purpose language
What You Will Learn
▪ A functional subset of Java (instead ofScheme/ML/Haskell)
▪ The Scala combinator parser library(instead of JavaCC/Antlr/yacc)
▪ JVM code generation▪ Scheme, functional JavaScript
▪ Prolog▪ Project: Web development with alternative languages
What You Need to Succeed
Prerequisites
▪ CS151: Interfaces, inner classes,generic types
▪ CS46A/B: Unix shell, basic scripting
Time
▪ You can't expect to learn complexskills just by listening to lectures
▪ Spend 9 hours per week for this class• 2.5 hours in class
• 6.5 hours (!) outside class for preparation and homework
Laptop
▪ Bring it to each class meeting
Questions
▪ Ask questions right away when you arestuck
▪ Use the Piazza online discussiongroup.
▪ You'll get a better grade if you ask lotsof questions.
▪ Answer your peers' questions. You'llget a better grade if you do.
▪ Private or confidential questions—email or office hours
Plagiarism and Cheating
▪ When you submit work, it must be yourown personal, individual intellectualcreation
• Not that of your “study group”
▪ Can you copy and paste?• Always ok to copy• Pasting is the problem ☺
• Ok to use code from the course materials without attribution• Otherwise, you must attribute the source
▪ Don't post/email working code• You don't know what the recipient will do with it• Sending someone a solution is cheating
▪ I periodically run a plagiarism checker
• If I find cheating, I don't play Sherlock Holmes to find who wrote it—I reportboth
▪ I report everyone whom I find cheating to Student Conduct• I have to• And I will recommend that an appropriate penalty is for you to fail the course
Git
▪ You submit all class work via Git▪ Make a private repo on BitBucket or
GitHub• If you use your SJSU email address, BitBucket
gives you a free account with unlimited privaterepos
▪ Add me as a contributor to your repo.My BitBucket and GitHub ids are
cayhorstmann▪ Use a single repo for all homework and exams▪ Make folders hw1, hw2, lab1, exam1, ...▪ Commit at least once a day for homework/every 10 min in an
exam
▪ Push before midnight on due date. Grace period until 6am nextmorning. No mercy afterwards.
▪ Never used Git? Check out parts A and D in this CS46B lab.
Adding
▪ Do homework 1 and send me youranswers.
▪ I will send you an add code whenspace becomes available. Use itwithin 24 hours, or it will become invalid
▪ Show up for all classes/labs even if youhaven't received your code
Things To Do Today
▪ Log into Piazza▪ Install Java 8, Scala, and Git on your
laptop▪ If you run Windows and don't know
your way around the Windows 10subsystem for Linux, or if you runMacOS and don't know how to navigatethe difference between Linux and BSD,install VirtualBox and a Linux VM.
▪ Complete Homework 1 and send me your Git repo URL▪ Important: In lieu of a roll call today, you must turn in homework
1, or I will drop you from this course for lack of presence/prerequisites.
Lecture 1 Clicker Question 1
What happens with this code?
List<String> strings = new ArrayList<>(Arrays.asList("Harry", "Sally"));List<Object> objects = strings;objects.set(0, new Integer(42));String first = strings.get(0);
1. The code compiles and runs without errors2. The code throws an exception in line 33. The code throws an exception in line 44. The code doesn't compile
Lecture 1 Clicker Question 2
What happens with this code?
List<String> strings = new ArrayList<>(Arrays.asList("Harry", "Sally"));List objects = strings;objects.set(0, new Integer(42));String first = strings.get(0);
1. The code compiles and runs without errors2. The code throws an exception in line 33. The code throws an exception in line 44. The code doesn't compile
Lecture 1 Clicker Question 3
What happens with this code?
String[] strings = { "Harry", "Sally" };Object[] objects = strings;objects[0] = new Integer(42);String first = strings[0];
1. The code compiles and runs without errors2. The code throws an exception in line 33. The code throws an exception in line 44. The code doesn't compile
Installing Git
▪ Make an account on BitBucket. Use your SJSU email address.▪ Make a repository on BitBucket. Be sure it is a private repo. Call itcs152. Add me (cayhorstmann) as a collaborator with Write access(in Settings → Access Management → Users).
▪ Start a Bash shell.▪ Make sure you have git:
git --version
It doesn't matter what version you have. If you don't have git, use
sudo apt-get install git
▪ Clone the repo:
git clone ssh://[email protected]/yourname/cs152.git ~/cs152
Adding to the Repository
1. In the cs152 subdirectory that was created by git clone, make a subdirectoryhw1
2. In that directory, make a file aboutme.txt as described in Homework 13. Open a terminal and change to the directory into which you cloned the repo:
cd cs152
4. Type
git add hw1/aboutme.txt
5. Type
git commit -a -m "Started homework 1 "
6. Type
git push origin master
7. In the BitBucket web interface, locate hw1/aboutme.txt8. Did you find it? Hooray—you have just reached level 2.
A UML Diagram
▪ Get together with two buddies▪ Read through homework 1▪ Grab a marker pen and step up to a whiteboard▪ Draw a UML diagram that shows the relationships between
Expr<Integer>Const<Integer>Op<Integer>Function<Integer>Sum
CS 152 - Lecture 2
Cay S. Horstmann
Functional Programming
▪ Functional programming: Functions are values▪ In Java, values are
• Primitive types int, double, etc.• References to objects or arrays
▪ A method is not a “first class” value in Java• Cannot declare a variable that holds a method• Cannot create new method in a running program
▪ In a functional programming language, functions are first-classvalues
• Can have variables that hold functions• Can create new functions
Functional Programming in Scala
▪ Functions can be values
val num = 3.14val fun = math.ceil _fun(num) // prints 4
▪ Functions can be anonymous...
(x : Int) => x * x
▪ ...just like numbers
3.14
▪ Of course, can put function values into variables and then usethem
val square = (x : Int) => x * xsquare(10) // prints 100
Why Functional Programming?
▪ Simpler and clearer programming style▪ Often useful to pass functions as parameters
val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)numbers.map((x : Int) => x * x)
// Yields a new List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100)
▪ How would you write a generic map function in Java that does thesame?
▪ You can pass behavior as an interface:
public static <T> List<R> map(List<T> values, Transformer<T, R> f)
▪ Java 8 lambdas provide syntactic sugar, but they are not truefunctions.
▪ Functions have been with us for hundreds of years ofmathematics.
Scala Basics
▪ Types Int, Double, Boolean, String▪ Arithmetic like in Java: + - * / %▪ Variable type is inferred:
val luckyNumber = 13 // luckyNumber is an Int
▪ Function types:
val square = (x : Int) => x * x // square is an Int => Int
▪ Semicolons at the end of a line are optional
val x = 1val y = 2 + // end line with operator to indicate that there is more to come3
▪ Everything is an object
Immutability
▪ Immutable: Cannot change▪ In Java, strings are immutable
• "Hello".toUpper doesn't change "Hello" but returns a new string "HELLO"
▪ In Scala, val are immutable
val num = 3.14num = 1.42 // Error
▪ Pure functional programming: No mutations▪ If you call a function twice with the same inputs, you know you'll
get the same result▪ Functions that don't mutate state are inherently parallelizable▪ Important consideration in light of the end of Moore's Law
If/Else
▪ if (booleanExpression) expression1 else expression2
▪ if/else is an expression, not a statement. Can be used in otherexpressions:
val x = (if (true) 2 else 4) * 3
▪ Like ? : in Java▪ Type is the most specific supertype common to expression1,
expression2
val x = if (true) 3 else "Hello" // type is AnyVal
▪ Omitting the else yields type Unit (like void in Java); not useful infunctional programming
Recursion
▪ def syntax for functions
def triple(x : Int) = 3 * x// same as val triple = (x : Int) => 3 * x
▪ With recursive functions, also need to specify return type:
def fac(x : Int) : Int = if (x == 0) 1 else x * fac(x - 1)
▪ Need def because the name is used on the right
val fac = (x : Int) => if (x == 0) 1 else x * fac(x - 1) // fac not defined yet
▪ Iteration (while, for) can always be expressed as recursion
To iterate is human; to recurse, divine (L. Peter Deutsch)
Lab
▪ Format of classes: approx. 20 minuteslecture, 45 minutes lab, 10 minutewrap-up
▪ You work with a buddy▪ One of you (the coder) writes the code,
the other (the scribe) types up answers▪ When you get stuck, ask your buddy first!▪ Switch coder/scribe roles each lab▪ The scribe submits lab work in lab1/report.txt inside the Git
repo. Include the coder's name in the report!▪ If the coder wants to submit something, that's cool too. Do
whatever is easy.
Step 1: The Scala Interpreter
1. Start Eclipse and make a new Scala project: File -> New -> Project -> ScalaProject. Give a name lab1 and click Finish. Right-click on the project in thePackage Explorer, then select New -> Scala worksheet. Call it sheet1.
2. Type 39+3 and save the worksheet. What do you get?3. Type val a = 39 + 3. What do you get?4. Type a + 1. What do you get?5. Type a = 9. What do you get? Why?6. Type val b; (This time with a semicolon.) What do you get? Why?
Step 2: Functions are Values
1. Type val triple = (x : Int) => 3 * x. What do you get?2. Type triple(5). What do you get?
HINT: These “What do you get” exercises are a lot more effective when you andyour buddy first discuss what you think you'll get. “Form a hypothesis” is an essentialpart of the learning path.
3. Type triple. What do you get?4. What is the type of triple in Scala?5. What is the type of 5 in Scala?
Step 3: Functions as Parameters
1. Type List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10). What do you get?2. Type List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).map(triple). What do you get?
Why?3. How do you get the cubes of the numbers from 1 to 10 without using val or
def? Hint: Anonymous function.
Step 4: Simple Recursion
1. Your task is to write a function sevens(n: Int): Int that counts how manydigits of n are the digit 7. For example, sevens(747) returns 2. How would youdo this in Java (without converting the number to a string)?
2. In functional programming, you can't increment a counter. But you can userecursion. How can you compute the answer recursively? Hint: If n is 0, youknow the answer. Otherwise, in plain English or pseudocode, how can youcompute the answer from n % 10 and sevens(n / 10)?
3. What's your code in Scala?
CS 152 - Lecture 3
Cay S. Horstmann
Blocks
▪ In Scala, a block is an expression with a value.
val distance = {val dx = x2 - x1val dy = y2 - y1math.sqrt(dx * dx + dy * dy)
}
▪ The last expression in the block is the value of the block.▪ Common in complex recursions.
def digitsum(n: Int): Int = if (n == 0) 0 else {val last = n % 10val rest = n / 10last + digitsum(rest)
}
Local Functions
▪ You can define a function inside another function:
def distance(x1: Int, x2: Int, y1: Int, y2: Int) = {def sq(x: Int) = x * xmath.sqrt(sq(x2 - x1) + sq(y2 - y1))
}
▪ Useful for recursive helper functions.
def digitsum(n: Int) = {@tailrec def digitsumHelper(n: Int, sum: Int): Int = if (n == 0) sum else
digitsumHelper(n / 10, sum + n % 10)digitsumHelper(n, 0)
}
▪ @tailrec tells Scala that you believe that the recursion can berewritten as a loop.
def digitsumHelper(n: Int, sum: Int): Int = {start:
if (n == 0) sum else {n = n / 10sum = sum + n % 10goto start
}}
Lists
▪ Very different from Java linked lists. No iterators▪ Three primitives: head, tail, :: (pronounced cons)▪ A list is either empty (Nil) or has a head and tail
val lst = List(1, 4, 9)lst.head // 1lst.tail // List(4, 9)lst.tail.tail.tail // Nil
▪ Use :: to build lists
0 :: lst // List(0, 1, 4, 9)
List Functions
▪ Use recursion for list functions
def sum(lst : List[Int]) : Int =if (lst.isEmpty)
0 elselst.head + sum(lst.tail)
▪ Use :: to recursively build lists
def squares(n : Int) : List[Int] =if (n == 0)
List(0) elsen * n :: squares(n - 1)
Why This Isn't Inefficient
▪ lst.tail doesn't make a new list—it is a reference to the tail cell
▪ Works because lists are immutable
More Recursion Examples
▪ def append(a: List[Int], b: List[Int]): List[Int] = if (a.isEmpty) b elsea.head :: append(a.tail, b)
▪ def reverse(a: List[Int]): List[Int] = if (a.isEmpty) a elseappend(reverse(a.tail), List(a.head))
▪ reverse((1 to 1000000).toList)(1 to 1000000).toList.reverse
▪ def reverse(a: List[Int]) = {@tailrec def reverseHelper(a: List[Int], reversed: List[Int]): List[Int] =
if (a.isEmpty) reversed else reverseHelper(a.tail, a.head :: reversed)reverseHelper(a, Nil)
}
Lab
▪ You work with a buddy▪ One of you writes the code (coder), the
other types up answers (scribe)▪ When you get stuck, ask your buddy
first!▪ Switch roles each lab. The previous
scribe is the coder for this lab.▪ The scribe submits lab work in lab2/report.txt inside the Git
repo. Include the coder's name in the report!▪ If the coder wants to submit something, that's cool too. Do
whatever is easy.
Step 1: Lists
1. What is the type of 1 :: 2 :: 3 :: Nil? (Just ask the Scala interpreter)2. What is the type of 1 :: "Hello" :: Nil?3. What happens when you evaluate the expression 1 :: 2 :: 3? Why?4. How do you make a list of the elements "San", "José", "State", "University"
using a list constructor? (Be sure to try your answer in the Scala interpreter)5. How do you make a list of the elements "San", "José", "State",
"University" using the cons (::) operator?
Step 2: List Functions
1. Write a recursive function concat that concatenates all strings in aList[String], yielding a String. Hint: (1) String concatenation is + in Scala,just like in Java (2) concat(Nil) is "". (3) Think about concat(lst) in terms oflst.head, lst.tail.
Give the code of your function.
2. What is the result of concat(List("San", "José", "State", "University"))?3. How can you modify concat so that it adds spaces between the strings (i.e.
so that concat(List("San", "José", "State", "University")) is "San JoséState University" but not "San José State University " or " San José StateUniversity"?
Step 3: More Recursion
1. Given a list, form a list of all pairs of elements (as lists of length 2) in some
order. If the original list has length n, form n2 pairs. What shouldpairs(List(1, 2, 3)) produce?
2. What is your strategy for recursion? Base case? Reduction to simpler case?3. What is your Scala code?
Step 4: Not Always Recursion
1. How can you implement the squares function from the lecture withoutrecursion?
2. The flatten method flattens out a list of lists into a list. To see what it does,just try out
List(List(1, 2),List(3, 4, 5)).flatten
Assuming that you are allowed to use this method, how can you implementthe pairs function of Step 3 without recursion?
CS 152 - Lecture 4
Cay S. Horstmann
Functions as Parameters
▪ Consider the map function:
val triple = (x : Int) => 3 * x(1 to 10).map(triple)// yields 3 6 9 12 ... 30
▪ Let's implement such a function. For simplicity, we only use setsand functions of Int
▪ Two parameters: List[Int], function (Int) => Int, returnsList[Int]
def map(lst : List[Int], fun: (Int) => Int) : List[Int] =
▪ Map of empty list is Nil, otherwise apply fun to lst.head and userecursion:
if (lst.isEmpty)Nil elsefun(lst.head) :: map(lst.tail, fun)
▪ Sample call:
map(List(1, 2, 3), (x : Int) => 3 * x)
▪ A function describes a piece of behavior, such as
What should map do with each element in lst?
Capturing the Enclosing Environment
▪ Consider this function:
val n = 3val fun = (x : Int) => n * x// What is fun(2)?
▪ n is not defined in the scope of fun, but that is ok. In the body of afunction, you can use any variable from the enclosing scope.
▪ Doesn't seem a big deal—n is immutable, so it will always be 3.But consider this:
def multiplyBy(n : Int) = (x : Int) => n * x
▪ Huh? Let's call it:
val quadruple = multiplyBy(4) // the function (x : Int) => 4 * x
▪ And let's call that:
quadruple(5) // yields 20
▪ Each call to multiplyBy yields a different function.▪ Each function has a different value of n▪ Closure = function + binding of its free variables (i.e the variables
that are not defined locally in the function)
Parameter Inference From Context
▪ When a function parameter type is known, you can supply ananonymous function without specifying its parameter types
def twice(f: (Int) => Int, x : Int) = f(f(x))twice((x) => 42 * x, 3)
// Ok, x : Int is inferred from context
▪ Very useful when calling library functions
List(1, 2, 3).map((x) => x * x)
• List[A].map(f : (A) => B) : List[B]• A is Int since List(1, 2, 3) is a List[Int]• f must be (Int)=> . . .• x must be Int
Parameter Simplifications
▪ Ok to omit () around a single inferred parameter
List(1, 2, 3).map(x => x * 0.5)List(1, 2, 3).sortWith((x, y) => x > y)
// need () with 2 or more parameters, or with 0 parameters
▪ Use _ for a parameter that only occurs once in the body
List(1, 2, 3).map(_ * 0.5)List(1, 2, 3).sortWith(_ > _)
▪ The _ can't be in an expression that is passed to another function.
List(1, 2, 3).map(math.sqrt(_ + 1)) // Error—Can't pass _ + 1 to math.sqrtList(1, 2, 3).map(math.sqrt(_) + 1) // Ok
Reductions
▪ map produces a list of values.▪ What if we want a single value?▪ In the previous lecture, we wrote
def sum(List[Int] lst): Int
▪ What about product? Max? Min?▪ The combining function should be an argument:
def reduce(lst: List[Int], op: (Int, Int) => Int): Int
▪ Call as
val result = reduce(lst, (x, y) => x + y)
▪ Or with anonymous variables:
val result = reduce(lst, _ + _)
▪ Implementation:
def reduce(lst: List[Int], op: (Int, Int) => Int): Int =if (lst.tail.isEmpty) lst.head elseop(lst.head, reduce(lst.tail, op))
Lab
▪ You work with a buddy▪ One of you writes the code (coder), the
other types up answers (scribe)▪ When you get stuck, ask your buddy
first!▪ Switch roles each lab. The previous
scribe is the coder for this lab.▪ The scribe submits lab work in lab3/report.txt inside the Git
repo. Include the coder's name in the report!▪ If the coder wants to submit something, that's cool too. Do
whatever is easy.
Step 1: Filters
1. What does the following function do? If in doubt, call it with a few values.
val isEven = (x : Int) => x % 2 == 0
2. Type (1 to 10).filter(isEven). As always, don't type the period, but typeENTER. What do you get?
3. Describe what filter does.
Step 2: A Random Number List
1. The random number generator in Scala is similar to that in Java. Whatoutput do you get from
val gen = new scala.util.Randomgen.nextInt(10)gen.nextInt(10)
2. What is the significance of the 10?3. We want to define a function randList(len : Int, n : Int) : List[Int] that
makes a list of length len of random integers between 0 and n - 1. Forexample, randList(5, 10) might yield a list of numbers 5 1 2 0 9. DefinerandList as a recursive function. What is the code of your function?
Hint: If len is 0, the result is nil. Otherwise, it is gen.nextInt(n) ::something. What is your definition?
Note: You need not define gen. You already defined it in part 1. Just useit.
Step 3: Filtering Large Numbers
1. Write a function greaterThan100(lst : List[Int]) that returns only thoseintegers in lst that are greater than 100. Don't use recursion; simply callfilter with an appropriate function:
def greaterThan100(lst : List[Int]) = {val fun = ... // your worklst.filter(fun) // NOTE: The last expression in a { ... } is its value
}
What is your function's code?
2. What is the value of greaterThan100(randList(10, 200))? Why does that giveyou confidence that you implemented everything correctly?
3. Of course, having a hard-wired bound of 100 is intensely ugly. Let'sgeneralize:
def greaterThan(n : Int, lst : List[Int]) = {val fun = ... // your worklst.filter(fun)
}
For example, greaterThan(50, nums) yields all values of nums > 50.
What is the code of your greaterThan function?
4. What is the value of greaterThan(100, randList(10, 200))?5. Why is the fun inside greaterThan a closure?
Step 4: Reduce
1. What is reduce(List(1,2,3,4,5), (x, y) => x - y)?2. Scala has two forms of reduce called reduceLeft and reduceRight. Try
(1 to 5).reduceLeft(_ - _)(1 to 5).reduceRight(_ - _)
What do each of them do?3. Given a list of digits, pick one of the two forms of reduce to compute the
decimal value. For example, List(1, 7, 2, 9) should turn into 1729. Hint:(x, y) => 10 * x + y.
4. We have implemented one of the forms of reduce in the lecture. Which one?Implement the other. Here is an outline:
def otherReduce(lst: List[Int], op: (Int, Int) => Int) = {def otherReduceHelper(lst: List[Int], op: (Int, Int) => Int, partialResult: Int): Int =
if (lst.isEmpty) ... elseotherReduceHelper(..., op, op(..., ...))
otherReduceHelper(..., op, ...)}
CS 152 - Lecture 5
Cay S. Horstmann
Higher Order Functions
▪ A higher order function consumes or produces another function.▪ Example of consumption: map
(1 to 10).map(x => x * x)
▪ Why would you want to produce a function?▪ And how?
Producing Functions
▪ We can define a function
def triple(x: Int) = 3 * x
▪ Then we can use it:
(1 to 10).map(triple)
▪ Here is another:
def quadruple(x: Int) = 4 * x(1 to 10).map(quadruple)
▪ What about a generic “multiply by n” function so that I can write:
(1 to 10).map(multiplyBy(5))
Producing Functions
▪ multiplyBy(5) needs to produce a function...▪ of type Int => Int:
def multiplyBy(n: Int) : Int => Int = ...
▪ It needs to produce a function that consumes an integer:
... = (x: Int) => ...
▪ and the result is n * x.▪ Now all together:
def multiplyBy(n: Int) : Int => Int = (x: Int) => n * x
▪ The return type is inferred:
def multiplyBy(n: Int) = (x: Int) => n * x
Comparators
▪ Comparators used for sorting:
List(1, 2, 3).sortWith(_ > _)
▪ Want to turn a discriminator function into a comparator:
strings.sortWith(comparingBy(s => s.length())
▪ What does comparingBy produce?• A function (String, String) => Boolean
▪ What does comparingBy consume?• A function String => Integer
▪ def comparingBy(f: String => Integer) =(x: String, y: String) => ... // A Boolean
▪ The rest is simple:
Composing Comparators
▪ Preparation: Make comparingBy generic.
def comparingBy[T, R : Ordering](f: T => R) = { // Don't worry about the detailsimport scala.Ordered.orderingToOrdered(x: T, y: T) => f(x) < f(y)
}
▪ Now it works for a map Person => String:
case class Person(first: String, last: String)val people = List(Person("Fred", "Flintstone"), Person("Fred", "Brooks"), Person("Barney", "Rubble"))people.sortWith(comparingBy(_.first))
▪ Now we want to compare by first name, using the last name tobreak ties:
people.sortWith(breakingTies(comparingBy(_.first), comparingBy(_.last)))
Composing Comparators
▪ What does breakingTies produce?▪ A function (Person, Person) => Boolean▪ What does breakingTies consume?▪ Two functions (Person, Person) => Boolean
▪ def breakingTies(c1: (Person, Person) => Boolean, c2: (Person, Person) => Boolean) =(x: Person, y: Person) => ... // A Boolean
▪ => if (c1(x, y)) true elseif (c1(y, x)) false elsec2(x, y)
Currying
▪ Currying = Turning a function that takestwo arguments into a function thattakes one argument. That functionreturns a function that consumes thesecond argument. (Named after thelogician Haskell Brooks Curry)
▪ Huh?▪ Simple example:
def multiply(x : Int, y : Int) = x * ymultiply(3, 4) is 12
▪ Now let's do this a little different:
def multiply(x : Int)(y : Int) = x * ymultiply(3)(4) is 12
▪ What is multiply(3)?▪ A function that is capable of eating 4, thus yielding 12:
def multiply(x: Int) = (y: Int) => x * y // That's our multiplyBy
▪ Can make higher-order functions easier on the eye:
def comparingBy(f: String => Integer) = (x: String, y: String) => f(x) < f(y)def comparingBy(f: String => Integer)(x: String, y: String) = f(x) < f(y)
▪ Similar to the rewrite of a val to a def:
val triple = (x: Int) => 3 * xdef triple(x: Int) = 3 * x
Lab
▪ You work with a buddy▪ One of you writes the code (coder), the
other types up answers (scribe)▪ When you get stuck, ask your buddy
first!▪ Switch roles each lab. The previous
scribe is the coder for this lab.▪ The scribe submits lab work in lab4/report.txt inside the Git
repo. Include the coder's name in the report!▪ If the coder wants to submit something, that's cool too. Do
whatever is easy.
Step 1: Short Function Notation
Define a variable lst as
val lst = List("Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot", "Golf")
What is the shortest command you can use to
1. Yield all short strings (of length < 5) from lst2. Yield the words sorted by increasing length3. Find the longest string (using reduce)
Step 2: Currying
▪ Write a function makeMin that consumes a comparator (a function(String, String) => Boolean) and produces a function (a:String, b: String) => String that computes the smaller of thetwo inputs, as measured by the comparator.
▪ Did you use Currying? If so, pat yourself on the back. Now do itthe other way around. Or if you did it the other way around, do itwith currying.
▪ Make your function generic. Just replace String with T and stick a[T] right after the function name. Don't worry—it won't hurt a bit.
Step 3: More Currying
1. Here is a function of computing the “maximum” string in a list that works forany ordering.
def max(lst : List[String], less : (String, String) => Boolean) =lst.reduce((x, y) => if (less(x, y)) y else x)
Make a call to max that yields the longest string in a list lst. Use _ for thestring parameters in your less function.
2. Now make this generic. Don't worry—it won't hurt a bit:
def max[T](lst : List[T], less : (T, T) => Boolean) =lst.reduce((x, y) => if (less(x, y)) y else x)
What happens when you call max(lst, _ < _)?
3. Ok, that didn't work so well. Currying to the rescue. Curry the max[T]function, exactly like multiply above. What is the code for your revisedfunction? What happens when you call max(lst)(_ < _)?
(Why does this work? Now the Scala type inferencer knows that T mustbe String after processing max(lst).)
CS 152 - Lecture 6
Cay S. Horstmann
Scala Documentation
▪ Online at http://www.scala-lang.org/api/2.12.4▪ Bookmark it right now!▪ Use text field in upper right corner to filter classes
Categories of List Methods
▪ Basic methods length head tail isEmpty▪ Operators :: :+ ++ == != /: :\▪ Access by position (n) take drop dropRight slice indexOflastIndexOf
▪ Methods without parameters reverse sorted sum max min▪ Methods with unary predicates count exists dropWhile filterfind findIndexOf forall partition remove span takeWhile
▪ Methods with unary function map reverseMap flatMap foreach▪ Methods with binary predicate sort▪ Methods with binary function reduceLeft reduceRight foldLeftfoldRight
▪ Other intersection union zip zipAll zipWithIndex mkString
List Operators
▪ :: appends in front, :+ in back
3 :: List(1, 2) is List(3, 1, 2)List(1, 2) :+ 3 is List(1, 2, 3)
▪ ++ concatenate lists
List(1, 2) ++ List(3, 4) is List(1, 2, 3, 4) // same as :::
▪ == and != compare lists
List("Hello", "World") == List("Hel" + "lo", "Wor" + "ld")
▪ /: and :\ later
Operator Precedence and Associativity
▪ On this calculator, what do you getwhen you type 1 + 2 * 3?
▪ What do you get in C++?▪ Precedence: Which operator is
executed first?
(1 + 2) * 31 + (2 * 3)
▪ Associativity: If the same operatoroccurs twice, is the left or right one executed first? Does 1 - 2 -3 mean
(1 - 2) - 31 - (2 - 3)
Scala Operators
▪ Function names can be any sequence of opchars: charactersother than whitespace, A-Z0-9()[]{}`'".,;
▪ Operator precedence depends on first character
(all letters)|^&< >= !:+ -* / %(all other special characters)
▪ Operators ending in : are right associative; all others are leftassociative
Lists: Access by Position
▪ Not very efficient for linked lists▪ Index values are 0-based▪ () are used for indexed access—not []
List(17, 29)(1) is 29
▪ slice takes sublist
List(2, 3, 5, 7).slice(1, 3) is List(3, 5)
Arguments to slice(from, to):
• from is the first index to include• to is the first index ≥ from to exclude
▪ See lab for drop take
Methods with Function Parameters
▪ Workhorse functions of the library▪ You already saw map, filter, reduce▪ Others explored in lab▪ Instead of looping, build a function and pass it to one of these
methods
def randList(len : Int, n : Int) =(1 to len).map((x : Int) => gen.nextInt(n))
▪ Some methods return a pair
List(2,3,5,7).partition(isEven) is (List(2),List(3, 5, 7))
Tuples
▪ Heterogeneous sequence of elements
val myFirstTuple = (1, 3.14, "Fred")
▪ Tuple type (Int, Double, String)▪ Access components with methods _1 (!), _2, _3
val second = myFirstTuple._2
▪ Or with destructuring
val (first, second, _) = myFirstTuple
Maps
▪ If I were stranded on a desert islandand could only take one data structurewith me, it would be the hash table. —Peter van der Linden
▪ Construct a map
val scores = Map("Alice" -> 10, "Bob" -> 3, "Cindy" -> 8)
▪ -> operator makes pairs. "Alice" -> 10 is the same as ("Alice",10)
▪ Access element
val alicesScore = scores("Alice")// They don't call it a map for nothing
val fredsScore = scores.getOrElse("Fred", 0)
▪ Update map (i.e. get new map with updates)
val newScores = scores + ("Bob" -> 10, "Fred" -> 7)val newerScores = newScores - "Alice" // Remove key and value
flatMap• Given an integer sequence s, it is easy to form all pairs (x, 0):
val s = (1 to 4).toLists.map(x => (x, 0))
// List((1, 0), (2, 0), (3, 0), (4, 0))
• What if we want to have all pairs (x, y) where x, y are elements of s?
• Try it by calling map twice:
s.map(y => s.map(x => (x, y)))// List(List((1,1), (2,1), (3,1), (4,1)),// List((1,2), (2,2), (3,2), (4,2)),// List((1,3), (2,3), (3,3), (4,3)),// List((1,4), (2,4), (3,4), (4,4)))
• Close, but not quite. To “flatten out” the result, use flatMap instead:
s.flatMap(y => s.map(x => (x, y)))// List((1, 1), (1, 2), (1, 3), (1, 4), (2, 1), (2, 2), ..., (4, 4))
Folding
▪ The sum of the elements in List(a, b, c) is
a + b + c = 0 + a + b + c = ((0 + a) + b) + c
▪ Use foldLeft or the /: operator
def sum(lst: List[Int]) = (0 /: lst) ((x, y) => x + y)
▪ The /: indicates the tree shape
a+b+c/ \
a+b c/ \
a b/ \
0 a
▪ The first argument of the folding function is the partial answer; thesecond is the new list value to be considered
▪ To recurse may be divine, but not to recurse is even better:
def fac(n : Int) = (1 /: (1 to n)) {_ * _}
▪ The foldRight operator works right-to-left: a + (b + (c + 0))
mkString▪ toString produces the familiar List(1, 2, 3)▪ What if we want 1 | 2 | 3?
lst.mkString("|")
One fewer separator than elements
▪ What if we want [1 | 2 | 3]?
lst.mkString("[", "|", "]")
▪ Nice attention to detail by the library designers
Lab
▪ You work with a buddy▪ One of you writes the code (coder), the
other types up answers (scribe)▪ When you get stuck, ask your buddy
first!▪ Switch roles each lab. The previous
scribe is the coder for this lab.▪ The scribe submits lab work in lab5/report.txt inside the Git
repo. Include the coder's name in the report!▪ If the coder wants to submit something, that's cool too. Do
whatever is easy.
Step 1: Warmup
1. What do take and drop do? Give a brief explanation and an example foreach.
2. What is the difference between take and dropRight?3. Look up the definition of span in Scaladoc. Make an example that
demonstrates how span works. What is your example, and what value does itproduce?
4. The span method returns a pair. Show how you can get at each of theelements in that pair.
Step 2: Folding
1. Use the /: folding operator to concatenate all strings in a List[String],separating them with spaces. For example, if you start with val lst =List("Hello", "Scala", "World"), you should produce an expressioninvolving lst and /: that yields "Hello Scala World". (Hint: It is very easy toget " Hello Scala World". The challenge is to get rid of the first space.
2. Folding is useful for much more than computing sums and products. Manyalgorithms that compute a value by making a loop through an array can beobtained with a suitable function whose first argument is the result from theelements that you have seen so far, and whose second argument is the nextlist element.
Consider the case of computing the maximum. Find a suitable functionwhose first argument is the maximum of all elements visited so far, andwhose second argument is the next element.
What the code for your function maximum(lst : List[Int]) : Int? (Youmay assume that the list has length > 0)
3. What does this function do?
def mystery(lst : List[Int]) =(List[Int]() /: lst) ((x, y) => if (y % 2 == 0) x else x :+ y)
Explain how the function works.
Step 3. A Puzzle
Here is a puzzle from a programmer web site:
Given an array of numbers, return the occurances [sic] of the number 1 in the array. So, if thearray contains 1, 2, 11, 13 you would return 4 (as 11 contains two instances of 1).
a. Suppose you are given a list of numbers, such as List(1, 2, 11, 13). To getdigits of one, it would be better to have strings. How do you get an array ofstrings? Hint: toString, map
b. Now look at an individual string such as "1811". How can one count thenumber of ones? Hint: (1) Strings are collections of characters (2) Try thecount method that takes a predicate. For example, (1 to 10).count(_ % 3 ==0) yields 3
c. Ok, now you need to apply that to each of the strings. How do you do that?(Hint: map)
d. That gives you a collection of counts. How do you total them up? Hint:reduce, or, if you prefer, look at the scaladoc of Seq for a simpler way.)
Step 4: Misery with Tuples
1. What is 1 -> 2 -> 3? What is the type of the result?2. Write a function flatten that takes such a thing and turns it into an (Int,
Int, Int). Use _1, _23. Repeat with destructuring.4. What is the result of the following statement, and why?
val x,y,z = (1,2,3)
Step 5: If I Had Just One Data Structure...
1. And if I could just take one method, it would be groupBy. Load all words froma dictionary into a list by calling
val words = io.Source.fromURL("http://horstmann.com/sjsu/spring2018/cs152/words").getLines.toList
What is words.groupBy(w => w.length)?2. Make a map so that myMap('a') yields all words that start with the letter a and
so on.3. How many words start with a given letter? Hint: myMap.map(...)4. Extra credit: Turn this into a list of tuples that is sorted by frequency. Hint:
sorted doesn't work because Scala doesn't know how to sort the tuples.There are two other sort methods. One of them wants a function that mapsinto something that Scala does know how to sort—the easy choice forsolving this exercise.
5. Extra credit: Now do it with the other method (and some tuple misery).
CS 152 - Lecture 7
Cay S. Horstmann
Language Translation Process
▪ Input: Source code▪ Lexical Analysis: Group input characters into reserved words,
identifiers, constants, operators, comments▪ Parsing: Build data representation of program, report syntax
errors▪ Code generation: Use data representation to generate, optimize
code▪ Output: Machine or VM code▪ Interpreter directly executes the parsed program
Lexical Analysis
▪ Remove white space▪ Remove comments▪ Break input into tokens▪ if ( radix >= 0.0) y = sqrt(radix)▪ Each token has type (reserved word, identifier, constant,
operator, etc.) and text▪ Use regular expressions to define token types (see below)▪ Process header files (C/C++)
Notes on White Space
▪ Free form language: All white space is optional▪ Line-oriented language: Line endings are significant. Ex.: Fortran▪ Scala is in the middle: Newline is a token and can be equivalent
to a ;▪ Python: Indentation indicates block structure
if radix >= 0:y = sqrt(radix)print(y)
else:print('error')
Parsing
▪ Translates stream of tokens into data structure▪ Parse tree for expression 3 + 4 * x
▪ Higher-level features have more complex structure
Ex. function has
• name• return type• list of parameters• body
Body has
• list of expressions (Scala)/statements (Java)
Code Generation
▪ Parse tree translated into machine or VM instructions▪ Example:
movl $4, %eaxmull x, %eaxaddl $3, %eax
Code Optimization
▪ Register allocation
result = (a + b) * (c + d)
movl a, %eaxaddl b, %eax
movl c, %ebxaddl b, %ebx
mull %ebx, %eaxmovl %eax, result
▪ Loop optimization
for (i = 0; i < n; i++) {t = u * v;a[i] = t + i;
}
▪ Inline functions
int max(int x, int y) { return x > y ? x : y; }
Translate max(x, 0) into x > 0 ? x : 0, avoid cost of functioncall
▪ Many others
Regular Expressions
▪ Describe “regular” sets of strings▪ Symbols other than ( ) | * stand for themselves▪ Concatenation α β: First part matches α, second part β▪ α | β = Match α or β▪ α* = 0 or more matches of α▪ Use () for grouping▪ Example: E(0|1|2|3|4|5|6|7|8|9)*
An E followed by a (possibly empty) sequence of digits
E123E9E
Convenience Notation
▪ α+ = one or more (i.e. αα*)▪ α? = 0 or 1 (i.e. (|α))▪ [xyz] = x|y|z▪ [x-y] = all characters from x to y, e.g. [0-9] = all ASCII digits▪ [^x-y] = all characters other than [x-y]▪ \p{Name}, where Name is a Unicode category (ex. L, N, Z for
letter, number, space)▪ \P{Name}: complement of \p{Name}▪ . matches any character▪ \ is an escape. For example, \. is a period, \\ a backslash
Regex Examples
▪ Integers: [+-]?[0-9]+, or maybe [+-]?\p{N}+• Note: + loses its normal meaning inside [], and a - just before ] denotes itself
▪ Hexadecimal numbers 0[Xx][0-9A-Fa-f]+▪ Quoted Java strings: ".*"▪ Well, actually not; the . will match a quote. Better: "[^"]*"▪ Well, actually not; you can have a \" in a quoted string."([^"\\]|\\.)*"
▪ Are we done yet? What about \x1B?
The Scala Regex Library
▪ Find all matches
import scala.util.matching._val regex = "[0-9]+".rregex.findAllIn("99 bottles, 98 bottles").toListList[String] = List(99, 98)
Check whether beginning matches
regex.findPrefixOf("99 bottles, 98 bottles").getOrElse(null)String = 99
▪ Groups
val regex = "([0-9]+) bottles".rval matches = regex.findAllIn("99 bottles, 98 bottles, 97 cans").matchData.toListmatches : List[Regex.Match] = List(99 bottles, 98 bottles)
Lab
▪ You work with a buddy▪ One of you writes the code (coder), the
other types up answers (scribe)▪ When you get stuck, ask your buddy
first!▪ Switch roles each lab. The previous
scribe is the coder for this lab.▪ The scribe submits lab work in lab6/report.txt inside the Git
repo. Include the coder's name in the report!▪ If the coder wants to submit something, that's cool too. Do
whatever is easy.
Step 1: Regular Expressions
Write down regular expressions for
▪ Java floating-point literals▪ Java character constants▪ C-style comments, i.e. /* ... */
Step 2: Check Your Regexes
Use the Scala Regex.findAllIn method to check your work from Step 1 with thestrings
▪ "3.14 -3.14 +3.14 3. 3 .3 3.14E2 3.14E+2 3.14E-2 3.14e100"
▪ "'a' '\\n' '\\\\' '\\'' '\\x1b' '\\033'"
▪ "/* foo */ /** foo */ /**/**/**/ /*/*/*/"
What results do you get?
Tip: In Java, it is unpleasant to deal with strings containing backslashes andquotes. For example, the regular expression ([^"\\]|\\.)*, as a Java string, is"([^\"\\\\]|\\\\.)*" Scala has an alternate way of specifying strings. Whenstrings are enclosed in """...""", nothing inside is escaped. (Of course, you can'thave a """ inside.) For example, """([^"\\]|\\.)*""".
Step 3: A Simple Lexer
In a lexer, we specify a set of patterns that are tried in sequence. For example, asimple language might have the following token types:
▪ Reserved words: if|def|val▪ Identifiers: \p{L}(\p{L}|\p{N}|_)*▪ Literals: [+-]?\p{N}+▪ Operators: [+*/%<=>-]▪ Syntactical marks: [(){};]▪ White space: \p{Z}+
Note that the order matters. We want if, def, val recognized as reserved words,not identifiers.
In this step, write a function firstMatch(input : String, patterns : List[Regex]):String that returns the first match in the input string for any of the regularexpressions or null if there is no match. For example,
val patterns = List("if|def|val".r, """\p{L}(\p{L}|\p{N}|_)*""".r,"""[+-]?\p{N}+""".r, "[+*/%<=>-]".r, "[(){};]".r, """\p{Z}+""".r
)val input = "if(x<0) 0 else root(x);"firstMatch(input, patterns)String : iffirstMatch(input.substring(2), patterns)String : (
What is the code for your function?
Hint: This is simple recursion. If the first regex matches, return the match,otherwise call firstMatch(input, patterns.tail).
Step 4: Complete the Lexer
Write a function tokens(input : String, patterns : List[Regex]) : List[String]that returns a list of matching tokens. For example,
tokens(input, patterns)List[String] = List(if, (, x, <, 0, ), , 0, , else, , root, (, x, ), ;, )
That's again simple recursion. If the input is empty, return the empty list.Otherwise, get the first match. If it's null, return the empty list. Otherwise,recursively call tokens(input.substring(first.length), patterns).
What is the code of your function?
CS 152 - Lecture 8
Cay S. Horstmann
Scala Classes
▪ A simple class:
class Point(val x: Double, val y: Double) {def move(dx: Double, dy: Double) = new Point(x + dx, y + dy)def distanceFromOrigin = math.sqrt(x * x + y * y)override def toString = "(" + x + ", " + y + ")"
}
▪ Constructor with two arguments.
val myFirstPoint = new Point(3, 4)
▪ Five methods: x, y, move, distanceFromOrigin, toString▪ Need override when overriding methods (here Object.toString)▪ No () for parameterless accessor methods
Construction Parameters
▪ val parameter gives rise to instance variable and accessor
Point(val x, val y)
▪ Parameter without val gives rise to instance variable withoutaccessor.
class Rand(gen: java.util.Random) { // No valdef value = gen.nextInt()
}
▪ Can also have private instance variable
class Rand {private val gen = new java.util.Random(42)def value = gen.nextInt()
}
▪ Calling superclass constructor:
Scala Objects
▪ Scala object = class with only one instance▪ Extend the App trait (similar to an interface) for an application
object:
object Main extends App {println("My first Scala App")
}
▪ A class can have a companion object with methods that aresimilar to static methods in Java. Examples:
List.tabulateSource.fromURL
▪ In Scaladoc, click on the circle with C or T to switch to thecompanion object, denoted with O
Case Classes
▪ Special kind of class, optimized for matching
case class ClassName(field1 : Type1, field2 : Type2, ...) extends Superclass
▪ Example: Binary tree with values only in the leaves
abstract class SimpleTreecase class Leaf(value : Int) extends SimpleTreecase class Node(left : SimpleTree, right : SimpleTree) extends SimpleTree
▪ Construct instances with ClassName(arg1, arg2, ...)
Node(Node(Leaf(3), Leaf(2)), Node(Leaf(7), Node(Leaf(6), Leaf(8))))
Pattern Matching
▪ selectorExpr match {case pattern => expr...case pattern => expr
}
▪ Many possibilities for the patterns; we care about case classes
▪ tree match {case Node(l, r) => expr1case Leaf(v) => expr2
}
▪ Variables l, r, v are bound to the values in the case classinstances; you can use them in the expressions.
def sum(t : SimpleTree) : Int = t match {case Node(l, r) => sum(l) + sum(r)
case Leaf(v) => v}
▪ Matches are tried in order. To have a default, use the “wildcard”pattern at the end
case _
Syntax Trees
▪ Tree that represents the syntax of a program (or a program part)▪ Example: Expression tree for 3 + 4 * x
▪ Model in Scala:
class Exprcase class Number(value : Int) extends Exprcase class Variable(name : String) extends Exprcase class Operator(left : Expr, right : Expr,
f: (Int, Int) => Int) extends Expr
Operator(Number(3), Operator(Number(4), Variable("x"), _ * _), _ + _)
▪ Next lecture: How to parse 3 + 4 * x into Scala value
Syntax Tree Evaluation
▪ Compute value of expression▪ Need values of free variables▪ Use a Map[String, Int]▪ Scala immutable map refresher:
• Map(key1 -> value1, key2 -> value2, ...) yields map with given key/valuepairs
• map + (key -> value) yields map with new key/value pair• map(key) yields value of key (must exist)• map.get(key) yields Option, either Some(value)or None
▪ def eval(expr : Expr, symbols : Map[String, Int]) : Int =expr match {
case Number(num) => numcase Variable(name) => symbols(name)case Operator(left, right, f) => f(eval(left, symbols), eval(right, symbols))
}
Functions with Variable Arguments
▪ Want to call a function as:
val result = sum(1, 7, 2, 9)val result2 = sum(3, 1, 4, 1, 5, 9, 2, 6)
▪ Define the argument type as Int*
def sum(args: Int*) = args.reduce(_ + _)
▪ Argument is wrapped into a Seq[Int]▪ If you already have a Seq[Int], you cannot pass it directly.▪ Use : _* ascription:
val s = sum(1 to 5: _*) // Consider 1 to 5 as an argument sequence
▪ Necessary in recursive definitions:
def recursiveSum(args: Int*) : Int = {if (args.length == 0) 0else args.head + recursiveSum(args.tail : _*)
}
Lab
▪ You work with a buddy▪ One of you writes the code (coder), the
other types up answers (scribe)▪ When you get stuck, ask your buddy
first!▪ Switch roles each lab. The previous
scribe is the coder for this lab.▪ The scribe submits lab work in lab7/report.txt inside the Git
repo. Include the coder's name in the report!▪ If the coder wants to submit something, that's cool too. Do
whatever is easy.
Step 1
Add the classes Expr, Number, Variable, Operator to a Main.scala file, inside a Mainobject. Add the eval method to the Main object. In the body of the Main object,construct the tree
print it and and evaluate it with a symbol table in which x is 5. What is the code ofyour Main.scala file?
Step 2
Now we want to process variable definitions (which we will write as val x = expr inthe next lecture). For now, we will assume that they are already parsed intoinstances of
case class Definition(name : String, expr : Expr)
For example, a definition val x = 2 would be a Definition("x", Number(2)). Adefinition changes the symbol table. In a functional setting, that means we need toreturn a new table that contains all bindings in the old table and the new binding.For example,
val def1 = Definition("x", Number(2))val def2 = Definition("y", Variable("x"))val sym0 = Map[String, Int]()val sym1 = eval(def1, sym0) // "x" -> 2val sym2 = eval(def2, sym1) // "x" -> 2, "y" -> 2System.out.println(sym2)
Implement this eval method. What is the code of your method? (You can get thefields of a definition as defi.name, defi.expr.)
Note: This eval method requires you to call the eval method that we defined forexpressions, but it is a different method—it consumes a definition and a symboltable, yielding another symbol table.
CS 152 - Lecture 9
Cay S. Horstmann
Context-Free Grammar
▪ Language: Set of valid token sequences▪ Terminal symbol: Token in the language. Ex: " class" "3.14" "+"▪ Nonterminal symbol: Symbols in the grammar. Ex. expression▪ Production: Rule for replacing a nonterminal by a sequence of
nonterminals and terminals
term ::= factor "*" term
▪ Can have more than one production for the same nonterminal
term ::= factor
▪ Derivation: Sequence of productions
term ::= factor "*" term::= factor "*" factor "*" term::= factor "*" factor "*" factor
▪ Not the same as an expression tree!▪ Parser: Component that follows derivation process, yielding some
actions▪ Most common actions: Building an expression tree. (Next lecture)
Grammar Example: List of Numbers
▪ Want to generate
(1)(1, 24, 4)()
▪ list ::= "(" contents ")"
▪ contents ::=contents ::= numbercontents ::= number "," contents
▪ No, that's wrong. We could derive (1, 24, )
Grammar Example: List of Numbers
▪ Try again
▪ list ::= "(" contents ")"list ::= "(" ")"
▪ contents ::= numbercontents ::= number "," contents
▪ Easy to see by induction that contents yields n numbersseparated by n - 1 commas
Extended BNF
▪ Backus-Naur form: Original syntax for grammar rules in Algol-60▪ Extended by conveniences for frequently recurring constructs▪ Alternatives A | B▪ Repetitions
• 0 or more (A)* or {A}• 1 or more (A)+• 0 or 1 (A)? or [A]
▪ For example,
list ::= "(" ( number ( "," number )* )? ")"
Parser Generators
▪ Parser generator: Produces a parser from a given grammar▪ Works in conjunction with lexical analyzer▪ Examples: yacc, Antlr, JavaCC▪ Programmer specifies actions to be taken during parsing▪ Far better than writing a parser by hand▪ No free lunch: need to know parsing theory to build efficient
parsers▪ Scala has a built-in “combinator parser”
Combinator Parser
▪ Each nonterminal becomes a function▪ Terminals (strings) and nonterminals (functions) are combined
with operators• Sequence ~• Alternative |• 0 or more rep(...)• 0 or 1 opt(...)
class SimpleLanguageParser extends JavaTokenParsers {def expr: Parser[Any] = term ~ opt(("+" | "-") ~ expr)def term: Parser[Any] = factor ~ opt(("*" | "/" ) ~ term)def factor: Parser[Any] = wholeNumber | "(" ~ expr ~ ")"
}
▪ Will replace Parser[Any] with something more useful later
Combinator Parser Result
▪ String returns itself▪ opt(P) returns Option: Some of the result of P, or None (see next
slide)▪ rep(P) returns List of the results of P▪ P ~ Q returns instance of class ~ (similar to a pair)▪ For example,
val parser = new SimpleLanguageParserval result = parser.parse(parser.expr, "3 - 4 * 5")
sets result to
((3~None)~Some((-~((4~Some((*~(5~None))))~None))))
▪ We'll transform this to something more useful in the next lecture
The Option Type
▪ Option type expresses “0 or 1” relationship▪ Case class with two cases: Some, None▪ Example: List.find returns Option[A].
val result = lst.find(_ % 2 == 0)result match {
case Some(x) => println(x)case None => println("No match")
}
▪ Why not return null if there is no match?▪ Int can't be null▪ null might be a valid value in the collection▪ null can lead to NullPointerException
Lab
▪ You work with a buddy▪ One of you writes the code (coder), the
other types up answers (scribe)▪ When you get stuck, ask your buddy
first!▪ Switch roles each lab. The previous
scribe is the coder for this lab.▪ The scribe submits lab work in lab8/report.txt inside the Git
repo. Include the coder's name in the report!▪ If the coder wants to submit something, that's cool too. Do
whatever is easy.
Step 0
1. Make an SBT project lab8:
cd cs152 # (or whereever you store your git repo)PROJECT=lab8echo $PROJECT | sbt new sbt/scala-seed.g8echo $PROJECT | sbt new cayhorstmann/cs152-seed.g8cd $PROJECTfind . -name Hello*.scala -exec rm {} \;sed -i -e "s/Hello/$PROJECT/" build.sbt
2. Edit build.sbt and change
libraryDependencies += scalaTest % Test
to
libraryDependencies ++= Seq(scalaTest % Test,"org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.0")
You can do this with a text editor or boldly with
sed -i -e 's/= scalaTest % Test/+= Seq\(scalaTest % Test, "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.0"\)/' build.sbt
(Thanks to Richard Faustino for this tip.)
3. If you use Eclipse, run
echo 'addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4")' > project/plugins.sbtsbt eclipse
Then start Eclipse and import the project.4. If you use IntelliJ, load the SBT project from the lab8 directory.
Step 1
1. Here is the complete application that was discussed in the lecture:
import scala.util.parsing.combinator._
class SimpleLanguageParser extends JavaTokenParsers {def expr: Parser[Any] = term ~ opt(("+" | "-") ~ expr)def term: Parser[Any] = factor ~ opt(("*" | "/" ) ~ term)def factor: Parser[Any] = wholeNumber | "(" ~ expr ~ ")"
}
object Lab8 extends App {val parser = new SimpleLanguageParserval result = parser.parse(parser.expr, "3 - 4 * 5")println(result.get)
}
2. Run the application. What is the output?3. Now change the expression to 3 * 4 - 5. How does the output differ?4. How does this indicate that the grammer “knows” that multiplication binds
more strongly than addition?
5. The factor rule recurses back to the expr rule. Give an example input to theparser that demonstrates this feature. What is your input? What is theoutput?
6. What happens when you give an illegal input to the parser? What input didyou give, and what was the result?
Step 2
1. Add the following function to the SimpleLanguageParser (and not to Lab8):
def eval(x : Any) : Int = x match {case a ~ Some("+" ~ b) => eval(a) + eval(b)case a ~ Some("-" ~ b) => eval(a) - eval(b)case a ~ Some("*" ~ b) => eval(a) * eval(b)case a ~ Some("/" ~ b) => eval(a) / eval(b)case a ~ None => eval(a)case a : String => Integer.parseInt(a)case "(" ~ a ~ ")" => eval(a)
}
2. In your main program, print the value computed byparser.eval(result.get). What output do you get when the parser inputis 3 - 4 * 5? 3 * 4 - 5?
3. What does the eval function do?4. Give an input that shows that the last matching rule is necessary. What is
your input? What is the output? What result do you get when you commentout the last matching rule?
Step 3
1. Now we want to enhance the language and add support for statements suchas
val x = 3 + 4 * y
In BNF, this would be
valdef ::= "val" identifier "=" expr
Add a valdef type to the combinator parser that represents such adefinition. (To see how to parse an identifier, go to the scaladoc ofJavaTokenParsers and then click on the source link on the top of the file.)
What is the type that you added?
2. What do you get when you parse val x = 3 + 4? Hint: Remember to call
parser.parse(parser.valdef, "val x = 3 + 4")
3. What happens when you parse val x = 3 + 4 * y? (Hint: Look closely at theoutput. Where is the y? Why?)
4. What do you do to fix the problem? (Hint: Handle variable identifiers at thesame level as numbers.)
5. Now what is the parser output?
CS 152 - Lecture 10
Cay S. Horstmann
Reminder: Context-Free Grammar
▪ Productions
expr ::= term (( "+" | "-" ) expr)?term ::= factor (( "*" | "/") term)?factor ::= wholeNumber | "(" expr ")"
▪ Non-terminals expr term factor▪ Terminals (tokens) wholeNumber " (" "+" ...▪ Parse tree shows derivation process
Reminder: Scala Combinator Parser
▪ Each nonterminal becomes a function▪ Terminals (strings) and nonterminals (functions) are combined
with operators• Sequence ~, returns instance of class ~ (similar to a pair)• Alternative |• 0 or more rep(P), returns List of the results of P• 0 or 1 opt(P), returns Option: Some of the result of P, or None
class SimpleLanguageParser extends JavaTokenParsers {def expr: Parser[Any] = term ~ opt(("+" | "-") ~ expr)def term: Parser[Any] = factor ~ opt(("*" | "/" ) ~ term)def factor: Parser[Any] = wholeNumber | "(" ~ expr ~ ")"
}
Transforming Combinator Parser Results
▪ Default combinator parser result is inconvenient
((3~None)~Some((-~((4~Some((*~(5~None))))~None))))
▪ Use ^^ operator to transform. For example,
wholeNumber ^^ (_.toInt)
▪ Use pattern matching for transforms
def expr: Parser[Int] = (term ~ opt(("+" | "-") ~ expr)) ^^ {case a ~ None => acase a ~ Some("+" ~ b) => a + bcase a ~ Some("-" ~ b) => a - b
}
▪ Use ~>, <~ to discard tokens
def factor: Parser[Int] = wholeNumber ^^ (_.toInt) |"(" ~> expr <~ ")"
Returning Expression Trees
▪ Returning Int works if we interpret an arithmetic expressionwithout variables
▪ If we have variables in a loop, need to evaluate multiple times▪ Want an expression tree: Parser[Expr]
class Exprcase class Number(value : Int) extends Exprcase class Variable(name : String) extends Exprcase class Operator(left : Expr, right : Expr,
f: (Int, Int) => Int) extends Expr
▪ class SimpleLanguageParser extends JavaTokenParsers {def expr: Parser[Expr] = (term ~ opt(("+" | "-") ~ expr)) ^^ {
case a ~ None => acase a ~ Some("+" ~ b) => Operator(a, b, _ + _)case a ~ Some("-" ~ b) => Operator(a, b, _ - _)
}
A Grammar Problem
▪ Consider the input 3 - 4 - 5▪ Parse:
expr ::= term - expr::= term - term - expr
▪ Resulting expression tree:
▪ That's the wrong tree. 3 - (4 - 5) is 3 - (-1) = 4▪ We want - to be left associative: (3 - 4) - 5▪ How about switching expr and term?▪ In theory, this works, but in practice, the Scala combinator parser
gets into an infinite recursion
Remedy: Manually Group Terms
▪ Reorganize Grammar
class SimpleLanguageParser extends JavaTokenParsers {def expr: Parser[Expr] = term ~ rep(("+" | "-") ~ term)def term: Parser[Expr] = factor ~ rep(("*" | "/" ) ~ factor)def factor: Parser[Expr] = wholeNumber | "(" ~ expr ~ ")"
▪ Now we have a list of all terms▪ Manually group them left to right
Operator(Operator(...(Operator(term1, term2, op1), term3, op2), ...)
▪ Reminder: foldLeft or the /: operator
def sum(lst: List[Int]) = (0 /: lst) ((x, y) => x + y)
▪ The /: indicates the tree shape
Use foldLeft
▪ Parsing 3 - 4 - 5 yields
3 ~ List("-" ~ 4, "-" ~ 5)
▪ Note that the tail is a flat list▪ Want the tree
-6/ \
-1 "-" ~ 5/ \
3 "-" ~4
▪ Initial element is 3▪ Next element is "-" ~ 4▪ Fold operator is
case (x, "+" ~ y) => Operator(x, y, _ + _)case (x, "-" ~ y) => Operator(x, y, _ - _)
▪ Now everything together:
def expr: Parser[Expr] = (term ~ rep(("+" | "-") ~ term)) ^^ {case a ~ lst => (a /: lst) {
case (x, "+" ~ y) => Operator(x, y, _ + _)case (x, "-" ~ y) => Operator(x, y, _ - _)
}
▪ Result: Parser yields Expr with the correct structure▪ Next lecture: What can we do with the tree?
Lab
▪ You work with a buddy▪ One of you writes the code (coder), the
other types up answers (scribe)▪ When you get stuck, ask your buddy
first!▪ Switch roles each lab. The previous
scribe is the coder for this lab.▪ The scribe submits lab work in lab9/report.txt inside the Git
repo. Include the coder's name in the report!▪ If the coder wants to submit something, that's cool too. Do
whatever is easy.
Step 1
Complete the program from slides 3 and 4.
import java.io._import scala.util.parsing.combinator._
class SimpleLanguageParser1 extends JavaTokenParsers {def expr: Parser[Int] = (term ~ opt(("+" | "-") ~ expr)) ^^ {
case a ~ None => acase a ~ Some("+" ~ b) => a + bcase a ~ Some("-" ~ b) => a - b}
. . .def factor: Parser[Int] = wholeNumber ^^ (_.toInt) | "(" ~> expr <~ ")"
}
object Main extends App {val parser = new SimpleLanguageParser1val result = parser.parse(parser.expr, new InputStreamReader(System.in))println(result)
}
1. How did you complete the parser?2. What result do you get when you input 3 + 4 * 5?
3. On your platform, how did you have to indicate the end of console input? (Ihad to type Ctrl+D in Linux.)
4. Why isn't the output a tree?
Step 2
Complete the program from Slide 5.
import java.io._import scala.util.parsing.combinator._
class Exprcase class Number(value : Int) extends Exprcase class Variable(name : String) extends Exprcase class Operator(left : Expr, right : Expr,
f: (Int, Int) => Int) extends Expr
class SimpleLanguageParser2 extends JavaTokenParsers {def expr: Parser[Expr] = (term ~ opt(("+" | "-") ~ expr)) ^^ {
case a ~ None => acase a ~ Some("+" ~ b) => Operator(a, b, _ + _)case a ~ Some("-" ~ b) => Operator(a, b, _ - _)}
...}
object Main {def main(args : Array[String]) : Unit = {}val parser = new SimpleLanguageParserval result = parser.parse(parser.expr, new InputStreamReader(System.in))
println(result)}
NOTE: Unfortunately, (Number(_.toInt)) doesn't work. Use (x =>Number(x.toInt))
1. How did you complete the code?2. What did you get when you parsed 3 + 4 * 5?3. What did you get when you parsed 3 - 4 - 5?4. What happens when you flip expr and term in the right hand side of the first
production? Try parsing 3 - 4 - 5 again.
Step 3
Complete the program from slide 8:
import java.io._import scala.util.parsing.combinator._
class Expr...
class SimpleLanguageParser3 extends JavaTokenParsers {def expr: Parser[Expr] = (term ~ rep(("+" | "-") ~ term)) ^^ {
case a ~ lst => (a /: lst) {case (x, "+" ~ y) => Operator(x, y, _ + _)case (x, "-" ~ y) => Operator(x, y, _ - _)
}}
...}
object Main extends App {...
}
1. How did you complete the program?
2. What did you get when you parsed 3 - 4 - 5?3. We never did anything about Variable. How can you enhance your program
to parse them as well, e.g. 3 - 4 * x?
CS 152 - Lecture 11
Cay S. Horstmann
Parsing 5
▪ Grammar techniques▪ Repetition▪ Precedence and associativity▪ LL(1) grammars▪ Avoiding left recursion▪ Left factoring▪ Ambiguity
Reminder: Context-Free Grammar
▪ Productions
expr ::= term (( "+" | "-" ) expr)?term ::= factor (( "*" | "/" ) term)?factor ::= wholeNumber | "(" expr ")"
▪ Non-terminals expr term factor▪ Terminals (tokens) wholeNumber "(" "+" ...▪ Parse tree shows derivation process
Repetition
▪ Simple repetition, ex. array bounds [10][10][20]
bounds ::= bound | bound boundsbound = "[" number "]"
▪ Or in EBNF
bounds ::= (bound)+
▪ In Scala, simply collect all
def bounds = bound ~ rep(bound) ^^ { case head ~ tail => head :: tail }bound = "[" ~> number <~ "]" ^^ { _.toInt }
▪ Or use rep1 convenience combinator
def bounds = rep1(bound)
No transform needed
▪ Repetition with separators, ex. function arguments id(arg1,arg2, ...)
funcall ::= ident ~ "(" ~ expr ("," expr)* ")"
▪ Scala convenience combinator
def funcall = ident ~ "(" ~> repsep(expr, ",") <~ ")"
repsep returns List without separators; here, List[Expr]
Operator Precedence
▪ Term/factor levels make * / stronger than + -:
expr ::= term (( "+" | "-" ) expr)?term ::= factor (( "*" | "/" ) term)?factor ::= wholeNumber | "(" expr ")"
▪ Even stronger operator, say ^ for “raise to a power”
term ::= factor (( "*" | "/" ) term)?factor ::= factor1 ( "^" factor )?factor1 ::= wholeNumber | "(" expr ")"
▪ Weaker operator, say == <>
expr ::= expr1 (( "==" | "<>" ) expr)?expr1 ::= term (( "+" | "-" ) expr1)?term ::= factor (( "*" | "/" ) term)?factor ::= wholeNumber | "(" expr ")"
Operator Associativity
▪ Right recursion is naturally right associative
assignment ::= ident ":=" assignment | ident ":=" expr
▪ For example,
a := b := 3
parses as
LL(1) Grammars
▪ Start from the grammar root▪ Apply productions until input string is reached▪ Which production?▪ With some grammars, choice can be deterministic▪ Desirable to have one-token lookahead property, called LL(1):
Only one production can be chosen depending on the next token▪ Example: 3 + 4 * 5▪ Start from grammar root: expr ::= term (( "+" | "-" ) expr)?▪ After matching 3 as term → factor → wholeNumber, back in term:
term ::= factorterm ::= factor ("*" | "/") term
▪ Should we go on with ("*" | "/") term? No—next token is "+"
Avoiding Left Recursion
▪ A grammar rule is left recursive if the left hand side appears atthe beginning of the right hand side
expr ::= expr "+" term | term
▪ Not LL(1) because there is no way of choosing between the tworules
▪ Infinite recursion in Scala combinator parser▪ Can be removed by observing that eventually expr must be a term
expr ::= term | term restrest ::= "+" term rest
▪ Annoyance: Right recursion not convenient for left-associativeoperators
▪ Scala solution: Collect all terms and fold
def expr = term ~ rep("+" ~> term) ^^{ case a ~ lst => (a /: lst) { Sum(_, _) } }
(See previous lecture for details)
▪ Left recursion is not a problem for bottom-up parsers (which wedon't discuss in this class)
Left Factoring
▪ If a grammar has rules of the form
A ::= α β | α γ
it can't be LL(1)
▪ Example:
Assignment statement and procedure call both start withidentifier
stat ::= ident ":=" expr | ident "(" expr ("," expr)* ")" | ...
▪ Factor out common left
stat ::= ident rest | ...rest ::= ":=" expr | "(" expr ("," expr)* ")"
▪ Annoyance: Complicates semantic analysis because identifier isseparated from assignment or call
▪ Scala solution: opt and match
def stat = ident ~ (":=" expr | "(" expr ")")
Ambiguity
▪ if with optional else
ifstat ::= "if" "(" expr ")" stat ("else" stat)?
▪ Gives rise to ambiguous construct
if (c1) if (c2) s1 else s2
or
▪ Disambiguation depends on parser technology▪ Scala: The obvious rule gives the expected behavior (see Lab)
(("if" ~ "(") ~> expr <~ ")") ~ stat ~ opt("else" ~> expr)
Reading Grammars
▪ Useful skill, even outside programming language theory▪ E.g. to determine correct file formats▪ Variation in grammar syntax▪ Terminals vs. nonterminals—some authors enclose nonterminals
in <...>
<expr> ::= <term> | <term> (+ | -) <expr>
Then no "..." around terminals
▪ [...], {...} instead of (...)?, (...)*
Lab
▪ You work with a buddy▪ One of you writes the code (coder), the
other types up answers (scribe)▪ When you get stuck, ask your buddy
first!▪ Switch roles each lab. The previous
scribe is the coder for this lab.▪ The scribe submits lab work in lab10/report.txt inside the Git
repo. Include the coder's name in the report!▪ If the coder wants to submit something, that's cool too. Do
whatever is easy.
Step 1
1. Look at the Annotated XML Specification Section 3.1. Can you have spacesbefore an element name in a start tag?
< name>
Which grammar rule did you use to answer this question?
2. Before the /> in an empty-element tag?
<name />
Which grammar rule did you use to answer this question?
3. Around the = in an attribute?
<name attr = "value">
Which grammar rule did you use to answer this question?
Step 2
1. Write a Scala parser for the grammar
expr ::= "if" "(" number ")" expr ("else" expr)? | number
What is your program? What do you get when you parse
if (1) if (2) 3 else 4
2. Ok, it's probably too tedious to figure out whether the else associates withthe first or second if. Enhance your program to yield a IfExpr. Use thefollowing outline:
class Exprcase class IfExpr(cond : Number, pos : Expr, neg : Expr) extends Exprcase class Number(value : String) extends Expr
class SimpleLanguageParser extends JavaTokenParsers {def expr: Parser[Expr] = ...def number: Parser[Number] = wholeNumber ^^ { Number(_) }
}
Transform cond ~ expr ~ None into IfExpr(cond, expr, null).
Now what do you get for
if (1) if (2) 3 else 4
3. Does the Scala parser resolve the if/if/else ambiguity in the same way asC++, or the other way?
CS 152 - Lecture 12
Cay S. Horstmann
The Simple Language SL1
▪ Grammar
block ::= (valdef | fundef)* exprexpr ::= expr2 | "if" "(" expr ")" block "else" blockexpr2 ::= term (( "+" | "-" ) term)*term ::= factor (( "*" | "/" ) factor)*factor ::= wholeNumber | "(" expr ")" | ident | funcall | funliteralfuncall ::= ident "(" (expr ("," expr)*)? ")"funliteral ::= "{" (ident ("," ident)*)? "=>" block "}"valdef ::= "val" ident "=" expr ";"fundef ::= "def" ident "=" funliteral ";"
▪ Similar to Scala subset, but• Only two types: integer and functions• No compile-time type checking• if fakes Boolean similar to C: > 0 is true, <= 0 is false• Definitions end in semicolons• Block has a single expression
Some SL1 Examples
▪ Simple computation:
val a = 3;val b = 4;a * a + b * b
▪ A function literal and a function call
val max = { x, y => if (x - y) x else y };max(3, 4)
▪ A higher-order function
val threeTimes = { f, x => f(f(f(x))) };threeTimes({x => x * x}, 2)
▪ A recursive function
def fac = { x => if (x) x * fac(x - 1) else 1 };fac(10)
Interpreter Outline
▪ Parse the program, converting into Expr (as before) / Block▪ Block is sequence of definitions, followed by one expression▪ Need to add capabilities to eval(Expr)
• if / else• function call
▪ Evaluating block:• Add definitions to symbol table• Evaluate expression with that table
The Symbol Table
▪ We had a lab (in this lecture) with evaluating variable definitions▪ In that lab, we used a Map[String, Int] for the symbol table.▪ Issue #1: Not all values are of type Int (i.e. functions)▪ Remedy: Use Any▪ Issue #2: We may have duplicate variable definitions
val a = 3;val fun = { x => val a = 2; a * x };a * fun(4)
▪ Remedy: Use a List[(String, Any)] instead▪ To add a definition, use ::▪ Newer definitions at the front shadow the older ones
((a, 2), (x, 4), (a, 3))
▪ To look up a symbol's value, use find to find the first match:
symbols.find(_._1 == name) match {case Some(pair) => pair._2case None => None
}
Evaluating Expressions
▪ eval of an expression yields a value▪ Need symbol table for looking up variables▪ Result may be either an Int or a function
val a = 3 * b;val sq = { x => x * x };
def eval(expr : Expr, symbols : List[(String, Any)]) : Any =expr match {
case Number(num) => numcase Variable(name) => ... // find name in symbolscase Operator(left, right, f) => ...
// was f(left, right), but now we don't know they are Intcase IfExpr(cond, block1, block2) => ......
}
Evaluating Blocks
▪ Block = list of definitions + one expression▪ Evaluate all definitions▪ Add pairs to symbol table
def evalDef(symbols : List[(String, Any)], defn : Valdef) =(defn.name, eval(defn.expr, symbols)) :: symbols
▪ (Additional wrinkle with function definitions—later)▪ Use resulting table to evaluate expression
def evalBlock(block : Block, symbols : List[(String, Any)]) : Any =eval(block.expr, (symbols /: block.defs) { evalDef(_, _) } )
Visualize foldLeft with two definitions: (red fringe = list ofdefs)
Parsing Challenge: Function Calls
▪ Lookahead required to distinguish between variable andfunction(args)
▪ Parse with optional parameter list▪ If found, generate a Funcall, else a Variable
def valOrFuncall = valOrFun ~ opt( "(" ~> repsep(expr, ",") <~ ")" ) ^^ {case expr ~ Some(args) => Funcall(expr, args)case expr ~ None => expr
}
▪ Lookahead problem isn't limited to names. { x => x * x } is afunction literal, { x => x * x }(3) is a function call.
def valOrFun = "(" ~> expr <~ ")" |ident ^^ { Variable(_) } |funliteral
def funliteral: Parser[Expr] = ("{" ~> repsep(ident, ",") <~ "=>") ~ expr <~ "}" ^^ {
case params ~ expr => Function(params, expr)}
▪ Note: repsep(ident, ",")matches comma-separated list of ident,yielding a List[String] (with the separators discarded)
Calling a Function
▪ Inputs:• Function with parameter list and block, e.g. { x, y => (x + y) / 2 }• List of parameters, e.g. (10, a + 2)
▪ Add bindings (param name, param value) to symbol table
x -> eval(10)
y -> eval(a + 2)
▪ Evaluate body with that symbol table
def eval(expr : Expr, symbols : List[(String, Any)]) : Any =expr match {
case Funcall(fun, args) => eval(fun, symbols) match {case Function(params, body) =>
evalBlock(body, params.zip(args.map(eval(_, symbols))) ::: symbols)}
▪ params.zip(args) makes a list of pairs ((p1, a1), (p2, a2), ...)▪ But we need to evaluate the arguments. The map takes care of
that▪ Parser and interpreter has < 150 lines of code!▪ Still need to discuss what to do with recursive function—next
class
Lab
▪ You work with a buddy▪ One of you writes the code (coder), the
other types up answers (scribe)▪ When you get stuck, ask your buddy
first!▪ Switch roles each lab. The previous
scribe is the coder for this lab.▪ The scribe submits lab work in lab11/report.txt inside the Git
repo. Include the coder's name in the report!▪ If the coder wants to submit something, that's cool too. Do
whatever is easy.
Step 1: Becoming Comfortable with SL1
1. Make a project lab11 and add this Scala code. Run the four SL1 programsfrom slide 3. What outputs do you get?
2. Write a SL1 program that defines pow2 to compute 2n for a given n, then callspow2(10). What is your program and what is its output?
Step 2: Understanding the Parse Tree
For each of the following SL1 expressions and blocks, draw a parse tree. Thatshould be the tree returned by parser.parse(...).get. Write the tree sidewayswith indentations, like this for the expression x * (y + 1). Use - for indentationsso they don't get lost in your report.
Feel free to modify the program to print the parse tree.
Operator-Variable(x)-Operator--Variable(y)--Number(1)--<add function>-<multiply function>
1. { x, y => x + y }2. fac(x - 1)3. val sq = { x => x * x } ; sq(2)
Step 3: Understanding Symbol Tables
For each of the following scenarios, write the symbol table. Write it as a set ofequations, like this
a = 3sq = { x => x * x }a = 2
where the newest symbols appear at the bottom. In our example, the last definitionof a shadows the previous one. Note that each variable is bound either to aninteger or a function.
1. At the end of the program val a = 3 ; val b = a + 1; val a = 2 ; a + b2. At the execution of the function call in the program val a = 3; val sq = { x
=> x * x } ; sq(a + 2). Be sure to show the binding for x.3. At the execution of the function call in the program val a = 3; val x = 2; {
x => a * x } (a)
Confirm your guesses for 2 and 3 with this trick. Define a function
def spy[T](t : T) = { println(t); t }
Then add a spy(...) call around
params.zip(args.map(eval(_, symbols))) ::: syms
in the evaluation of a Funcall.
CS 152 - Lecture 13
Cay S. Horstmann
Scope
▪ Scope of named entity = region where name references the entity▪ Scopes can overlap
▪ class Example // Java{private int foo;public void fun(double foo) {
System.out.println(foo); // Which foo?}public void fun() {
System.out.println(foo); // Which foo?}
}
▪ Particularly common in languages with nested functions
def fun(x : Int) { // Scaladef helper() { var x : Int; ... }
Static vs. Dynamic Scoping
▪ Static scope: Depends on program source only▪ Dynamic scope: Depends on execution history▪ Example
val x = 1;def f(y) = x + y;def g() { val x = 2; f(x) };g()
▪ Static scoping: Result is 1 + 2▪ Dynamic scoping: Result is 2 + 2▪ Dynamic scoping is common in interpreted languages (see lab)▪ Disadvantage: Meaning of program harder to understand
Implementation of Dynamic Scoping
▪ Global symbol table▪ Push definition when it is encountered, pop at end of scope▪ Example:
val x = 1;def f(y) = x + y;def g() { val x = 2; f(x) };g()
x -> 1 // first def. of xx -> 2, x -> 1 // def. of x inside gy -> 2, x -> 2, x -> 1 // def. of parameter y
Implementation of Static Scoping
▪ Each function has its own symbol table▪ Records the meanings of symbols when the function was defined▪ Function body is evaluated with that table (augmented by
parameter bindings)▪ Example:
val x = 1;def f(y) = x + y; // x -> 1def g() { val x = 2; f(x) }; // f -> ...
Table only needs to store free variables in function body
▪ When f is executed:
y -> 2, x -> 1
Implementing Recursive Functions
▪ Can't use val:
val fac = x => if (x == 0) 1 else x * fac(x - 1)
▪ RHS is an anonymous function with free variable fac
x => if (x == 0) 1 else x * fac(x - 1)
▪ Then a new variable fac is defined▪ def simultaneously adds fac and RHS to symbol table
case class Closure(params : List[String], body : Block, var env : List[(String, Any)])...
def evalDef(symbols : List[(String, Any)], defn : Definition) =defn match {
case Defdef(name, Function(params, body)) => {val cl = Closure(params, body, symbols)val syms = (name, cl) :: symbolscl.env = syms // mutation
syms}...
}
▪ Here, for the first time, we use a var in Scala.▪ Pure functional solution is possible but complex (Google for Y
combinator).
Lab
▪ You work with a buddy▪ One of you writes the code (coder), the
other types up answers (scribe)▪ When you get stuck, ask your buddy
first!▪ Switch roles each lab. The previous
scribe is the coder for this lab.▪ The scribe submits lab work in lab12/report.txt inside the Git
repo. Include the coder's name in the report!▪ If the coder wants to submit something, that's cool too. Do
whatever is easy.
Step 1: Bash
▪ Launch your bash shell.▪ Next, some background information. The bash command shell has
a rudimentary programming language. Here are some of itsfeatures.
1. You declare global variables with the syntax
varname=initialValue
2. You declare functions with the syntax
function funname {. . .
}
Function parameters are not named. Instead, you refer to them as$1, $2, $3...
3. You call functions with the syntax
funname arg1 arg2 arg3 . . .
4. You declare local variables (inside functions) with the syntax
local varname=initialValue
5. You get the value of a variable with the syntax
$varname
For example, the command
echo $a
prints the value of a
Step 1: Bash (Continued)
1. Make a file test1.bash with the contents
x="2"
function f {echo $x
}
function main {local x="3" ;f ;
}
main
.2. Start the bash shell. Run the program: source test1.bash. What does it
print?3. What does that tell you about variable scoping in the bash language?
Step 2: More Bash
1. Make a file test2.bash with the contents.
x="2"
function f {echo $x
}
function g {local x="3" ;f ;
}
function main {local x="4" ;f ;g ;f ;
}
f ;main
What do you expect this program to print?
2. Start the bash shell. Run the program: source test2.bash. What does itprint?
3. Explain the behavior by drawing diagrams of the symbol table.
Step 3: Understanding Closures in SL1
1. Consider the SL1 program
val a = 3;val f = { x => x * a };val g = { a => a * f(a)};g(1)
What result output do you expect?
2. Start the SL1 interpreter from the previous lab and run the program. Whatoutput did you get?
3. Place a breakpoint in the line
case Valdef(name, Function(params, body)) => { (name, Closure(params, body, symbols)) :: symbols }
Run the debugger with the same program as input. When the breakpointis hit, inspect symbols. What do you get for symbols when the definitionsof f and g are evaluated?
4. Change the code for
case Funcall(fun, args) => eval(fun, symbols) match
to
case Funcall(fun, args) => val funval = eval(fun, symbols); funval match
Debug the program again, this time with a breakpoint in the line
evalBlock(body, params.zip(args.map(eval(_, symbols))) ::: syms)
The breakpoint will be triggered when g and f are executed. What arethe values of symbols and the syms in the closure object when g isexecuted?
5. With what symbol table is the body of f executed?