ECE326 PROGRAMMING LANGUAGES
Lecture 1 : Course Introduction
Kuei (Jack) Sun ECE University of Toronto Fall 2019 Course Instructor
Kuei (Jack) Sun Contact Information Use Piazza Send me (Kuei Sun) a private post Not “Instructors”, otherwise the TAs will also see it http://piazza.com/utoronto.ca/fall2019/ece326 Sign up to the course to get access Office: PRA371 (office hours upon request only) Research Interests Systems programming, software optimization, etc.
2 Course Information
Course Website http://fs.csl.toronto.edu/~sunk/ece326.html Lecture notes, tutorial slides, assignment handouts Quercus Grade posting, course evaluation, lab group sign-up! Piazza Course announcement, course discussion Assignment discussion Lab TAs will read and answer relevant posts periodically
3 Course Information
No required textbook Exam questions will come from lectures, tutorials, and assignments See course website for suggested textbooks Lab sessions Get face-to-face help from a TA with your assignments Tutorials Cover supplementary materials not in lectures Go through sample problems that may appear on exams
4 Volunteer Notetakers
Help your peers Improve your own notetaking skills Receive Certificate of Recognition Information: https://www.studentlife.utoronto.ca/as/note-taking Registration: https://clockwork.studentlife.utoronto.ca/custom/misc/home.aspx Notetakers needed for lectures and tutorials 🙏🙏
5 Background
Programming Languages A formal language consisting of a instructions to implement algorithms and perform tasks on computers Thousands exist, more being made Programming Paradigms A way to categorize programming languages by features Execution model Code organization Style and syntax …etc
6 Course Outline
Imperative Programming Writing instructions and commands (APS105) Object-Oriented Programming Objects interacting with one another (ECE244) Metaprogramming Writing code that generates more code (CSC324) Concurrent Programming Multiple tasks overlapping in their executions (ECE344, CSC367) Functional Programming Programming in the style of evaluating mathematical functions (CSC324)
7 Course Objective
Learn different ways of writing computer programs Law of the instrument “To the man who only has a hammer, everything he encounters begins to look like a nail.” – Abraham Maslow One style may work better than others for a given problem E.g. recursion vs. iteration Analyze programming languages and their features Semantic: more expressive power Syntactic: easier to read/write Optimization: improves performance and efficiency
8 Syntactic Sugar
Example: Java For Loop String[] fruits = { “Apple”, “Banana”, “Strawberry” }; for (int i = 0; i < fruits.length; i++) { system.out.println(fruits[i]); } For-Each Loop
for (String f : fruits) { system.out.println(f); } Same bytecode generated, but easier to read
9 Compiler Optimization
Example: C restrict keyword Informs compiler that a pointer has no alias
void add2(int * a, int * b, int * restrict c) { *a += *c; // normally, *c must be reloaded because a may // point to same address as c, which means that // *c could change after “*a += *c” is executed *b += *c; } Compiler can thus be more aggressive in optimizing this function
10 Course Objective
Learn implementations of language features Cost-benefit analysis E.g. automatic memory management Avoids bugs associated with manual memory management Requires garbage collection Learn programming languages that matters Popularity – large community of active developers Easy to find help Easy to find libraries or packages Easy to find jobs
11 Popularity
By job posting Java: 65,986 jobs Mainly used by web application developers Python: 61,818 jobs Known for high productivity JavaScript: 38,018 jobs The de facto language for modern browsers C++: 36,798 jobs Favored by game developers, large application software, …etc. C#, PHP, Perl…
Source: https://www.codingdojo.com/blog/the-7-most-in-demand-programming-languages-of-2019 12 Popularity
By contribution on GitHub JavaScript Java Python PHP C++ C#, TypeScript, Shell, C, Ruby, …etc Correlates well with popularity by job postings
Source: https://github.blog/2018-11-15-state-of-the-octoverse-top-programming-languages/
13 Popularity
By Google search trends JavaScript Python Java Go Google’s programming language Aims to simplify programming in multicore, networking environment Elixir Ruby, Kotlin, TypeScript, Scala, Clojure, ..etc.
Source: https://codeburst.io/10-top-programming-languages-in-2019-for-developers-a2921798d652
14 Popularity
By growth in community Kotlin HCL TypeScript PowerShell Rust CMake Go Python, Groovy, SQLPL
Source: https://github.blog/2018-11-15-state-of-the-octoverse-top-programming-languages/
15 Lab Assignments
4 Assignments 3 Programming languages C++11 A newer version of C++ compared to what’s learned in ECE244 Python 3 Extremely popular high-level programming language Rust Fairly new systems programming language Focuses on performance and safety Requires you to really understand your own code
16 Lab Assignments
1. Easy Blackjack Play the game automatically 2. Optimal strategy calculator Play Blackjack optimally 3. Object-relational mapping Seamless integration of objects with a relational database 4. Concurrent Database Implement a fast toy database
17 Lab Assignments
Expect significant efforts involved You will need to spend time outside of lab hours Learn new programming languages Apply new programming techniques Require time to design and implement your program Require lots of time for debugging and performance improvement Groups of 2 Sign up for a group on Quercus Required for you to gain access to Git repository Allows you to work on a common code base Also used to submit your assignment
18 Lab Information You may go to any of the lab sessions for TA help Lab times and location posted on course website After hour support (on Piazza) Your peers! Teaching Assistants Assignment 1: Xue (Chloe) Geng Assignment 2: Wenjun (Wendy) Qiu Assignment 3: Szu-Chieh (Jeffrey) Fang Assignment 4: Jemin Andrew Choi Me I won’t answer unless the problem is with the assignment
19 Assignment Grading
Automated Tester Runs a set of tests Correctness Performance Gives you a mark on your solution WYSIWYG Mark you get from tester is what you expect for the assignment Assignment Submission Follow lab instructions You may submit multiple times – do not submit last minute!
20 Assignment Policy
No deadline extensions Failure to submit on time will result in zero marks There’s two of you per group You are given 3 to 5 weeks per assignment start early, don’t procrastinate! Make sure you submit something for partial marks Plagiarism and Cheating Severe penalty Grade reduction to suspension from University Talk to me if you feel overwhelmed
21 Grading Scheme
Midterm: 25% October 29th, 4pm to 5:30pm, EXE320 Final: 45% Assignments: 26% 5%, 6%, 7%, 8%, respectively Tutorial Quizzes: 4% 12 tutorials, 12 quizzes Only need to complete 10/12 quizzes for full marks
22 Next Lecture
“Classifications of Programming Languages”
Instructor: Kuei (Jack) Sun
Course Website: http://fs.csl.toronto.edu/~sunk/ece326.html Piazza Discussion: http://piazza.com/utoronto.ca/fall2019/ece326
23 ECE326 PROGRAMMING LANGUAGES
Lecture 2 : Comparison of Programming Languages
Kuei (Jack) Sun ECE University of Toronto Fall 2019 Programming Languages
Thousands of them Goal How to describe a programming language in one line? Buzzwords! Intended audience (i.e. programmers) Performance Expressiveness Dominant style, syntax, or feature etc…
2 By Intended Users
General-purpose language Used by various application domains E.g. C++, Python, Java, …etc Domain-specific language (DSL) Specialized use E.g. Galaxy Designed for StarCraft 2’s map editor by Blizzard Entertainment No clear boundary Perl and SQL used to be considered DSL
3 By Support
Standard Library Provides frequently used functionality E.g. libstdc++ Build tool Automates creation of executables from source code E.g. GNU make, Apache Ant Compiler must support separate compilation Package Manager E.g. Python pip Tool that automates installing, upgrading, and removing software and/or data in a consistent manner
4 By Implementation
Compiled Source code compiled to machine code (executable) Runs faster/more efficient Requires recompilation for any source code change Can impact productivity, especially for large software Portability concerns Compiler must support all target architectures Bugs may be introduced when porting to another architecture E.g. long in C is architecture dependent – can break developer assumption long long – at least 64 bits In couple of years: long long long for 128-bits?
5 By Implementation
Interpreted Usually associated with high-level languages Interpreter directly executes instructions of a program Usually done in one of the following way: 1. Parse as you go Syntax errors not caught until line is (about to be) executed E.g. Bash script 2. Translate to some intermediate representation first E.g. Python Limited form of optimization possible
6 By Implementation
Mixture of both Virtual machine Source code compiled to bytecode Run bytecode on virtual machine Virtual machine can run on any hardware platform (portability) E.g. Java Virtual Machine (JVM) Just -in-time compilation Compile while program is running! Compile when “needed” E.g. if compilation can result in speed up over running on the interpreter
7 Programming Idioms
A language-specific convention of accomplishing a task E.g. the “Pythonic” way Create a string of numbers delimited by white space nums is an array of numbers
def slow(nums): def fast(nums): text = str(nums[0]) return “ ".join( for n in nums[1:]: map(str, nums)) text += " %d"%n return text
fast is about 50% faster than slow (on my laptop) Performance can still be good if you know the way
∴ 8 By Level of Abstraction
- more abstraction - easier to write High-Level Python, Ruby Languages Java, Kotlin, Scala, Clojure Haskell, Racket Visual Basic, C# Systems C/C++ Languages Ada, D, Rust Swift (by Apple, for iOS apps) - more direct Low-Level Assembly Languages hardware access Languages Machine Languages - better performance
9 By Level of Abstraction
Systems programming languages Designed for performance Allows some level of hardware awareness Optimization hints (e.g. restrict, volatile, …etc) Inline assembly Still provides some high-level concepts High-level programming languages Designed for convenience Designed for expressiveness Functional programming languages, E.g. Haskell
10 By Programming Style
Imperative Programming Writing commands and statements, changing program state Concerns with how a program operates E.g. procedural programming, object-oriented programming Declarative Programming Writing expressions and desired result Concerns with what a program should achieve E.g. SQL queries, functional programming
SELECT firstName, LastName FROM Customers WHERE city=“Toronto”;
11 Turing Complete
A programming language that can solve any computation problems (theoretically) Requirements 1. Supports conditional branching Allows for conditional (e.g. if else) and loops 2. Can work with unlimited amount of memory Some languages are not Turing complete E.g. regular languages, vanilla SQL, Datalog
12 By Type System
Type system the rules governing the use of types in a program, and how types affect the program semantics
unsigned sum_of_squares(unsigned a, unsigned b); // which one of these is an error in C++11? char res = sum_of_squares(3.3, -2); Statically Typed Types of variables checked before runtime Dynamically Typed Types are checked at runtime, on the fly
13 Implicit Type Conversion
#include
15 By Type Safety
Protection from incorrect use of a value
Example union Foo { untagged union (C/C++) int i; float f; Union }; All member variables share the same memory location // in main() Can lead to type-unsafe Foo u; u.i = atoi(argv[argc-1]); usage! printf("%f\n", u.f); Solution: tagged union > ./union 1237864534 Adds a tag field to indicate 1640970.750000 which member is in use
16 By Features
Generic Programming Functions and classes defined in terms of parameterized types Parameterized types Instantiation of a generic type with actual type arguments E.g. Java
public class Box
17 By Features
Reflective Programming Introspection Program has knowledge of itself at runtime C++ Runtime Type Information (RTTI) Enables correct functioning of dynamic_cast Dynamic cast Attempt to cast a base class to a derived class Dog * dog = dynamic_cast
18 By Simplicity
E.g. Java vs. C++ E.g. Visual Basic Simple is good “Focus on debugging your application rather than debugging your programming language knowledge” – Zig developers Design language for average programmers, not pros Reduces chance of allowing for mistakes Cheaper to hire 😒😒
19 By Syntax
E.g. Off-side rule Blocks in the language are expressed by indentation Python: def sum(n): if n == 0: return 0 return 2*n + sum(n-1) Free-form languages Whitespace characters serve only as delimiters Scheme: (define (sum n) (if (= n 0) 0 (+ (* n 2) (sum (- n 1))) ))
20 By Seriousness
Esoteric programming languages Programming as art, or a joke Sometimes a subset of another language (sanitized) E.g. JSF👀👀k A subset of JavaScript Uses only 6 characters: [ ] ( ) ! + Prints “Hello world” in 26,924 characters [][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[] +!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[ +!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![] +[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+( !![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[] ]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]... 21 Next Week
“Imperative Programming” Reminder Please sign up on Quercus for a group if you have not done so First lab (for PRA0101 and PRA0103) starts next week Course Website: http://fs.csl.toronto.edu/~sunk/ece326.html Piazza Discussion: http://piazza.com/utoronto.ca/fall2019/ece326
22 ECE326 PROGRAMMING LANGUAGES
Lecture 3 : Anatomy of a Programming Language
Kuei (Jack) Sun ECE University of Toronto Fall 2019 Administrative Matter
Assignment 1 released http://fs.csl.toronto.edu/~sunk/asst1.html Due Date: Sunday September 29th, 11:59pm Group Sign-Up Deadline is Thursday, September 12th, 11:59pm If you do not sign up before the deadline, you will be assigned a random group Working Alone Private message me first, otherwise you will be assigned a random partner 2 Python
General purpose programming language Interpreted High-level of abstraction (from hardware) Multi-paradigm Dynamically typed Comprehensive standard library Easy to learn, hard to master
3 Interactive Mode
Benefit of interpreted language Can execute code as you type $ python3 Python 3.6.9 (default, Jul 21 2019, 14:33:59) [GCC 7.4.0] on cygwin Type "help", "copyright", "credits" or "license" for more information. >> x = 2 >> x + 3 5 >> exit()
$ …back in command prompt…
4 Source Code
hello.py
#!/usr/bin/python3 Tells terminal which interpreter to run # this is a comment (like // in C++) (we will be using # main is not required Python3 for this course) print(“hello world!”)
> python3 hello.py Explicitly run with hello world! Python3 > chmod +x hello.py > ls –l hello.py Make the script -rwxr-xr-x 1 jack jack 41 Sep 10 12:10 executable hello.py > ./hello.py Run script as executable hello world!
5 Value
A unit of representation or data Program can use or manipulate a value Associated with a type E.g. 5 is a value of type integer (integer value)
6 Type
A set of values, and (implicitly) a set of behaviours on those values A way to express constraints E.g. you should only use integer instructions on integer types Conveys intended use Conveys semantics (meaning) Defines how values of that type is stored E.g. Two’s complement for signed integers
7 Basic Types
Usually correlates with machine data types Integers E.g. short, int, long in C++ In Python >> x = 5 >> type(x)
Types natively supported by the programming language String >> type(‘a’) There is no “char” type in Python,
9 Variable
A name (i.e. identifier) associated with a memory location that contains a value In Python, a variable is created through initialization >> foo = 6 In contrast, C++ requires variable declaration (e.g. int a;) A variable has a type >> type(foo)
10 Keyword
A word reserved by the programming language Has special meaning Cannot be used as an identifier Common (between Python and C++) if, else, for, while, continue, break, return, etc. Python only in, and, or, def, with, pass, yield, etc.
11 Literal and Constant
Literal: represents a fixed value in source code E.g. 5, “hello”, 3.3 Constant is same as variable, except its value cannot be changed once initialized Python does not enforce constants Programmer enforced via naming convention Use all uppercase letters, words separated by underscore MAX_LENGTH = 128 C++ const keyword: const int max_length = 128; max_length = 64; // error: assignment of read-only variable
12 Expression
A combination of one or more values, operators, and functions that produces another value This process is known as evaluation >> 3 + 5 8 >> -foo * math.sqrt(3) -10.392304845413264 An expression can have subexpressions Innermost subexpression is evaluated first
13 Operators
Symbols or keywords that behave like functions However, they differ syntactically and/or semantically E.g. add(1, 2) vs. 1 + 2 Follows operator precedence Python uses same rules as mathematics (i.e. BEDMAS)
Arithmetic >> 3**2 # 3 to the power of 2 Same as C++, 9 >> 3 + 6/2 # true divsion plus more… 6.0 >> 6//2 # floor division 3
14 Operators
Relational Performs comparison, returns true or false Logical
C++ && || ! Python and or not
>> x = 5 >> y = 4 >> y + 1 == x and y < 5 or not print(“hello”) True Remember short-circuit evaluation?
15 Statement
A syntactic unit of imperative programming Performs “some” action(s) May contain expressions Doest no evaluate to a value If a statement is just an expression, the value is discarded/unused. // In C++, this is valid, but // doesn’t do anything (useful) x + 5; Example Loop control (i.e. continue and break) Function return
16 Statement
In C++ Terminates with a semicolon ; int a = 1 + 2 + 3 + 4 + 5 + 6 + 7 + In Python 8 + 9 + 10; Terminates at end of line Multi-line statement requires line continuation Use forward slash \ to continue statement to next line >> a = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + \ .. 9 + 10 55 >> 17 Compound Statement
Contains one or more statements Control Flow Execution results in choice made on which path to take Example If statement switch statement while loop for loop Etc
18 Block
Consists of one or more statements Usually part of control flow E.g. Conditional in Python if a == 3: Same as an empty body in C++ Reminder: pass Required for syntactic reason Do NOT mix else: tabs and b = 5 // in C++ spaces c = a + 6 if (a == 3) {} d = a - 3 else { … } Off-side rule d = a – 3; Blocks are expressed by their indentation
19 Conditional
Python Syntax
if boolean-expression: block elif boolean-expression: block Recall in C++: boolean-expression elif : if (bool-expr) { block … … } else: block No parentheses required for the conditions Python does not have a switch statement
20 Assignment
Assignment is an expression in C++ Evaluates to the assigned value int a, b; a = b = 5; // same as a = (b = 5); if ((b = foo(a))) // this is allowed assert(b != 0); // always true Assignment is a statement in Python!
a = b = 5 # syntax error, if statement # same as below # expects an expression temp = 5 if a = 5: a = temp b = 3 b = temp
21 Function
A reusable sequence of program instructions Usually has an associated name Also known as subroutines In Python def foo(parameters…): block Function can take zero or more parameters Return type does not need to be specified Can return different types returns None if function did not end with return
22 Scope
Name binding Association of name to a variable, constant, or function Region of code where binding is valid Block Scope Name valid within the block its declared in Example: C++ if (a == 3) { int b = foo(); // b is valid inside this … // block only } std::cout << b; // error: b not in scope
23 Function Scope
Python is different from C++ Local variable valid until end of function
def is_big(i): if i > 10: big = True # boolean true in Python x = foo(i) else: big = False # boolean false print(big) # this is valid, big in scope print(x) # this is invalid if i <= 10 return big # function returns a boolean
24 ECE326 PROGRAMMING LANGUAGES
Lecture 4 : Sequence Types
Kuei (Jack) Sun ECE University of Toronto Fall 2019 Administrative Matter
Group Sign-Up Deadline is Today, September 12th, 11:59pm If you do not sign up before the deadline, you will be assigned a random group Working Alone Private message me first, otherwise you will be assigned a random partner Tutorial Improvement N ew TA dedicated to tutorials only Will do exercise(s) based on previous week’s lecture 2 Function Scope
Python global variables are read-only inside functions CONST = 5 def foo(a): print(CONST+a) foo(3) # prints 8 Declaring variable of same name shadows the global CONST = 5 def foo(a): CONST = 6 # new local variable print(CONST+a) foo(3) # prints 9 print(CONST) # prints 5
3 Function Scope
UnboundLocalError Read global followed by write of same name CONST = 5 def foo(a): print(CONST+a) CONST = 6 # error: trying to write global Solution(?): global keyword def foo(a): global CONST print(CONST+a) CONST = 6 foo(3) # prints 8 print(CONST) # prints 6 4 First Class Citizen Can do everything that other entities can Example Can be assigned to a variable Can be passed to or return from a function Can be modified E.g. type is first class in Python (Not in C++) >> a = int >> a() 0 >> type(int)
5 ∴ Sequence
An ordered collection of values Python list, string, tuple, range, …etc Repetition of elements is allowed E.g., in a string, letter a can appear more than once Provides mapping from index to value Like in C, uses zero-based index Python provides many built-in sequence types Makes programming easier and you more productive Sequences are also objects have methods
6 Python String
Similar to C++ std::string Can be declared with single or double quote >> a = ‘hello “world”’ # no need to escape >> print(a) hello “world” >> print(“good \”bye\””) # need to escape good “bye” Strings are immutable Cannot be changed once assigned Copy is made for every operation
7 String Method
Remove whitespace from both sides >> “ hello ”.strip() ‘hello’ Checks if string ends with substring >> “hello world”.endswith(“world”) True C style format string >> “hello %s #%d”%(“world”, 42) ‘hello world #42’ Many more (look them up) E.g. lower, format, isspace, replace, …
8 Python List
Similar to C++ std::vector – more powerful Can place objects of different types within >> a = [ 1, 2.5, “hello” ] # common initialization >> list() # another way (empty) [] Lists are mutable, they can be updated >> a.pop() # remove last element and return it “hello” >> a [1, 2.5] >> a.append(3) # add element to end of list >> a [1, 2.5, 3] 9 Alias
Different names referring to same memory location Problem: update one implicitly changes the other Sometimes unintentional (frequent source of bugs) >> a = b = [] >> b = a # shallow copy of a >> a >> b[1] = 4 # update element [] >> a >> b [1, 4, 3] [] >> import copy >> a.append(5) >> d = copy.deepcopy(a) >> a >> d[0] = 5 [5] >> a Solution: make a >> b # why? [1, 4, 3] deep copy (instead [5] >> d of shallow copy) >> a = [1, 2, 3] [5, 4, 3] 10 List Methods
Insertion >> a = [9, 2, 3, 4, 3] >> a.insert(0, 6) # insert 6 to index 0 >> a [6, 9, 2, 3, 4, 3] Remove by index >> del a[1] # del expr is a statement >> a # a.pop(1) is an expression [6, 2, 3, 4, 3] Remove by value >> a.remove(3) # removes first occurrence of 3 >> a [6, 2, 4, 3]
11 String and List Methods
Tokenize >> “hello big world”.split(‘ ’) [‘hello’, ‘big’, ‘world’] Join a list of string using a delimiter
>> ‘-’.join([‘hello’, ‘big’, ‘world’]) ‘hello-big-world’ Merge with another list Sort list >> a = [5, 9] >> a = [5, 9, 1, 2] >> a.extend([1, 2]) >> a.sort() >> a >> a [5, 9, 1, 2] [1, 2, 5, 9]
12 Tuple
Same as list, except immutable (not exactly, more on this later) >> a = 1, 2, “hello”, 4 >> a (1, 2, “hello”, 4) >> a[1] = 7 TypeError: 'tuple' object does not support item assignment Can do neat tricks Swap Packing/Unpacking >> a = 3 >> foo() def foo(): >> b = 6 (5, 7) return 5, 7 >> a, b = b, a >> x, y = foo() >> a, b >> x (6, 3) 5
13 Common Operations
On Sequence Types
14 Index Operator
Returns nth element of the sequence syntax: sequence[n] >> b = [2, 3, 5, 7, 11, 13, 17] >> b[2] 5 >> b[7] IndexError: list index out of range >> b[-1] # returns last element 17 >> b[-8] IndexError: list index out of range For List (mutable), can update element >> b[-1] += 6
15 Slicing
Extracts subset of elements from sequence sequence[i:j:k], i: start, j: end k: step j th element is excluded from the slice >> b = [2, 3, 5, 7, 11, 13, 17] >> b[:2] # get 0th and 1st [2, 3] >> b[4:-1] # last element excluded [11, 13] >> b[4:] # last element included [11, 13, 17] >> b[::2] # skip every second element [2, 5, 11, 17] >> b[3::-1] # reverse list, from 4th element backwards [7, 5, 3, 2] 16 Relational Operator
Sequence types are compared by value >> b = “hello” >> b[:5] == “hell” True >> a = [1, 2, 3] >> a > [8, -9] # lexicographical order False Check for alias (compare by reference) is operator >> a = b = [1, 2, 3] >> a is c >> a is b False True >> a == c >> c = [1, 2, 3] True 17 Built-in Functions
Many operate on iterables Iterable An object that contains elements you can iterate through Go through each element one after another All sequence types are iterable! E.g. sorted – returns a list of sorted elements >> b = [5, 9, 1, 2] >> sorted(“bad”) >> sorted(b) # returns a copy [‘a’, ‘b’, ‘d’] [1, 2, 5, 9] >> sorted((3, 2, 1)) >> b [1, 2, 3] [5, 9, 1, 2]
18 Foreach loop
>> for n in [2, 3, 5]: # enumerate is a built-in .. print(n+2) # function; returns a tuple 4 >> s = “world” 5 >> for i, c in enumerate(s): 7 .. print(“%d: %s”%(i, c)) >> for c in “hello”: 0: w .. print(c.upper()) 1: o H 2: r E 3: l L 4: d L O
19 Membership Operator
Checks for existence of element >> 5 in [3, 6, “5”] False >> 5 in [5, “hello”, 3] True Check for absence of element >> ‘a’ not in “banana” False >> ‘seed’ not in “banana” True
20 Length Function
>> len([1, 2, 3, 4]) 4 >> len(“hello”) 5 >> len([]) 0
# in Python import sys # arguments to program stored here argc = len(sys.argv) # argc (C++) is length of sys.argv
// in C++ int main(int argc, const char * argv[]) { … 21 Repetition and Concatenation
>> “hello ” * 3 ‘hello hello hello ’
>> [0] * 4 # common used to initialize list [0, 0, 0, 0]
>> a = “hello” >> b = “world” >> a + “ ” + b # concatenate three strings ‘hello world’
>> [1, 2] + [3, 4] # concatenate two lists [1, 2, 3, 4]
22 List Comprehension
Creates sequence from an iterable In set-builder notation P(x) for x in iterable P(x) for x in iterable if F(x) P(x) if F(x) else Q(x) for x in iterable Where P, F, Q are expressions
>> [ str(i) for i in range(5) ] [‘0’, ‘1’, ‘2’, ‘3’, ‘4’]
>> matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] >> [ r[2] for r in matrix ] # 3rd element of sublists [3, 6, 9]
23 List Comprehension
Can loop through multiple iterables! # sieve of eratosthenes >> composite = [ j for i in (2, 3, 5, 7) \ .. for j in range(i*2, 50, i) ] >> tuple( x for x in range(2, 50) if x not in composite ) (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47) >> sorted(set(range(2, 50)) – set(composite)) [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
>> data = [ 1, -5, 3, 7, -7, -6, -4, 0, 9, -2 ] >> [ x if x >= 0 else –x for x in data ] [ 1, 5, 3, 7, 7, 6, 4, 0, 9, 2 ]
>> [ w for w in “lorem ipsum dolor sit”.split() if ‘i’ in w ] [‘ipsum’, ‘sit’] 24 ECE326 PROGRAMMING LANGUAGES
Lecture 5 : Dictionary and File
Kuei (Jack) Sun ECE University of Toronto Fall 2019 Dictionary
As known as associative array std::unordered_map in C++ Collection of key value pairs All keys must be unique Unordered You cannot sort a dictionary Implementation Hash table Search tree (e.g. red-black tree)
2 Dictionary
{ key1:value1, key2:value2, … } >> eng2sp = {‘one’: ‘uno’, ‘two’: ‘dos’, ‘three’: ‘tres’} >> eng2sp['one'] 'uno' >> eng2sp[4] # key not in dictionary KeyError: 4
>> eng2sp['four'] = 'cuatro’ # add key-value pair >> eng2sp {'one': 'uno', 'two': 'dos', 'three': 'tres', 'four': 'cuatro'}
>> 'two' in eng2sp # membership test on key True >> ‘dos’ in eng2sp # cannot use to check if value exists False 3 Key Requirement
Dictionary key must be hashable Related to immutable Tuple is hashable if it has no reference to mutable objects
>> d = dict() # creates an empty dictionary >> d[1,2] = “hi” # OK >> a = [1, 2] >> d[a] = “bye” # not OK, list is mutable TypeError: unhashable type: 'list‘ >> t = (1, a) >> t (1, [1, 2]) >> d[t] = “bye” # not OK, tuple contains a mutable object TypeError: unhashable type: 'list‘
4 Building Dictionary
zip built-in function Creates n m-tuples from m sequences of length n E ach element in tuple taken from same position of each sequence Lazy iterable: computes as you loop through >> a = zip(“abcd”, range(4), [1.5, 2.5, 3.5, 4.5]) >> a # zip is ‘lazy’ (so is range)
>> d = dict(zip(‘abcde’), range(5))) >> d.get(‘f’, -1) # avoids KeyError by providing default value -1
>> d.keys() dict_keys(['a', 'b', 'c', 'd', 'e']) >> d.values() dict_values([0, 1, 2, 3, 4])
>> ‘’.join(c for c in d) # loops through keys only ‘abcde’
>> for k, v in d.items(): # loops through (key, value) .. print(k+str(v), end=' ') a0 b1 c2 d3 e4
6 Dictionary Methods
>> d = dict(zip(‘abcdef’), range(6)))
>> del d[‘a’] # remove key ‘a’ and its value >> d {'b': 1, 'c': 2, 'd': 3, 'e': 4, 'f': 6}
>> d.pop(‘c’) # same as above and return its value 2 >> d {'b': 1, 'd': 3, 'e': 4, 'f': 6}
>> d.update(dict(zip(‘abc’, range(6,9)))) # ‘b’ gets new value >> d {'b': 7, 'd': 3, 'e': 4, 'f': 6, 'a': 6, 'c': 8}
7 Dictionary Comprehension
{ K(x):V(x) for x in iterable } And the other two forms Loop through unique s # make histogram of letters in string characters only >> s = ‘mississauga’ >> { c:s.count(c) for c in set(s) } {'u': 1, 'g': 1, 'a': 2, 's': 4, 'i': 2, 'm': 1} Convention for # keep only pairs where value > 3 “I don’t care” >> import random >> r = [ random.randint(0, 9) for __ in range(4) ] >> d = dict(zip(“abcd”, r)) >> { k:v for k,v in d.items() if v > 3 } {'b': 8, 'd': 5}
8 Function Arguments
Keyword arguments Specify an argument using parameter name Useful for skipping over default arguments def pizza(size=14, dough=“regular”, sauce=“tomato”, \ cheese=“mozzarella”, toppings=[]): …
# dough is regular mine = pizza(16, sauce=“alfredo”, cheese=“cheddar”)
# all arguments before toppings use default values yours = pizza(toppings=[“bacon”, “pepperoni”, “salami”])
9 Variadic Function
Allows you to take variable number of arguments Both positional and/or keyword arguments def foo(*args, **kwargs): print(args, kwargs)
>> foo(1, 2, bar=3, baz=4) ((1, 2), {'baz': 4, 'bar': 3})
# only accepts keyword arguments def foo(**kwargs): print(kwargs)
>> foo(1, 2) TypeError: foo() takes exactly 0 arguments (2 given) 10 Dynamic Programming
Divide and conquer Breaks down problem into sub-problems Solve sub-problems and combine results to form solution Caches (saves) result of function for future reuse Requires overlapping sub-problems Example: Fibonacci series F(n) = F(n-1) + F(n-2) = F(n-2) + F(n-3) + F(n-3) + F(n-4) = … Lots of overlapping sub-problems! 11 Bottom-Up Approach
Start from bottom-most unsolved sub-problem Solve its way up to the final solution max() returns max # bottom-up Fibonacci value in the iterable def fibonacci(n): if n not in fibonacci.table: # start from smallest unsolved sub-problem mx = max(fibonacci.table) + 1 for i in range(mx, n+1): # ends at i == n fibonacci.table[i] = fibonacci.table[i-1] + \ fibonacci.table[i-2] return fibonacci.table[n]
# static function variable! fibonacci.table = { 0 : 1, 1 : 1 } # f(0) = f(1) = 1 12 Memoization
Same as dynamic programming, except top-down Advantage (over dynamic programming) less computation if not all sub-problem needs to be solved Disadvantage Recursion requires more memory than tabulation Example: Prolog (logic programming language) ?- fibonacci(100, F). ERROR: Out of local stack :- table fibonacci/2 ?- fibonacci(100, F). F = 573147844013817084101. 13 Top-Down Approach
If sub-problem already solved, use result directly Else solve the sub-problem and add solution to table
# top-down Fibonacci def fibonacci(n): # assume n >= 0 if n in fibonacci.table: return fibonacci.table[n] else: v = fibonacci(n-1) + fibonacci(n-2) fibonacci.table[n] = v return v fibonacci.table = { 0 : 1, 1 : 1 }
14 Pure Function
Output solely determined by input to function Also cannot have side effects i.e. changing states outside of local environment e .g. modifying non-local variables, perform I/O, etc. Important in functional programming Referential transparency Replacing expression by its corresponding value does not change program behaviour Guaranteed from a pure function Requirement for memoisation/dynamic programming 15 Files
Use open built-in function >> f = open(“hello.txt”) # read-only mode, file must exist >> h = open(“io.h”, “w”) # write-only mode, file will be wiped Reading text files
>> for line in f: # file objects are iterable .. print(line) Writing text files
>> h.write(“hello world\n”) # add a new line to sentence Close file (after finished) >> f.close()
16 Error Handling
Deals with runtime error without crashing Need to disrupt int i; Dir * d = malloc(sizeof(Dir)*NUM_DIRS); normal execution if (d == NULL) goto fail; flow for (i = 0; i < NUM_DIRS; i++) { if (!(d[i] = alloc_dir())) Example: goto fail_d; goto /* do stuff with d[i] */ statement } in C return 0; fail_d: for (i-- ; i >= 0; i--) free_dir(d[i]); free(d); fail: return –ENOMEM; 17 Error Handling
C++/Python: try statement Jumps to exception handler on error May need to unwind stack frames (function calls) Can be expensive (C++ compile option --fno-exceptions) try: f = open(“hello.txt”) except OSError as err: print(err) else: # if no error occurs print(f.read()) # this reads everything f.close()
18 With Statement
Some objects have pre-defined clean-up actions Special __exit__ method Makes code look much cleaner
# close called automatically when exiting block
with open(“hello.txt”) as f: print(f.read())
# Note: f still in scope here (but is closed)
19 User-Defined Exception
Create a class derived from base Exception class More on creating class in future lectures
class MyError(Exception): pass
>> raise MyError(“It’s bad”) # raise your own exception __main__.MyError: It's bad
pr = analyze_move(mv) if pr > 1.0: # use built-in exception raise ValueError(“probability can’t be > 1!”)
20 Multiple Exceptions def baz(): try: foo() # exceptions can be raised from inside bar() # a function call for caller to handle … except (KeyError, ValueError): # deal with these two the same way return 0 except OSError as err: print(err) return -1 except: print(“unexpected exception!”) raise # re-raise the exception to … # caller of this function
21 ECE326 PROGRAMMING LANGUAGES
Lecture 6 : Review and Python Tidbits Kuei (Jack) Sun ECE University of Toronto Fall 2019 Course Prerequisite
ECE244: Programming Fundamentals ECE297: Design and Communication Not a prerequisite ECE302: Probability and Applications ECE345: Algorithms and Data Structures
2 Assignment 2
Released next week (postponed by 1 week) Will use basic concepts from high school Probability Expected Value Dynamic Programming Intent of the course Practical, programmer perspective i.e. save result of function and reuse on same argument Not part of the course Theory: e.g. asymptotic runtime complexity analysis, amortized … 💤💤 3 Result of Class Poll
4 Updates to Assignments
Before After
Assignment Start End Marks Start End Marks 1 Sept 9 Sept 29 5% Sept 9 Oct 6 7% 2 Sept 16 Oct 9 6% Sept 23 Oct 20 8% 3 Oct 7 Nov 10 7% Oct 14 Nov 17 8% 4 (50% easier) Nov 4 Dec 8 8% Nov 11 Dec 8 7% Quiz 4% Bonus 5%
5 Code Organization
C/C++ Files (usually) separated into source and header Header: contains class definition and function prototypes Meant to be exported, i.e. used by others Source: contains function definition Sometimes static functions and opaque user-defined types Information Hiding Reduces external complexity Prevents user from making access outside of provided interface Results in better abstraction and a stable interface
6 Static Function
Function only available in the file it is defined in Similar to private member functions But not even specified in the header file Completely hidden from external user foo.c static int foo_helper(int a) { /* complex calculation */ } int foo(int x, int y) { return foo_helper(x) + foo_helper(y); } foo.h int foo(int x, int y); /* no one knows about foo_helper */
7 Opaque Data Type
Data type only declared, not defined Concrete representation hidden from its users list.c struct Node { int value; struct Node * next; }; struct List { struct Node * head; }; struct List * create_list(void) { struct List * list = malloc(sizeof(struct List)); list->head = NULL; return list; } list.h Returns an opaque pointer struct List; struct List * create_list(void); struct List * add_to_list(struct List * list, int value); 8 Code Organization
Module A Python source file or directory Contains a collection of definitions and statements Prevents name conflict E.g. math.abs vs. bodybuilder.abs Similar to C++ namespace Import To gain access to definitions and functionalities of a module No information hiding, everything is accessible Use name of file (minus the .py) E,g, to import foo.py, use import foo File executed when importing
9 Python Idiom
Avoid execution if imported as module foo.py print(“hello world”) def main(): pass # will not run if imported as module if __name__ == “__main__”: main()
bar.py (we run this file) import foo # prints “hello world” print(type(foo)) # prints
A Python statement Can be called anywhere in code Convention: prefer at top of source file Optimization: avoid import until just before use
def unlikely_called_function(): import huge_module huge_module.do_something() Python tracks which module already imported Same module will not be re-imported
11 Import
Import functions and types into local namespace Don’t have to prefix with module name
from collections import namedtuple, OrderedDict, deque ordered = OrderedDict(zip(“abcde”, range(5)))
# OrderedDict([('a', 0), ('b', 1), ('c', 2), ('d', 3), ('e', 4)]) print(ordered)
#
12 Import
A directory can also be imported (a.k.a package) Must have a special file named __init__.py Good way to organize very large code base mydir/__init__.py from mydir.foo import bar print(“hello import”) mydir/foo.py def bar(): print(“hello bar”) >> import mydir hello import >> mydir.bar() # imported definition is exported hello bar 13 Default Argument
Default value assigned to missing arguments In C++, default arguments always recreated
struct A { int main() { int x; foo(); A() : x(0) {} foo(); ~A() { return 0; cout << "destroyed\n"; } } }; $ ./foo 0 void foo(A a=A()) { destroyed cout << a.x << endl; 0 a.x = 5; destroyed } 14 Default Argument
In Python, only evaluated once, when defined Beware of mutable default arguments!
def add_topping(budget, toppings=list()): if budget > 4.99: budget -= 4.99 toppings.append(“chipotle steak”) … return toppings
>> pizza1 = add_topping(5) >> pizza2 = add_topping(4) >> add_topping(3) [‘chipotle steak’, ‘grilled chicken’, ‘broccoli’]
15 Default Argument
Workaround Convention: use None Similar to NULL in C/C++
def add_topping(budget, toppings=None): if toppings is None: toppings = list() … return toppings
>> pizza1 = add_topping(5) >> pizza2 = add_topping(4) >> add_topping(3) [‘broccoli’]
16 Function Scope
Local variable valid until end of function Can be defined within nested block Usable outside of block where definition occurred Global variable Reassignment requires use of global statement MUSIC = [ “Pop”, “EDM” ] def retro(): global MUSIC MUSIC = [ “Classic”, “Jazz” ] >> retro() >> print(MUSIC) [‘Classic’, ‘Jazz’]
17 Function Scope
Global variable Read and update are permitted without global
MUSIC = [ “Pop”, “EDM” ] def retro(): # empties the list and re-populate it MUSIC.clear() MUSIC.extend([ “Classic”, “Jazz” ])
>> retro() >> print(MUSIC) [‘Classic’, ‘Jazz’]
18 Scope and Except
An “exception” to function scope Exception variable is deleted at end of block
def try_fail(): e = "hello" try: a = range(2) print(a[3]) except IndexError as e: print(e)
# NameError: name 'e' is not defined print(e + " world")
19 Scope and Except
Why the “exception”? Exception variable interferes with garbage collection Potentially large amount of memory cannot be reclaimed until exception variable is deleted Technical detail in future lecture
Workaround keep_me = None If you want to keep it, try: … reassign it to another except IndexError as e: variable keep_me = e # range object index out of range print(keep_me)
20 List Comprehension
Creates sequence from an iterable Form 1: P(x) for x in iterable map function: Applies function, e.g. P(x), to all elements R eturns a list of results in the same order # function: P(x) def map(function, iterable): output = [] for element in iterable: output.append(function(element)) return output 21 List Comprehension
Form 2: x for x in iterable if F(x) filter function: From input iterable, returns only elements that satisfies F(x) i.e. when F(x) returns true Original ordering is preserved
def filter(function, iterable): output = [] for element in iterable: if function(element): # if F(x) is true output.append(element) return output 22 List Comprehension
>> [ x*x for x in range(1, 6) ] Imperative [1, 4, 9, 16, 25] Programming >> [ x for x in range(10) if x%2 ] Style [1, 3, 5, 7, 9]
def square(x): Functional return x * x Programming >> map(square, range(1, 6)) [1, 4, 9, 16, 25] Style
def odd(x): return x%2 != 0 >> filter(odd, range(10)) [1, 3, 5, 7, 9] 23 ECE326 PROGRAMMING LANGUAGES
Lecture 7 : Object-Oriented Programming Kuei (Jack) Sun ECE University of Toronto Fall 2019 Object-Oriented Programming
Object Contains both state (data) and behaviour (code)
Data Code C++ Member variable Member function Java Field Method Python Data attribute Method Models real world things and concepts Provides encapsulation E.g. restricts direct access to some parts of an object
2 Object-Oriented Programming
Sending messages object maps messages to values. Responds to a message by looking up the corresponding value Class A “blueprint” to create objects of the same behaviour Can create instances of a class, which determines their type Instance Manifestation of an object Emphasizes the distinct identity of the object
3 Object-Oriented Programming
Class-based programming Inheritance occurs by deriving classes from existing ones i.e. occurs through subtyping E.g. Student is a subclass of Person Prototype-based programming Inheritance occurs by copying existing object and adding to it Individual objects would be cloned from the prototype E.g. Student object is Person object with an extra ID field jack instance is created by copying student object and setting the names and ID fields to real values (instead of the defaults)
4 Object-Oriented Programming
Class-based programming Usually used by statically-type languages: e.g. C++, Java Prototype-based programming Usually only possible for dynamically-typed languages Only one stands out: JavaScript Python Class-based programming BUT Can support prototype-based programming
5 Python Class
Declared via class keyword Can be completely empty Same as C++, but MUCH MORE POWERFUL
class Foo: pass >> a.baz AttributeError: Foo instance # create an instance of Foo has no attribute 'baz' >> a = Foo() >> type(a) # unlike C++, you can ADD
Special __init__ method Self variable Variable that references the current instance (this in C++) Must be first parameter of all instance methods
class Point: def __init__(self, x, y): self.x = x self.y = y
# create an instance of Point (calls Point.__init__) >> a = Point(3, 4) >> a.x 3 7 Instance Method
Always takes class instance variable as first argument
self: named by convention, denotes ‘this’ instance
class Point: … def distance(self, other): dx = self.x – other.x dy = self.y – other.y return math.sqrt(dx*dx + dy*dy)
>> a = Point(4, 5) >> b = Point(1, 1) >> a.distance(b) # a is the first argument to distance() 5.0 8 Attribute
In Python, means either data members or methods Can add new ones after __init__ Although it may confuse other programmers
class Point: def move(self, dx, dy): self.x, self.y = self.x + dx, self.y + dy self.moved = True
>> a = Point(0, 0) >> a.moved AttributeError: ‘Point’ object has no attribute ‘moved’ >> a.move(5, 5) >> a.moved True 9 Attribute
Can also remove attributes
class Point: def move(self, dx, dy): self.x, self.y = self.x + dx, self.y + dy self.moved = True if self.x == 0 and self.y == 0: del self.moved
>> a = Point(3, 2) >> a.move(-3, -2) >> a.moved AttributeError: ‘Point’ object has no attribute ‘moved’
10 dir
Convenience function to learn about an object Returns list of attributes Use this as a way to debug your program
class A: def __init__(self): pass
>> dir() [‘A’, ‘__builtins__’, ‘__name__’, …]
>> dir(A) [‘__init__’, ‘__class__’, '__delattr__', '__dict__', …]
11 __dict__
Let’s dig a little deeper into how an instance works
>> a = [1, 2, 3] >> a.x = 5 AttributeError: 'list' object has no attribute 'x'
>> b = Foo() >> b.x = 5 >> b.__dict__ {‘x’: 5} >> a.__dict__ AttributeError: 'list' object has no attribute '__dict__'
>> b.__dict__[‘y’] = “hello” >> b.y “hello” 12 __slots__
Denies creation of __dict__ P revents instance from accepting new attributes Evil: can put __dict__ into __slots__ Also reduce memory consumption
class Point: __slots__ = (‘x’, ‘y’) def __init__(self, x=0, y=0): # uses default arguments self.x, self.y = x, y
>> a = Point() >> a.z = 5 AttributeError: ‘Point’ object has no attribute ‘z’
13 Encapsulation
There is no private keyword in Python Cannot (easily) protect attributes from direct access Convention Start name of attribute with underscore _ Tells other programmers: “it is private (pretty please)” class Submarine: def _launch_missile(self): … def try_launch_missile(self, password): if self.password == password: self._launch_missile() else: raise PermissionError(“access denied”) 14 Class Attribute
In Python, class is also an object An instance can access its class’s attributes That’s how it calls methods defined in the class It can also access class member variables
class Point: origin = (0, 0) def __init__(self, x=0, y=0): self.x, self.y = x, y
>> a = Point() >> a.origin (0, 0)
15 Class Attribute
Class attributes are read only by their instances Reassignment bounds new attribute to the instance Typically not what you want to do
class Point: origin = (0, 0) # static class variable in C++
>> a, b = Point(), Point() >> a.origin = (1, 1) # now local to a >> a.origin (1, 1) >> b.origin # still refers to Point.origin (0, 0) >> Point.origin # not changed by a (0, 0) 16 Instance Method
When instance calls functions defined by its class, it passes itself in as the first argument Alternatively, you can do it manually
class Point: def move(self, dx, dy): …
>> a = Point() # (0, 0) >> Point.move(a, 3, 4) # (3, 4) >> func = Point.move # similar to member function pointer >> func(a, 2, 2) >> a.x, a.y (5, 6) 17 Class Method
Takes class as first argument
Use @classmethod decorator (more on this later)
class Point: origin = (0, 0) @classmethod def debase(cls, dx, dy): cls.origin = (dx, dy)
>> a = Point() >> a.origin (0, 0) >> Point.debase(3, 7) >> a.origin (3, 7) 18 Ad-Hoc Polymorphism
Ability for an entity to behave differently based on input or contained types Function Overloading Functions of same name with different implementations Not supported by Python (why?) Operator Overloading Operator has different implementation based on operand(s) Allows use of notation/syntax similar to basic types E.g. use a + b to add two complex numbers instead of a.add(b)
19 Operator Overloading
In Python, almost every operator has a corresponding special method that can be invoked if defined class Complex: def __init__(self, r=0, i=0): self.r, self.i = r, i def __add__(self, other): if isinstance(other, (int, float)): self.r += other elif isinstance(other, Complex): self.r += other.r self.i += other.i return self
>> Complex(3, 2) + 1.5 (4.5, 2) # I cheated 20 __str__
Converts objects to string Automatically done when passed to print class Complex: # continue from previous example def __str__(self): if self.i == 0: return str(self.r) elif self.i > 0: return "{} + {}i".format(self.r, self.i) elif self.i < 0: return "{} - {}i".format(self.r, -self.i) else: return "{}i".format(self.i)
>> print(Complex(3, -2)) 3 – 2i 21 Index Operator
class Bitmap: def __init__(self, data=0): self.data = data def __getitem__(self, idx): return self.data & (1 << idx) def __setitem__(self, idx, val): if val: self.data |= (1 << idx) else: self.data &= ~(1 << idx)
>> bm = Bitmap(0xE3) >> print(bm[4]) >> bm[0] = 0 0 >> bm[4] = 1 >> print(bm[1]) >> print("0x%x"%bm.data) 2 0xf2 22 Operator Overloading
Relational Operators __eq__, __ne__, …etc Reverse Arithmetic Operators Used when instance at right side of operator E.g. 3 + Complex(-1, 2) __radd__, __rsub__, __rmul__, …etc Assignment Operator Performs name binding in Python Cannot be overloaded
23 ECE326 PROGRAMMING LANGUAGES
Lecture 8 : Inheritance and Runtime Polymorphism Kuei (Jack) Sun ECE University of Toronto Fall 2019 Subtyping
In some languages, means same thing as inheritance However, subtyping inherits only the interface Forms a strict “is-a” relationship Allows for substitution where parent type is expected Has semantic relationship with parent type E.g. Dog being a subtype of Animal has semantic meaning Inheritance Parent class may have no semantic relationship E.g. Chat class inherits from Network class to gain its implementation for making connections to other participants over the Internet
2 Inheritance
Creates new class (subclass) based on existing one(s) Acquires all attributes and behaviours of parent (base class) Retains all existing implementation Enables code reuse Can replace (override) existing implementation Can extend to support new behaviours Add more functionality Used interchangeably Subclass – child class – derived class Super class – parent class – base class
3 Inheritance in Python
class Animal: def __init__(self, age, weight): self.age, self.height = age, height def move(self, location): print(“It moved to %s”%location)
class Dog(Animal): def __init__(self, age, weight, name=“Marley”): self.age, self.height = age, height self.name = name def move(self, location): print(“%s moved to %s”%(self.name, location))
>> dog = Dog(5, 35.2) >> dog.move(“the park”) Marley moved to the park 4 Runtime Polymorphism
Choosing behaviour through single interface at runtime E.g. C++ virtual function Decides which implementation of a virtual function to call There are no non-virtual functions in Python Also no pure virtual functions (why?) Everything can be overridden By child class or by manual manipulation Sometimes by accident If a child class has an attribute of the same name, it will take precedence over the parent’s
5 super
Allows accessing attribute of the super class without having to specify “which” super class Very important for multiple inheritance
class Dog(Animal): def __init__(self, age, weight, name=“Marley”): Animal.__init__(self, age, weight) self.name = name
class Dog(Animal): def __init__(self, age, weight, name=“Marley”): super().__init__(age, weight) self.name = name
6 Dynamic Dispatch
A polymorphic operation has different implementations Dynamic dispatch determines which to call at runtime based on context Context can include caller’s type and input types Static Dispatch Ad-hoc polymorphism Knows which function to call based on their signatures Can be done at compile-time E.g. function overloading, operator overloading, …etc
7 Single Dispatch
C++ virtual functions Context is based solely on type of instance Not the reference type of the variable struct A { B b = B(); virtual void foo() { cout << “A::foo\n”; // reference type is A } // instance type is B }; A * ap = &b;
struct B : public A { // prints B::foo virtual void foo() override { ap->foo(); cout << “B::foo\n”; } }; 8 Override Keyword
Available since C++11 Compile-time error if virtual function does not override Helps detect unexpected bugs on signature change struct A { virtual void foo(long a) { cout << “A::foo ” << a << endl; } Error: B:foo marked }; ‘override’ but does struct B : public A { not override virtual void foo(int a) override { cout << “B::foo” << a << endl; } }; 9 Final Keyword
Error when attempting to override a final function Helps prevent accidental overriding
struct B : public A { virtual void foo(long a) final { cout << “B::foo ” << a << endl; } Error: overriding }; final function struct C : public B { B::foo() virtual void foo(long a) { cout << “C::foo” << a << endl; } };
10 Final Keyword
Error when attempting to inherit from a final class
struct B final : public A { virtual void foo(long a) { cout << “B::foo ” << a << endl; } }; Error: cannot derive from final struct C : public B { base ‘A’ … };
11 Virtual Table
Implements dynamic dispatch in C++ A lookup table to resolve virtual function calls Implemented as an array of function pointers
struct A { virtual void foo() {} virtual void bar() {} }; struct B : public A { virtual void foo() override {} }; struct C : public A { virtual void bar() override {} }; 12 Virtual Table
For each class in the hierarchy, a VTable is created A::vtable struct A { virtual void foo() {} A::foo virtual void bar() {} A::bar }; B::vtable struct B : public A { virtual void foo() override {} B::foo A::bar }; struct C : public A { C::vtable virtual void bar() override {} A::foo }; C::bar
13 Virtual Table Pointer
In the base class, a hidden pointer is added Both base and derived class will have this pointer (why?)
struct A { /* points to array of member * function pointers */ void * __vptr; virtual void foo() {} virtual void bar() {} };
14 Virtual Table Pointer
During instantiation, it will be set based on its type __vptr will point to the corresponding A::vtable virtual table for its instance type A::foo A::bar struct A { void * __vptr; }; B::vtable B::foo struct B : public A { A::bar void * __vptr; // inherited }; C::vtable struct C : public A { A::foo void * __vptr; // inherited C::bar }; 15 Virtual Function Call
Instead of calling function directly, goes through VTable Each virtual function has a fixed index in Vtable Index based on order of appearance in class definition Use __vptr and index to call the actual function
B b = B(); A * ap = &b;
// goes through virtual table, calls B::foo ap->foo();
// the actual implementation // uses syntax for member function pointer call (ap->*__vptr[0])(); 16 Multiple Dispatch
Context also includes input parameter types Julia – dynamically-typed just-in-time compiled language Used by scientific communites for its high performance Example Suppose we have a polymorphic function, eat, where an instance of type Animal eats some an instance of type Food
abstract type Animal end abstract type Food end
eat(eater:Animal, meal:Food) = println(“yum!”)
17 Multiple Dispatch
Now let’s add some real animals and food…
struct Dog <: Animal end struct Carrot <: Food end struct Lion <: Animal end struct Beef <: Food end struct Sheep <: Animal end We want to make sure some animals reject some food
eat(eater:Lion, meal:Carrot) = println(“yuk!”) eat(eater:Sheep, meal:Beef) = println(“puke!”)
>> kimba = Lion() >> fido = Dog() >> eat(kimba, Carrot()) >> eat(fido, Beef()) yuk! yum!
18 Multiple Dispatch
How to do the same thing in C++? struct Animal { virtual void eat(Carrot * meal) { cout << “yum!\n”; } virtual void eat(Beef * meal) { cout << “yum!\n”; } /* Question: why won’t ‘eat(Food * meal)’ work? }; struct Lion : public Animal { virtual void eat(Carrot * meal) { cout << “yuk!\n”; } }; … What if there were more animals and food? What if we add other parameters? (e.g. time of day) 19 Multiple Dispatch
Emulating multiple dispatch in C++ Use N-dimensional array of function pointers enum Animal_ID { enum Food_ID { DOG = 0, CARROT = 0, LION = 1, BEEF = 1, SHEEP = 2, }; }; void (* matrix[3][2])(Animal *, Food *) = { { animal_eat_food, animal_eat_food, }, { lion_eat_carrot, animal_eat_food, }, { animal_eat_food, sheep_eat_beef, }, }; // both Animal and Food class have associated ID field void eat(Animal * a, Food * f) { matrix[a->animal_id][f->food_id](a, f); } 20 Late Binding
Associates name with an operation at runtime Type unknown until use (e.g., evaluation) Early Binding Type is known at time of instantiation Usage of term sometimes conflated Can mean dynamic dispatch or “duck typing” (defer to later lecture) Quote from father of OOP “OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.” – Alan Kay
21 Late vs. Early
class Point: def __str__(self): return str((self.x, self.y)) def move(self, dx, dy): self.x, self.y = self.x + dx, self.y + dy Late Binding Early Binding >> a = Point(2, 3) >> a = Point(2, 3) >> def move2(self, x, y): >> def move2(self, x, y): .. self.x = x … .. self.y = y # assume you can do this >> Point.move = move2 >> Point.move = move2 >> a.move(5, 5) >> a.move(5, 5) >> print(a) >> print(a) (5, 5) (7, 8) 22 Binding vs. Dispatch
Both are examples of runtime polymorphism Late binding is concerned with the object Calling method by name Name resolved to method by object type Object behaviour can change after instantiation Dynamic dispatch is concerned with the operation Calling method by context Context determines which implementation to call Object behaviour remains the same after instantiation
23 ECE326 PROGRAMMING LANGUAGES
Lecture 9 : Inheritance in C++ Kuei (Jack) Sun ECE University of Toronto Fall 2019 Single Inheritance
All object-oriented languages supports it Derived class can only inherit from one base class Java only supports single inheritance Simplifies compiler implementation
2 Object Layout
Used by all compiled languages that support inheritance struct A { higher memory address On 64-bit architecture, int x; structures are 8-bytes aligned int y; C }; struct z: 8 bytes sizeof(C) -> 32 struct B : public A { padding: 4 bytes char m[12]; m: 12 bytes sizeof(B) -> 20 }; y: 4 bytes sizeof(A) -> 8 struct C : public B { x: 4 bytes long z; };
3 Alignment
Some architectures have data alignment requirements E.g. A 64-bit integer must be 8 bytes aligned 0xFFFFCC00 is 8-byte aligned but 0xFFFFCC02 is not Unaligned data requires extra instructions to re-align Padding An unnamed structure member to align subsequent fields Note: C/C++ does not allow reordering Fields must be placed in order of appearance in structure definition Disable padding Add __attribute__((packed)) to structure definition
4 Packed
struct Loose { struct Tight { int x; int x; /* 4-bytes padding */ long y; long y; char z[10]; char z[10]; long w; /* 6 bytes padding */ } __attribute__((packed)); long w; }; sizeof(Tight) -> 30 sizeof(Loose) -> 40
5 Abstract Base Class
Contains one or more pure virtual function(s) Pure virtual function Declared, but not defined (implemented) Cannot be instantiated
struct Shape { /* pure virtual function */ virtual double area() const=0; /* normal virtual function */ virtual const char * get_name() const { return “Shape”; } }; 6 Virtual Table
Shape::vtable Cannot have nullptr in vtable, cannot instantiate these classes nullptr ∴ Shape::get_name struct Shape { virtual double area() const=0; virtual const char * get_name() const; Polygon::vtable ; nullptr Shape::get_name struct Polygon : public Shape { nullptr virtual int sides() const=0; }; Rect::vtable struct Rect : public Polygon { Rect::area double w, h; Shape::get_name virtual double area() const { return w*h }; Rect::sides virtual int sides() const { return 4; } }; 7 Multiple Inheritance
Derived class has two or more base classes Use Cases: Support for multiple interfaces E.g. Amphibian class is both a Terrestrial and a Swimmer Implementation inheritance Base class inherited for its implementation (code reuse) E.g. Actor class is a Person, and borrows implementation from Singer Introduces possibility of ambiguity E.g. both Cowboy and Painter have a draw function Special NPC character Joe is both a Cowboy and a Painter
8 Object Layout
Base classes are stacked by order of appearance struct A { higher memory address class B is placed in int p; the middle! int q; }; struct C struct B { t: 8 bytes char s[16]; s: 16 bytes }; q: 4 bytes struct C : public A, public B { p: 4 bytes long t; };
9 Resolving Ambiguity
When accessing members of same name from different base classes, must specify which base class tDoes no check function signature struct Cowboy { Joe joe = Joe(); void draw(Target *); }; // error: request for member // ‘draw’ is ambiguous struct Painter { joe.draw(canvas); void draw(Canvas *); }; // ok – base class specified joe.Painter::draw(canvas); struct Joe : public Cowboy, public Painter { // ok – base class specified … joe.Cowboy::draw(victim); }; 10 Upcasting
Casting a more specific type to a more generic type i.e. from a subclass to a super class struct A { /* single inheritance */ int x; /* &c == 0xffffcbf0 */ int y; C c = C(); }; /* ap == 0xffffcbf0 */ A * ap = &c; struct B : public A { char m[16]; /* bp == 0xffffcbf0 */ }; B * bp = &c; struct C : public B { Upcasting results in the long z; same pointer location }; 11 Upcasting
Casting a more specific type to a more generic type i.e. from a subclass to a super class Upcasting results in the struct A { higher memory address int x; same pointer location int y; }; struct C struct B : public A { z: 8 bytes char m[16]; m: 12 bytes }; &c, ap, bp y: 4 bytes struct C : public B { x: 4 bytes long z; }; 12 Upcasting
For multiple inheritance, upcasting may require shifting of memory address /* multiple inheritance */ struct A { C c = C(); int p; Upcasting results in the A * ap = &c; int q; different memory address B * bp = &c; }; struct B { struct C char s[16]; t: 8 bytes }; s: 12 bytes bp struct C : public A, public B { q: 4 bytes &c, ap long t; p: 4 bytes }; 13 Downcasting
Casting a generic type to a specific type i.e. from a super class to a subclass Can be potentially dangerous (type unsafe) if coerced Type coercion: forcefully cast one type to another Requires special cast operator: e.g. reinterpret_cast Single inheritance Safe as long as type is correct Multiple inheritance Requires pointer offset
14 Runtime Type Information
Exposes information about type of object at runtime Adds runtime overhead, can be turned off Allows for type-safe downcasting dynamic_cast Attempts to cast, return nullptr if not type safe Offsets pointer correctly for multiple inheritance Penguin p = Penguin(); Animal * ap = &p; // success, pp is a valid pointer Penguin * pp = dynamic_cast
Appears more than once during inheritances struct Person { const char * name; }; struct Student : struct Teacher : public Person { public Person { int student_id; int class_id; }; }; struct TA : public Student, public Teacher { int hours; };
16 Repeated Base Class
By default, multiples copies of base class is made struct Person { const char * name; TA::hours }; Teacher::room struct Student : public Person { Person::name int id; }; Student::id Person::name struct Teacher : public Person { int room; }; struct TA : public Student, public Teacher { int hours; };
17 Ambiguous Base
Occurs when trying to access members of repeated base class – requires disambiguation TA::hours TA ta = TA(); Teacher::room // error: ‘Person’ is an ambiguous Person::name // base of ‘TA’ Student::id Person * pp = &ta; Person::name
// following two lines are fine Person * teacher = (Teacher *)&ta; Person * student = (Student *)&ta;
teacher.name = “Jack”; // Both teacher and student student.name = “Bob”; // have their own copy of name 18 Diamond Problem
When repeated base classes are undesirable Each parent class has its own copy of common base class Causes ambiguity, even after disambiguation! What we want is shared common base class
Person
Student Teacher
TA
19 Virtual Base Class
Allows common base class to be shared struct Person { const char * name; }; Person struct Student : virtual public Person { TA int id; Teacher }; Student struct Teacher : virtual public Person { int room; }; struct TA : public Student, public Teacher { int hours; };
20 Object Layout
virtual base class now higher memory address placed at top of struct TA
struct TA TA::vtable Person::name virtual base offset = 32 TA::hours offset to top = 0 Teacher::room typeinfo = typeinfo(TA) Teacher::__vptr ------virtual base offset = 16 padding: 4 bytes offset to top = 16 Student::id typeinfo = typeinfo(TA) Student::__vptr
higher memory address 21 Upcasting
To access virtual base class from one of the parent classes, consult virtual base offset in table
1. TA ta = TA(); 2. Student * student = &ta; 3. student->name = “Jack”; struct TA Person::name +32 // locate student object (offset = 0) TA::hours 2. student = &ta + vtable.top_offset; +28 Teacher::room // locate shared person object (offset = 32) Teacher::__vptr 3. _person = student + vtable.vbase_offset; // locate name field in person object padding: 4 bytes +16 // (offset 0, first field) Student::id __name = _person + 0; Student::__vptr // set name field 0 *__name = “Jack”; 22 Upcasting
1. TA ta = TA(); 2. Teacher * teacher = &ta; 3. teacher->name = “Jack”; struct TA Person::name +32 // locate teacher object (offset = 16) TA::hours 2. teacher = &ta + vtable.top_offset; +28 Teacher::room // locate shared person object (offset = 16) Teacher::__vptr 3. _person = teacher + vtable.vbase_offset; // locate name field in person object padding: 4 bytes +16 // (offset 0, first field) Student::id __name = _person + 0; Student::__vptr // set name field 0 *__name = “Jack”;
23 Downcasting
Downcasting in multiple inheritance requires vtable If base class is not virtual, cannot downcast
Person * person = new TA(); // error: source type ‘person’ is not polymorphic Student * student = dynamic_cast
struct Person { const char * name; virtual ~Person() {} };
24 Object Layout higher memory address TA::vtable struct TA virtual base offset = 32 Person::name offset to top = 0 Person::__vptr typeinfo = typeinfo(TA) TA::hours ------virtual base offset = 16 Teacher::room offset to top = 16 Teacher::__vptr typeinfo = typeinfo(TA) padding: 4 bytes ------virtual base offset = 0 Student::id offset to top = 32 Student::__vptr typeinfo = typeinfo(TA)
higher memory address 25 ECE326 PROGRAMMING LANGUAGES
Lecture 10 : Composition and Mixins Kuei (Jack) Sun ECE University of Toronto Fall 2019 Fragile Base Class
Changing base class can break derived classes Root cause Implementation inheritance breaks encapsulation Subclass depends on implementation of base class Solution? Never change the base class Author of base class must control all its subclasses E.g. all subclasses are contained within same module Don’t use implementation inheritance
2 New Interface Problem
/* Dictionary with key-value pair type (cstring, int) */ class Dict { /* some sort of hash table implementation */ public: /* both functions return true on success, false on fail */ virtual bool set(const char * key, int value); bool get(const char * key, int & value); }; /* Dictionary that only stores prime numbers */ struct PrimeDict : public Dict { virtual bool set(const char * key, int value) override { if (!is_prime_number(value)) return false; return Dict::set(key, value); } }; 3 New Interface Problem
What happens if we add a new interface?
class Dict { … public: virtual bool set(const char * key, int value); bool get(const char * key, int & value);
/* new interface */ int & operator[](const char * key); }; We just opened a backdoor to bypass the prime number check made by PrimeDict::set
4 Override Problem class List { // list of integers public: /* both functions return true on success, false on fail */ virtual bool add(int value); virtual bool extend(const List & other); }; /* Stores statistics on the numbers in the list */ struct StatsList : public List { int prime_counter = 0; // and other stats… virtual bool add(int value) override { if (is_prime_number(value)) prime_counter++; return List::add(value); } virtual bool extend(const List & other) override { for (iterator it = other.begin(); it != other.end(); it++) if (is_prime_number(*it)) prime_counter++; return List::extend(other); } 5 }; Override Problem
What if List::extend uses List::add? bool List::extend(const List & other) { for (iterator it = other.begin(); it != other.end(); it++) if (!this->add(*it)) return false; return true; } bool StatsList::add(int value) { if (is_prime_number(value)) prime_counter++; return List::add(value); } bool StatsList::extend(const List & other) { for (iterator it = other.begin(); it != other.end(); it++) if (is_prime_number(*it)) prime_counter++; return List::extend(other); } 6 Override Problem
What if List::extend uses List::add? bool List::extend(const List & other) { for (iterator it = other.begin(); it != other.end(); it++) if (!this->add(*it)) return false; return true; prime counter } is incremented twice! bool StatsList::add(int value) { if (is_prime_number(value)) prime_counter++; return List::add(value); } bool StatsList::extend(const List & other) { for (iterator it = other.begin(); it != other.end(); it++) if (is_prime_number(*it)) prime_counter++; return List::extend(other); } 7 New Implementation Problem class Album { std::vector
/* allows for removal of photos from album */ struct SecureAlbum : public Album { void remove(const Photo & photo) { current.erase(std::remove(current.begin(), current.end(), photo), current.end()); archived.erase(std::remove(archived.begin(), archived.end(), photo), archived.end()); } }; 8 New Implementation Problem
What if we made album track location and people?
using namespace std; class Album { vector
9 Solution
Disallow inheritance final keyword Prevents class from being inherited Alternative: composition Instead of subclassing, make it a field in your class Instead of is-a, now a has-a Forward methods to contained instance Treats existing class as a blackbox No more dependency of implementation Enforces encapsulation
10 New Interface struct Dict { bool set(const char * key, int value); Adding new bool get(const char * key, int & value); interface to Dict int & operator[](const char * key); does not affect }; the interface of class PrimeDict { PrimeDict Dict dict; public: bool set(const char * key, int value) { if (!is_prime_number(value)) return false; return dict.set(key, value); } bool get(const char * key, int & value) { return dict.get(key, value); Forwarding } }; 11 No Override struct List { bool add(int value); bool extend(const List & other); Avoid changing behaviour of base }; class when overriding methods by using composition instead. class StatsList { List list; Question: Is there a trade-off here? int prime_counter = 0; public: bool add(int value) { if (is_prime_number(value)) prime_counter++; return list.add(value); } bool extend(const List & other) { for (iterator it = other.begin(); it != other.end(); it++) if (is_prime_number(*it)) prime_counter++; return list.extend(other); } }; 12 No Override struct List { bool add(int value); bool extend(const List & other); Answer: the extend function }; will no longer accept StatsList class StatsList { as an argument because it is List list; not a subclass of List. int prime_counter = 0; public: bool add(int value) { if (is_prime_number(value)) prime_counter++; return list.add(value); } bool extend(const List & other) { for (iterator it = other.begin(); it != other.end(); it++) if (is_prime_number(*it)) prime_counter++; return list.extend(other); } }; 13 Drawback
Forwarding requires re-implementation Can be tedious for large number of methods In contrast, subclassing allows for immediate code reuse Solutions Automatic forwarding (delegation) Type embedding Mixin Traits Protocol (in Java, called Interface)
14 Delegation
Kotlin Google’s preferred language for Android app development Statically-typed language that runs on JVM interface Base { fun print() Automatically forwards } this method call to class BaseImpl(val x: Int) : Base { b.print(), where b is an override fun print() { print(x) } instance of BaseImpl } class Derived(b: Base) : Base by b fun main() { val b = BaseImpl(10) Derived(b).print() } 15 Delegation
Can also override delegation interface Base { fun print() } class BaseImpl(val x: Int) : Base { override fun print() { print(x) } Prints “abc” instead } class Derived(b: Base) : Base by b { override fun print() { print(“abc”) } } fun main() { val b = BaseImpl(10) Derived(b).print() } 16 Delegation
Java Delegate Annotation Allows for even more fine-grained control over delegation public class DelegationExample {
// only allow add and remove to be forwarded private interface SimpleCollection { boolean add(String item); boolean remove(Object item); }
@Delegate(types=SimpleCollection.class) private final Collection
Java Delegate Annotation Can exclude methods from being forwarded public class DelegationExample {
private interface Add { boolean add(String item); boolean addAll(Collection x); }
// do NOT forward add and addAll @Delegate(excludes=Add.class) private final Collection
Allows for composition with automatic forwarding Introduced in Go Does not have (implementation) inheritance Uses composition for code reuse type Job struct { command string // go syntax is IDENT TYPE *log.Logger // embedded types have no identifier, } // just the type: * log.Logger job := &Job{“take out trash”, log.New(os.Stderr, “”, log.LUTC)}
// job has all methods of log.Logger, including Println job.Println(“working on it now...”) 19 Mixin
Code reuse without becoming the parent class Inclusion rather than inheritance Provides functionality for another class Can contain states (e.g. have member variables) Frequently same as implementation inheritance Convention: do not use mixin as an abstract base type isinstance(obj, mixin) is semantically meaningless Also implemented using metaprogramming techniques
20 Mixin
Python Comparable
# Comparable Mixin, you must supply __lt__ to enable these class Comparable: def __eq__(self, other): return not (self < other) and not (other < self) def __ge__(self, other): return not (self < other) def __ne__(self, other): return self < other or other < self def __le__(self, other): return self < other or not (other < self) def __gt__(self, other): return not (self < other) and other < self 21 Mixin class Student(Person, Comparable): def __init__(self, name, score): Person.__init__(self, name) self.score = score Receives all the other comparison operators def __lt__(self, other): just by implementing return self.score < other.score the less than operator
>> a = Student(“Alice”, 50) >> b = Student(“Bob”, 60) >> a == b, a != b, a >= b, a > b, a <= b, a < b (False, True, False, False, True, True) >> c = Student(“Clive”, 50) >> a == c, a != c, a >= c, a > c, a <= c, a < c (True, False, True, False, True, False)
22 Mixin, Traits, and Protocol
Provides functionality without subtyping
Mixin Trait Protocol Allows for states Yes No No (member variables) Interface only No No Yes (no method definition) Code Reuse Yes Yes No Composability Inheritance Commutative Commutative
The Comparable example is also a trait It contained no states (no member variables were used)
23 Conclusion
Use inheritance only for interface (i.e. subtyping) Use composition and other techniques for code reuse Avoid fragile software, difficult to maintain and extend Dependency Inversion Principle High level modules should not depend on low level modules. Both should depend on interfaces. Interface should not depend on implementation. Implementation should depend on interface. Can swap out any component without behaviour change Performance change is possible, and improvement is preferred
24 ECE326 PROGRAMMING LANGUAGES
Lecture 11 : Method Resolution Order Kuei (Jack) Sun ECE University of Toronto Fall 2019 Property
Makes methods appear as data attributes
Created using property built-in function class Person: def __init__(self, first, last): self.first, self.last = first, last def get_full(self): return self.first + “ ” + self.last def set_full(self, value): self.first, self.last = value.split(“ ”, maxsplit=1) def del_full(self): del self.first del self.last full_name = property(get_full, set_full, del_full) 2 Property class Person: … other definitions … full_name = property(get_full, set_full, del_full)
>> p = Person(“John”, “Doe”) >> p.full_name ‘John Doe’ >> del p.full_name >> p.first AttributeError: ‘Person' object has no attribute 'first' >> p.full_name = “Jane Smith” >> p.last ‘Smith’ >> p.first ‘Jane’
3 Property
Advice Don’t use if the function performs expensive computation The function is called every time you access the field Property “hides” the fact that it’s actually a function call Use case Interface change E.g. the field was accessed directly. Now you want to change the way it is used or accessed Make field read-only Do not supply the setter and deleter
4 Property
Example: throw an error if Celsius below absolute zero class Temperature: def __init__(self, value): self.celsius = value # this will call the property def get_celsius(self): return self._celsius def get_fahrenheit(self): return self._celsius * 9 / 5 + 32 def set_celsius(self, value): if value < -273.15: raise ValueError(“Cannot go below 273.15 degrees C”) self._celsius = value celsius = property(get_celsius, set_celsius)
5 Multiple Inheritance
Python supports multiple inheritance Used extensively for mixins Also for naturally complex relationships Review Python has no pure virtual functions All attributes in Python objects are virtual (can be overridden) All base classes in Python are virtual No repeated base class issue A subset of the diamond problem still exists
6 Multiple Inheritance class Person: >> TA("jack", def __init__(self, name, age): .. 13, 48957257, self.name = name # print("In Person") .. "hello world") self.age = age class Student(Person): In Student def __init__(self, id, **kwargs): In Teacher self.id = id # print("In Student") In Person super().__init__(**kwargs) class Teacher(Person): def __init__(self, resume, **kwargs): self.resume = resume # print("In Teacher") pass everything as super().__init__(**kwargs) keyword argument class TA(Student, Teacher): def __init__(self, name, age, id, resume): super().__init__(name=name, age=age, id=id, resume=resume) 7 Cooperative Inheritance
Take the arguments you need and pass the rest on Power of Python’s variable keyword arguments super() is used to chain the __init__ calls in different classes Do not use positional arguments Automatically removed from kwargs class Teacher(Person): def __init__(self, resume, **kwargs): print("In Teacher") Keyword argument self.resume = resume unpacking. Passes super().__init__(**kwargs) unused arguments to the next class
8 Alternative (Bad) class Person: >> TA(“joey", def __init__(self, name, age): .. 21, 63490483, … set name and age … .. “good bye") class Student(Person): def __init__(self, name, age, id): In Student self.id = id # print("In Student") In Person Person.__init__(name, age) In Teacher In Person class Teacher(Person): def __init__(self, name, age, resume): self.resume = resume # print("In Teacher") Person.__init__(name, age) Person’s constructor class TA(Student, Teacher): got called twice… def __init__(self, name, age, id, resume): Student.__init__(name, age, id) Teacher.__init__(name, age, resume) 9 Method Resolution Order
The order in which attributes are looked up super() knows which __init__ to call next class Person: >> ta = TA("jack", 13, def vacation(self): 48957257, "hello world") return “Toronto Islands” >> ta.vacation() class Student(Person): Caribbean Islands pass >> TA.__mro__ (
10 Depth First Search – Left to Right
Naïve approach Used by Python 2.1 and earlier
class TA(Student, Teacher): pass MRO using DFS-LR: (TA, Student, Person, Teacher) Not what we intuitively expect Teacher is more specialized than Person Huge problem for Python 2.2 object becomes the base class of all classes 11 Refinement
Python 2.2 Still uses depth-first search, left to right Delete duplicate if it shows up again later
# pseudo code for new method resolution order def mro_v2(cls): mro = [ cls ] for parent in cls.parents: mro = [ c for c in mro if c not in parent.__mro__ ] mro.extend(parent.__mro__) return mro Now order is (TA, Student, Teacher, Person, object) However… 12 Local Precedence Ordering
Order in which parent classes are inherited The new MRO does not honor this ordering class Person: Under new algorithm, the MRO is: def vacation(self): (PartTime, Student, Person) return “Toronto Islands” class Student(Person): >> PartTime(…).vacation() def vacation(self): Queen’s Park return “Queen’s Park” class PartTime(Person, Student): Q: But Student is more specialized pass than Person? A: Yes, as such, this inheritance hierarchy is ambiguous.
13 Monotonicity
Given that C1 and C2 are part of the inheritance hierarchy of C, then if C1 precedes C2 in the linearization of C, then C1 must precedes C2 in the linearization of any subclass of C. Method Resolution Order (MRO) is the set of rules that constructs the linearization of class. Hierarchy that fails this criteria is ambiguous for C Python 2.3 and later will raise TypeError if it detects ambiguous hierarchy
14 Ambiguous Hierarchy
Using Python 2.2 MRO algorithm
class X: pass Python 2.2 used to accept ambiguous hierarchy, which class Y: pass can lead to subtle bugs because the resolution ordering class A(X, Y): pass changes depending on whether A is a subclass of C or not. class B(Y, X): pass class C(A, B): pass >> a = A() # resolution order changes >> c = C() # when A is a subclass of C L[A] = (A, X, Y, o) L[B] = (B, Y, X, o) L[C] = (C, merge(L[A] + L[B])) L[C] = (C, merge(A, X, Y, o, B, Y, X, o)) L[C] = (C, A, B, Y, X, o)
# this violates monotonicity because in L[C], Y comes before # X but in L[A], X comes before Y! 15 C3 Linearization
An algorithm designed for Dylan programming language Maintains local precedence ordering and monotoncity Used by many languages E.g., Python, Perl, …etc The linearization of C is the sum of C plus the merge of the linearizations of the parents and the list of the parents.
L[C(B1 ... BN)] = (C, merge(L[B1] ... L[BN], B1 ... BN))
16 Terminology
Head The first element of the list (i.e. linearization) E.g. 5 is the head of the list [5, 2, 3, 7] Tail The remaining elements of the list (not head) E.g. [2, 3, 7] is the tail of the list [5, 2, 3, 7] L[C] The linearization of the class C Base case: L[object] = object
17 C3 Linearization
Good head A class that does not exist in the tail of any other lists Algorithm For each list in local precedence order, remove the head from the merge if it is a good head. Otherwise try the next list Repeat until all classes are removed or there is no good head In latter case, merge is not possible Error will be raised for ambiguous hierarchy
18 C3 Linearization class F: pass class E: pass E D F class D: pass class C(D,F): pass class B(D,E): pass B C class A(B,C): pass A L[F] = (F, o) L[E] = (E, o) L[D] = (D, o) L[C] = (C, merge((D, o), (F, o), (D, F)) # D is a good head, remove it from all lists and move it out L[C] = (C, D, merge((o), (F, o), (F)) # o is not a good head, but F is L[C] = (C, D, F, merge((o), (o)) # o is now a good head, and we’re done L[C] = (C, D, F, o) 19 C3 Linearization class F: pass class E: pass E D F class D: pass class C(D,F): pass class B(D,E): pass B C class A(B,C): pass L[F] = (F, o) A L[E] = (E, o) L[D] = (D, o) L[C] = (C, D, F, o) L[B] = (B, D, E, o) L[A] = (A, merge((B, D, E, o), (C, D, F, o), (B, C)) L[A] = (A, B, merge((D, E, o), (C, D, F, o), (C)) L[A] = (A, B, merge((D, E, o), (C, D, F, o), (C)) L[A] = (A, B, C, merge((D, E, o), (D, F, o)) L[A] = (A, B, C, D, merge((E, o), (F, o)) L[A] = (A, B, C, D, E, merge((o), (F, o)) L[A] = (A, B, C, D, E, F, merge((o), (o)) L[A] = (A, B, C, D, E, F, o) 20 Ambiguous Hierarchy class X: pass class Y: pass X Y class A(X, Y): pass class B(Y, X): pass class C(A, B): pass A B L[X] = (X, o) L[Y] = (Y, o) C L[A] = (A, merge((X, o), (Y, o))) L[A] = (A, X, Y, o) L[B] = (B, Y, X, o) L[C] = (C, merge((A, X, Y, o), (B, Y, X, o), (A, B)) L[C] = (C, A, merge((X, Y, o), (B, Y, X, o), (B)) L[C] = (C, A, B, merge((X, Y, o), (Y, X, o))
# Uh-oh, cannot continue. Neither X or Y are good heads 21 ECE326 PROGRAMMING LANGUAGES
Lecture 12 : Move Semantics Kuei (Jack) Sun ECE University of Toronto Fall 2019 Assignment 2
Calculate the optimal strategy for Easy Blackjack Approach: Divide and Conquer For every encounter (a particular hand vs. the dealer’s) Calculate the expected value (EV) of available actions i.e. surrender, hit, stand, double, split Select the action with the highest EV Expected value Probability weighted average of all possible value Assume finitely many outcomes Value: for this assignment, monetary value (not point value of hands)
2 Example
Standing on hard 18 against dealer’s hard 16
Dealer Outcome Probability EV 17 +$1 1/13 1/13 18 0 1/13 0 19, 20, 21 -$1 3/13 -3/13 bust +1 8/13 8/13
EV = P1V1 + P2V2 + P3V3 + P4V4 EV = (1/13)(1) + (1/13)(0) + (3/13)(-1) + (8/13)(1) EV = 6/13 = 0.462 3 Stand EV Table
Once calculated, fill the corresponding entry in table standing on hard 18 against dealer’s hard 16
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 AA A2 A3 A4 A5 A6 4 ------5 ------6 ------7 ------dealer‘s------hand ------8 ------9 ------10 ------11 ------12 ------13 ------14 ------15 ------16 ------17 ------18 ------0.462 ------19 ------20 ------21 ------AA ------player‘s ------A2 ------A3 ------hand ------A4 ------A5 ------A6 ------A7 ------A8 ------A9 ------
4 Dynamic Programming
Used extensively throughout the assignment Many overlapping subproblems Example Reuse the dealer’s probability table for different starting hand E.g. this time player has hard 19 against the dealer’s hard 16
Dealer Outcome Probability EV 17,18 +$1 2/13 2/13 19 0 1/13 0 20, 21 -$1 2/13 -2/13 bust +1 8/13 8/13
5 Hints and Advice
Make sure you know the rules of Easy Blackjack well Calculating expected value Make sure probabilities of all outcomes add up to 1 Use assert(isclose(psum)) to crash immediately if check fails Use Python’s debugger python3 –m pdb main.py Debug at point of failure e.g. unhandled error, assertion, etc. Partially done milestones are still worth marks!
6 Move Semantics and rvalue reference
7 Passing Arguments
C++ has two ways of passing arguments to functions Pass by value The value of an argument is copied into the formal parameter Example:
Complex add(Complex c, int a) { c.r += a; return c; } … Complex c(5, 2); Complex cp = add(c, 3); Both c and a are copied before function is invoked
8 Pass by Value
Arguments are copied to parameters Arguments can be variable, literal, or expression If variable is passed, it is guaranteed to be unaffected by the function call If an object is passed, its copy constructor will be called To make a copy of the object for the function call This can be expensive
9 Pass by Reference
Reference to variable is passed to parameter Argument can only be a variable Function can modify value of the argument Tip: pass by const reference can guarantee to caller that function will not modify the argument
void swap(int & a, int & b) { int temp = a; a = b; b = temp; } Passing a pointer by value is NOT pass by reference!
10 lvalue vs rvalue
Notice pass by reference only accepts variable Not literal or expression Because it only accepts lvalue lvalue (i.e. left value) Value that is stored in memory – has an address Appears on left side of an assignment (by value) rvalue (i.e. right value) Value that is temporary, not necessarily in memory Appears on right side of an assignment (by value)
11 Example int i, j, *p; i = 6; // OK – i is lvalue, 6 is rvalue j = i; // OK – j is lvalue, i can be converted to rvalue
9 = j; // FAIL – left operand must be lvalue j*2 = 3; // FAIL – j*2 is an rvalue (temporarily calculated) p = &6; // FAIL – rvalue has no address p = &i; // OK – lvalue has an address *p = 5; // OK – dereferenced pointer is an lvalue *((int *)123) = 3; // OK – dereferencing static address
((i < 4) ? i : j) = 6; // OK – operator returns lvalue
12 Example int i, j; int & r = 5; // FAIL – cannot bind non-const lvalue // reference to rvalue int & r = j; // OK – lvalue reference binds to lvalue const int & r = 5; // OK – const lvalue reference to lvalue void foo(int & a) { … } foo(5); // FAIL – argument requires lvalue foo(j); // OK int foo() { return 5; } foo() = 3; // FAIL – foo() returns rvalue int & global() { return i; } global() = 4; // OK – global() returns lvalue
13 Rvalue Reference
A reference to rvalue! Extends the lifetime of rvalue until reference expires E.g. goes out of scope Allows reference to temporary objects and modify them Helps reduce making redundant copies
string a = “hello ”; string b = “world”; string c = a + b; // may invoke copy constructor string && r = a + b; // rvalue reference to temporary object // no extra copy is made Regular references are called lvalue references
14 Example struct Foo { int * p; Foo(int a) : p(new int(a)) {} Foo(const Foo & f) : p(new int (*f.p)) { cout << "copy " << *p << endl; } ~Foo() { delete p; } Foo operator+(const Foo & f) { return Foo(*p + *f.p); } }; >> ./foo Foo a(3); Foo b(5); >> # no output (copy constructor Foo c = a + b; # not called) Foo && r = a + a;
15 Copy Elision
Optimization technique for avoiding copying of objects Return value optimization Instead of copying temporary object to final location, build object at final location directly Must be object of exactly same type Can change behaviour of program if copy constructor has side effects (e.g. increment a global variable) Enabled by default Can be turned off: -fno-elide-constructors
16 Pass by Rvalue Reference Copy elision only applies to return values When pass by value, copy still must be made For large objects, you should usually pass by const reference Unless you need to modify a local copy S earch algorithm where you make a hypothetical move, go deeper in recursion, and undo the move (may be useful in assignment 2!) Standard C++ library Uses this to maintain genericity while improving performance Forwarding Allows arguments to be forwarded without additional copies
17 Move Constructor struct Foo { int * p; Foo(int a) : p(new int(a)) {} Foo(const Foo & f) : p(new int (*f.p)) { cout << "copy " << *p << endl; } ~Foo() { delete p; } Foo operator+(const Foo & f) { return Foo(*p + *f.p); }
/* move constructor */ Foo(Foo && f) : p(f.p) { /* we “moved” f.p to this->p, so must set f.p to * to nullptr otherwise ~Foo() will delete it! */ f.p = nullptr; } }; 18 Move Assignment struct Foo { int * p; Foo(int a) : p(new int(a)) {} ~Foo() { delete p; } Foo operator+(const Foo & f) { return Foo(*p + *f.p); } …
/* move assignment */ Foo & operator=(Foo && f) { if (this == &f) return *this; delete p; // delete current resource p = f.p; // move resource from other f.p = nullptr; // makes sure this->p is not deleted return *this; // when temporary object f dies } }; 19 std::move
Forces move semantics on a variable Convention: moved variable should become “empty” Reality: some developers may leave moved object in unsafe state
#include
string a = “hello world”; string b = a; // copy constructor is called string c = std::move(a); // move constructor is called
// prints nothing (a is an empty string), could be worse… cout << a << endl; Use to forward arguments or as part of move constructor
20 std::move bool Rule::can_surrender(Hand hand) { … } struct BJ { std::vector
Use when variable will be “consumed” i.e. will not be used afterward Example Set up an array of strings
std::vector
Move semantics should not acquire new resources Just reassigning content to new location
Performance improvement if noexcept specified
struct BJ { std::vector
POD types Plain old data types – same as a C struct Does not use object-oriented features NO copy constructor, static member fields, virtual table, etc. Does not benefit as much from move semantics Unless it holds pointers to larger structures Should always pass large PODs by lvalue-reference (const or non-const) unless local copy is needed Primitive types Always pass by value if read-only
24 ECE326 PROGRAMMING LANGUAGES
Lecture 13 : Generic Programming Kuei (Jack) Sun ECE University of Toronto Fall 2019 Container
A collection of objects Commonly used containers Dynamic arrays Constant time random access, best performance when used as stack Doubly Linked List C onstant time deletion, best performance when used as queue Map Stores key-value pairs Priority Queue Automatically sorts data during insertion
2 Generic Container
In C, typically done using void * pointers struct vec1 { Sample usage: void ** array; struct vec1 * v = new_vector1(); int bytes; struct point * p = new_point(1, 2); int count; v1append(v, p); }; … p = (struct point *)v1get(v, 1);
int v1append(struct vec1 * v, void * element) { if (v->count >= v->bytes/sizeof(void *)) { if (!(v->array = realloc(v->array, v->bytes *= 2))) return –ENOMEM; } v->array[v->count++] = element; return 0; } 3 Generic Container
Specially managed buffer (element embedded in buffer)
struct vec2 { char * buffer; void * v2get(struct vec2 * v, int i) { int bytes; if (i >= count) return NULL; int count; return (void *)(v->array + i*v->size); int size; } };
int v2append(struct vec2 * v, void * element) { if ((v->count+1)*v->size > v->bytes) { if (!(v->buffer = realloc(v->buffer, v->bytes *= 2))) return –ENOMEM; } memcpy(v->buffer+(v->count++)*v->size, element, v->size); return 0; } 4 Generic Container
element 1 element 2 element 3
vec1:
vec2: element 1 element 2 element 3
5 Linux Doubly Linked List
Used extensively by Linux Kernel struct list_head { struct list_head *next, *prev; }; struct bar b = { 0 }; struct foo { INIT_LIST_HEAD(&b.head); int x, y; struct foo q, p; struct list_head node; list_add(&q.node, &b.head); }; … // get the item at head of list struct bar { struct foo * f; int a, b; f = list_first_entry( struct list_head head; &b.head, struct foo, node }; );
6 Circular Linked List typedef struct list_head { struct list_head *next, *prev; } LH; static inline void INIT_LIST_HEAD(LH *list) { list->next = list; list->prev = list; }
#define list_add(new, head) \ __list_add((new), (head), (head)->next) static inline void __list_add(LH *new, LH *prev, LH *next) { next->prev = new; new->next = next; new->prev = prev; prev->next = new; } 7 Circular Linked List
list_head structures point to themselves, and not to their containing object(s) b empty list head: prev next list of size 2
b p q head: node: node: prev next prev next prev next
8 container_of
How does list_first_entry cast list_head to struct foo? struct foo * f = list_first_entry(&b.head, struct foo, node);
#define list_first_entry(ptr, type, member) \ list_entry((ptr)->next, type, member)
#define list_entry(ptr, type, member) \ container_of(ptr, type, member)
#define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type, member) ); \ })
9 typeof
Returns type of expression (at compile time) int x = 5; typeof(&x) y; // y is of type “int *”
#define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type, member) ); \ }) container_of((&b.head)->next, struct foo, node) expands to: typeof(((struct foo *)0)->node) *__mptr = ((&b.head)->next); (struct foo *)((char *)__mptr - offsetof(struct foo, node));
10 offsetof
Returns byte offset of member variable in structure h igher address #define offsetof(TYPE, MEMBER) \ 4 a: int ((size_t) &((TYPE *)0)->MEMBER) 1 struct baz { int a; char s; char t; }; s: char // prints 5 1 t: char printf(“%d”, offsetof(struct baz, t)); typeof(((struct foo *)0)->node) *__mptr = ((&b.head)->next); (struct foo *)((char *)__mptr - offsetof(struct foo, node)); expr struct list_head * __mptr = ((&b.head)->next); p /* this is the value of the expression */ x, y (struct foo *)((char *)__mptr – 8; __mptr node: prev next 11 Generics in C
Use generic pointer (void *) Use generic buffer Use container_of to reference arbitrary parent object
Lots of low level pointer manipulation Type-unsafe Easy to make mistakes
12 Generic Programming
Writing program that makes minimal assumption about the structure of data Maximize code reuse across different data types Parametric polymorphism Ability to handle values without depending on their types Concatenating two lists can be done without knowing type of element Metaprogramming Writing program that modifies programs Generics often implemented with metaprogramming
13 Parametric Polymorphism
Problem with statically typed languages Redundant implementation of common algorithm
/* assumed operator< implemented */ int min(int a, int b) { Complex min(Complex a, Complex b) { return a < b ? a : b; return a < b ? a : b; } } Imagine if common code is complex (like a container) Tedious Time consuming Error prone (one mistake means fixing all other versions)
14 Generics in Java
Before J2SE 5.0
List v = new ArrayList(); v.add(“hello world”); // inserting a string into v Integer i = v.get(0); // ERROR: runtime type error After Compile-time type checking added to generics Underlying implementation remains same (shared generics)
List
Template Metaprogramming For each template instantiation, new code is generated
vector
vector
16 C++ Template Programming
an introduction
17 Template Function
Behaves like function except function is created when function is used (template instantiation) Template must fully implement (define) function Cannot just declare the function (why?)
template
std::cout << min
18 C++ Template
Type-safe at compile time Template instantiated (code generated) on use Type must support all used operations in template E.g. operator< must be supported by type T
template
19 Template Specialization
Allows alternative implementation for a particular type Benefit Code does not make it to executable if not used! template
/* use strcmp to compare cstrings */ template<> const char * min(const char * a, const char *b) { return strcmp(a, b) < 0; } 20 Multiple Parameters
Templates can have multiple parameters Instantiation may require disambiguiation
/* template declaration – must define within same file */ template
C++ templates do not restrict on parameter type
template
1. Non-template function overload 2. Template specialization 3. More specific and specialized template 4. Base template template
Lecture 14 : C++ Class Template
Kuei (Jack) Sun ECE University of Toronto Fall 2019 Class Survey
Need more examples Examples are hard to follow Laser pen ordered Need extra reading Practice midterm In class, Friday 25th, solution will be posted afterwards Tuesday 29th class will be for review only More correlation between assignment and lecture Subsequent assignments (2 and onward) are related 2 Class Template
Template for creating classes Typically used to implement generic constructs E.g. containers Reuse source code instead of object code Allows full spectrum of C++ features Inheritance Dynamic dispatch Ad-hoc polymorphism
3 Generic Stack template
4 Member Functions
Can be defined outside of class template But must be defined together, preferably in same header file template
5 Member Functions template
Creates an alias for the type name Common use Indicate different usage E.g. size_t instead of unsigned long confer context reduce length of type names, or improve appeal Again, do not abuse this feature
/* this is bad – hides the fact that it’s a pointer */ typedef char * cstring; typedef Stack
DoubleStack ds = DoubleStack(); double * dp, d = 1.1; cout << "Pushing elements onto stack" << endl; while (ds.push(d)) { cout << d << ' '; d += 0.7; } cout << endl << "stack full" << endl << endl << "Popping elements from stack" << endl ; while ((dp = ds.top())) { cout << *dp << ' '; ds.pop(); } cout << endl << "stack empty" << endl ; Pushing elements onto stack Popping elements from stack 1.1 1.8 2.5 3.2 3.9 4.6 5.3 6 6 5.3 4.6 3.9 3.2 2.5 1.8 1.1 stack full stack empty 8 Generic Container
Duplicate code if many template instantiations Can cause code bloat – large executable size Goal Can we use a generic Stack implementation, and use templates to provide type-safety? Approach Mix template and inheritance Note the following example actually worsens executable size
9 Generic Stack class Generic { public: Generic(unsigned size, void (*init)(char *), void(*dest)(char *), unsigned cnt=8); ~Generic(); /* pop, empty and full are omitted – same implementation */ private: void(*dest)(char *); // function pointer to destructor int end; // points to top of stack int size; // size of each element protected: int count; // capacity of stack char * buffer; void * push(); // returns address to place new element void * top(); // return current top of stack }; 10 Generic Stack Constructor
Generic::Generic(unsigned size, void (* init)(char *), void (* dest)(char *), unsigned cnt) : dest(dest) , end(-1) , size(size) , count(cnt) , buffer(new char[size*cnt]) { char * ptr = buffer; for (int i = 0; i < count; i++) { // calls constructor for each element init(ptr); ptr += size; } }
11 Generic Stack Destructor
Generic::~Generic() { char * ptr = buffer; for (int i = 0; i < count; i++) { /* calls destructor for each element */ dest(ptr); ptr += size; }
delete [] buffer; }
12 Generic Stack Functions
// returns address to place new element void * push() { if (full()) return nullptr; return buffer + (++end)*size; }
// return current top of stack void * top() { if (empty()) return nullptr; return buffer + end*size; }
13 Stack Template template
Calling constructor of type at preallocated address When used, destructor must be called manually
template
template
15 Sample Use typedef Stack
16 Static Members
Static member functions Function shared across all instances of same class Doest no have a this pointer Static member variable Variable shared across all instances of same class Static function variable Has global lifetime but has same scope as local variables Each template class/function has its own copy of static members, not shared across templates!
17 Static Class Members class Puppy { static int num_puppies; public: Puppy() { num_puppies++; } ~Puppy() { num_puppies--; } static void status() { cout << “I have ” << num_puppies << “ puppies” << endl; } }; int Puppy::num_puppies = 0; // instantiate static variable … Puppy a, b, c, d; Puppy::status();
I have 4 puppies
18 Static Function Variable
Persists through function calls Initialized once (unless changed by function) Can be useful if your function cannot return null Must instead return default or error value template
Compile time check for certain guarantees Helps you find bugs at compile time rather than runtime #include
Template parameters can take defaults too template
/* creates an integer stack with capacity of 16 */ Stack<> stack;
21 Partial Specialization
Specialize only some of the template parameters template
Specialize only some of the template parameters template
23 Template Aliases
Similar to typedef, except for partial templates Not the same as partial specialization Only gives a different name to templates
template
template
24 ECE326 PROGRAMMING LANGUAGES
Lecture 15 : Reflective Programming
Kuei (Jack) Sun ECE University of Toronto Fall 2019 Introspection
The ability to examine the type or attribute of a value At runtime Compile-time introspection is called static introspection Python examples isinstance(object, cls) Checks if object is an instance of class class A : pass >> isinstance(obj, A) class B(A) : pass True >> isinstance(obj, int) >> obj = B() False >> isinstance(obj, B) >> isinstance(A, B) True False 2 Introspection
issubclass(cls1, cls2) Checks if class is a subclass of another
class A : pass >> issubclass(A, B) class B(A) : pass False >> obj = B() >> issubclass(B, A) >> issubclass(obj, B) True TypeError: obj must be a class
dir(object=None) Returns a list of object’s attributes >> dir(A) [‘__init__’, ‘__class__’, '__delattr__', '__dict__', …]
3 Introspection
hasattr(object, name) Checks if string name is the name of one of the object’s attribute
class A: >> hasattr(A, ‘y’) x = 5 False def foo(): pass >> my_name = ‘foo’ >> hasattr(A, my_name) >> hasattr(A, ‘x’) True True type(object) Returns type of object >> type(A.foo) >> a = A()
C++ Example Runtime Type Information (RTTI) typeid Returns the type id of an object
if (typeid(Student) == typeid(*object)) { return hash_student(object); } dynamic_cast Downcasts a base class pointer to a subclass pointer, if valid
Animal * ap = animals.pop(); Lion * lp = dynamic_cast
5 Static Introspection
Introspection at compile time Treating compiler as a white box The compiler reveals what it knows about an entity type, variable, expression, …etc Make use of how compiler internally represents an entity C++ example decltype Returns the type of an expression at compile time typeof is the non-standard version of decltype decltype(7/2) a = 5; // a is of type int 6 Reflection
The ability for a process to introspect and modify itself Changes its own code, such as structure and behavior Can even change the programming language itself E.g. syntax, semantic, implementation Process A running instance of a program Static reflection Generates compile-time meta-objects E.g. dir from Python for C++, only accessible at compile-time
7 Reification
Turns abstract representation into concrete data types and/or addressable objects Simpler definition Converting compile time types into run-time entities Java Example Type information kept to perform runtime type checking
String strings[] = {"a", "b"}; Object objects[] = strings; // allowed at compile time objects[0] = 5; // allowed at compile time java.lang.ArrayStoreException: java.lang.Integer 8 Type Erasure
Removal of type information/checks at runtime Type checking at compile-time, none at runtime C++ Example struct data { int normalized(struct data * d, int i) { int norm; return d->sample[i] / d->norm; int sample[16]; } } ; movslq %edx, %rdx Generated code assumes movl 4(%rcx,%rdx,4), %eax correct structure (struct data) cltd is passed in. No type checking Idivl (%rcx) is made at runtime, which ret improves performance
9 Reflection and Reification
Statically-typed, interpreted or byte-compiled languages Anything that uses JVM, E.g. Java, Kotlin
Reflection
Executable Source Code Code (Compile (Run time) time)
Reification 10 Static Reflection
Statically-typed, systems programming languages
Type introspection Reflection
Executable Compiler Source Code Code Internal
Type erasure Reification
11 Python Reflection
setattr(object, name, value) Set an object’s attribute with name to arbitrary value
class A: >> setattr(A, ‘y’, 7) x = 5 >> A.y 7 >> setattr(A, ‘x’, set) # equivalent to A.z = None >> A.x >> setattr(A, ‘z’, None) set([]) getattr(object, name, default=None) Retrieves an attribute by name, return default if not found. If default is not specified, raise AttributeError
>> getattr(A, ‘y’, 0) # equivalent to A.y 0 >> getattr(A, ‘y’) 12 Python Reflection
delattr(object, name) Delete an object’s attribute by name
class A: >> hasattr(A, ‘x’) x = 5 False # equivalent to del A.z >> delattr(A, ‘x’) >> delattr(A, ‘z’,) globals(), locals(), vars(object) Returns a dictionary of all global/local/instance variables
>> globals() {'__name__': '__main__', '__doc__': None, '__package__': None, '__annotations__': {}, '__builtins__':
13 Managed Attributes
Provides control over attribute access E.g. fetch (get), assignment (set), or deletion (del) Property Allows attribute access to invoke methods Makes calling methods appear as a data attribute access
class Person: def get_full(self): return self.first + “ ” + self.last def set_full(self, value): self.first, self.last = value.split(“ ”, maxsplit=1) full_name = property(get_full, set_full)
14 Descriptor
A class that customizes get, set, and/or delete of another object’s attribute Similar to property, except more flexible Since it’s a class, it can be subclassed, or inherit another
class Descriptor: def __get__(self, instance, owner): … def __set__(self, instance, value): … def __delete__(self, instance): … class Foo: managed = Descriptor() f = Foo() f.managed = 5 # calls Descriptor.__set__ 15 Descriptor
__get__(self, instance, owner) instance is the instance variable, None if attribute is accessed through the class (Foo.attr instead of f.attr) owner is always the class (e.g. Foo)
>> f.managed # self: Descriptor instance, instance: f, owner: Foo >> Foo.managed # self: Descriptor instance, instance: None, owner: Foo
__set__(self, instance, value) If not defined, allows attribute to be overwritten! Unlike property, default behaviour makes attribute read-only 16 Descriptor class CreditCard: NUM_DIGITS = 16 def __init__(self, name, number): self.name, self.number = name, number class Number: def __get__(self, instance, owner): return self.number[:-4] + '****' def __set__(self, instance, value): value = value.replace('-', '') if len(value) != instance.NUM_DIGITS: raise TypeError('invalid credit card number') self.number = value number = Number() card = CreditCard("Jack", "1234-3453-5256-1758") print(card.number) # prints 123434535256**** 17 __setattr__
Intercepts all assignments to the object’s attribute Example
class Immutable: def __init__(self, x, y): self.x, self.y = x, y
def __setattr__(self, name, value): raise AttributeError(“cannot update read-only object”)
>> obj = Immutable(5, 6) >> obj.x = 3 AttributeError: cannot update read-only object
18 __getattr__
Intercepts all fetch (get) from an object that results in attribute not found Before the AttributeError is raised Use case Returning default values on attribute not found Automatic forwarding Caveat Does not intercept if method overloads an operator Anything that starts and ends with __ (e.g. __getitem__)
19 Automatic Forwarding class Hand: def __init__(self, cards=tuple()): self.cards = list(cards) # copy the list
def _points(self): return sum(self.cards) points = property(_points)
def __getattr__(self, name): return getattr(self.cards, name)
>> p = Hand([2, 3, 4]) >> p.append(9) # goes through __getattr__ >> print(p.points) # points exists – does not go 19 # through __getattr__
20 __getattribute__
Intercepts all fetch (get) from an object Also includes those not found (i.e. __getattr__) Danger – improper use will result in infinite recursion Use super() instead of self to avoid infinite recursion Similar caveat as __getattr__ May be bypassed by operator overloading Use case Disable access to “private” members
21 Private Members class Protected: def __init__(self, x, y): self._x, self._y = x, y
def getX(self): return vars(self)['_x'] # same as self.__dict__[‘_x’]
def __getattribute__(self, name): val = super().__getattribute__(name) if name != "__dict__" and name.startswith("_"): raise AttributeError(name + “ is a private member") return val
>> p = Protected(5, 7) >> p.getX() >> p._x 5 AttributeError: _x is a private member 22 ECE326 PROGRAMMING LANGUAGES
Lecture 16 : Python Decorator
Kuei (Jack) Sun ECE University of Toronto Fall 2019 Decorator
A way to augment an existing function or class E.g. Do something before and/or after a function call E.g. modify a class in some ways
@function_decorator @class_decorator def foo(): class Foo: pass pass Decorator is a syntactic sugar A callable that returns a callable object (e.g. function or class)
# equivalent to the definitions above foo = function_decorator(foo) Foo = class_decorator(Foo)
2 Callable
An entity that can be called Accepts some argument(s) and returns some value(s) Functor A function object In Python, an instance of a class that implements __call__ In C++, an instance of a class that implements operator() Has a different meaning in mathematics, do not confuse!
class Foo: >> f = Foo() def __call__(self, arg): >> f(5) # works just like return arg + 2 7 # a function
3 Inner Function
A function defined inside a function Also known as a nested function Have access to variables in enclosing function Known as outer variables Requires nonlocal statement to reassign outer variables def outside(): def outer(): outsideList = [1, 2] stuff = None def nested(): def inner(): outsideList.append(3) nonlocal stuff nested() stuff = 5 print(outsideList) inner() # [1, 2, 3] return stuff # returns 5 4 Closure
An inner function that is returned by the outer function that uses outer variables Accessible even after the outer function finishes! Retain values they had at the time the closure was defined
def adder(outer_argument): # outer function def inner(inner_argument): # inner function # ** notice outer_argument ** return outer_argument + inner_argument return inner add5 = adder(5) # a function that adds 5 to its argument add7 = adder(7) # a function that adds 7 to its argument print add5(3) # prints 8 print add7(3) # prints 10 5 Decorator
Wraps a function and modify its behaviour def time_me(func): def wrapper(*args, **kwargs): start = datetime.now() ret = func(*args, **kwargs) diff = datetime.now() - start print("Took”, diff, “to run”, func.__name__)) return ret return wrapper @time_me def non_recursive_expensive_function(n): … # does some expensive computation >> non_recursive_expensive_function(100) Took 0:01:02.423414 to run non_recursive_expensive_function 6 Decorator
Decorator permanently alters the function All later calls are also “decorated” Make sure that’s the behaviour you want @time_me def fib(n): return 1 if n < 2 else fib(n-1) + fib(n-2) >> fib(5) Took 0:00:00.000004 to run fib Took 0:00:00.000006 to run fib Took 0:00:00.000281 to run fib … @time_me def fibonacci(n): # fixed version return fib(n) # assume fib(n) no longer decorated 7 Pros and Cons
Benefits Explicit syntax easy to notice augmentation on function/class Code reuse Same decorator can be used for many functions/classes Drawbacks Performance Decorator requires additional function call(s) Type change Function/class now exists inside a wrapper, may break existing usage
8 Decorator with Arguments
Decorators can take arguments def repeat(ntimes): def decorator(func): def repeater(*args, **kwargs): for __ in range(ntimes): func(*args, **kwargs) # no return value return repeater decorator return >> a = foo(“hi”) # same as foo = repeat(5)(foo) hi hi hi hi hi @repeat(5) >> a None def foo(msg): print(msg, end=“ ”) return len(msg) Decorator can “eat” your return value, beware 9 Caveat
Decorator breaks some introspection attributes def foo(msg): “foo prints msg without new line” This is called a doc print(msg, end=“ ”) string, which allows you return len(msg) to use help(object) during interactive use . >> foo.__name__, foo.__doc__ (‘foo’, ‘foo prints msg without new line’)
@repeat(5) def foo(msg): … >> foo.__name__, foo.__doc__ (‘repeater’, None) 10 wraps Decorator
If it matters to you, fix it via wraps decorator import functools def twice(func): ntimes = 2 @functools.wraps(func) # fixes introspection def repeater(*args, **kwargs): for __ in range(ntimes): func(*args, **kwargs) return repeater @twice def foo(msg): … >> foo.__name__, foo.__doc__ (‘foo’, ‘foo prints msg without new line’) 11 Decorator Example
Decorator does not have to alter function Can be used to register certain functions for other use
EXPORTS = {} def register(func): EXPORTS[func.__name__] = func return func
@register def mangle(text): … # scrambles text and returns it
def manipulate_text(transformer, text): if transformer in EXPORTS: return EXPORTS[transformer](text) return text 12 Decorator Example
Modifying output of function def add_unit(unit): def decorator(func): def transform(*args, **kwargs): # assume this class keeps magnitude and unit return Quantity(func(*args, **kwargs), unit) return transform return decorator # assume we know this function returns speed in km/hr @add_unit(“km/hr”) def speed(distance, time): … >> print(speed(10, datetime.timedelta(minutes=5)) 120 km/hr 13 Input Type Checking def type_check(**argchecks): def decorator(func): code = func.__code__ allargs = list(code.co_varnames[:code.co_argcount]) def on_call(*pargs, **kargs): positionals = allargs[:len(pargs)] for argname, argtype in argchecks.items(): if argname in kargs: assert(isinstance(kargs[argname], argtype)) elif argname in positionals: pos = positionals.index(argname) assert(isinstance(pargs[pos], argtype)) return func(*pargs, **kargs) return on_call return decorator
14 __code__
Contains information about the function’s code code.co_varnames: local variables, first N are arguments code.co_argcount: number of arguments
@type_check(a=int, b=float) def foo(a, b): # co_varnames = (‘a’, ‘b’, ‘c’) c = 5 # co_argcount = 2 return a + b + c >> foo(2, 2.3) # OK >> foo(5, b=3.3) # OK >> foo(3, 4) # FAIL – b is not a float AssertionError
15 Built-in Decorators
Decorators we have seen (or used)
class Foo: default = 0 @property def value(self): return getattr(self, ‘_v’, self.default) @value.setter def value(self, v): # method name MUST be the same! self._v = v @classmethod def setDefault(cls, v): cls.default = v
16 Decorator with Functor class counter: def __init__(self, func): self.func = func self.count = 0
def __call__(self, *args, **kwargs): self.count += 1 return self.func(*args, **kwargs)
@counter def fibonacci(n): return 1 if n < 2 else fib(n-1) + fib(n-2) >> fibonacci(10) >> fibonacci.count 177 17 Problem with Method
Decorator made with functor requires self in __call__ When decorating method, which self does it refer it? The decorator, or the instance of the decorated method?
class Foo: @counter # this doesn’t work def add(self, n): self.x += n Solution Must use a proxy object
18 Workaround class proxy: def __init__(self, desc, inst): self.desc, self.inst = desc, inst
def __call__(self, *args, **kwargs): return self.desc(self.inst, *args, **kwargs) class counter: def __init__(self, func): >> f = Foo() self.func = func >> f.add(2) self.count = 0 >> f.add(6) # must go through desc # from descriptor! >> f.add.desc.count def __get__(self, instance, owner): 2 return proxy(self, instance)
19 Class Decorator
Example: Singleton def singleton(aClass): instance = None def onCall(*args, **kwargs): # On instance creation nonlocal instance if instance == None: instance = aClass(*args, **kwargs) else: raise RuntimeError(“Cannot instantiate ” + \ aClass.__name__ + “ more than once”) return instance return onCall >> a = Calculator() >> b = Calculator() @singleton RuntimeError: Cannot instantiate Calculator class Calculator: more than once … 20 Class Wrapper def tracer(Cls): # On @ decorator class Tracer: def __init__(self, *args, **kargs): self.fetches = defaultdict(int) self.wrapped = Cls(*args, **kargs) def __getattr__(self, name): self.fetches[name] += 1 return getattr(self.wrapped, name) return Tracer >> sue = Employee(“Sue”, 40, 39.99) @tracer >> print(sue.name) class Employee: Sue … >> sue.pay() def pay(self): 1599.6 return self.hours * \ >> sue.fetches self.rate {'name': 1, 'pay': 1} 21 Limitation
__getattr__ does not work for special built-in methods Workaround Use a mixin that to force routing through __getattr__ class BuiltinMixin: def __init__(self): self._getattr = self.__class__.__getattr__ def __add__(self, other): return self._getattr(self, '__add__')(other) def __str__(self): return self._getattr(self, '__str__')() def __getitem__(self, index): return self._getattr(self, '__getitem__')(index) 22 Updated Tracer def tracer(Cls): class Tracer(BuiltinMixin): def __init__(self, *args, **kargs): # initialize super class super().__init__() self.fetches = defaultdict(int) self.wrapped = Cls(*args, **kargs) def __getattr__(self, name): self.fetches[name] += 1 return getattr(self.wrapped, name) return Tracer >> sue = Employee(“Sue”, 40, 39.99) >> print(sue) # calls __str__ … >> sue.fetches {'name': 1, 'pay': 1, '__str__': 1} 23 ECE326 PROGRAMMING LANGUAGES
Lecture 17 : Python Metaclass
Kuei (Jack) Sun ECE University of Toronto Fall 2019 Metaclass
A class whose instances are classes Defines behaviour of classes and their instances Similar to class decorator in purpose Differences Class decorator Rebinds class name to a callable Can modify the existing class or add a wrapper around existing class Metaclass Inserts or routes specialized logic during class creation
2 type
The base metaclass Creates all classes, including metaclasses All classes are instance of ‘type’ type(name, bases, attrs)
>> Foo = type(‘Foo’, (object, ), {‘a’ : 1}) >> Foo.a 1 >> type(Foo)
# equivalent to this: class Foo: a = 1
3 __class__
The class type of the instance
>> Foo = type(‘Foo’, (object, ), {‘a’ : 1}) >> f = Foo() >> type(f)
4 Metaclass
Basic example Class that does not need an __init__ method class NoInit(type): # metaclass inherits from type def __call__(cls, *pargs, **kwargs): inst = type.__call__(cls, *pargs) [ setattr(inst, k, v) for k, v in kwargs.items() ] return inst class Car(metaclass=NoInit): def __str__(self): return " ".join([k+"="+v for k, v in vars(self).items()]) >> car = Car(make="Mazda", model="CX-5", year="2019", color="White") >> str(car) ‘make=Mazda model=CX-5 year=2019 color=White’ >> type(car), type(Car) type of Car (
ob
ob.__init__() ob ob
6 __new__
The actual “constructor” __init__ is actually just an initializer Customizes instantiation of the object __new__(c ls) Default implementation class Foo: # __new__ is a static method (does not take Foo as 1st argument) def __new__(cls): # super() will eventually call object.__new__ # object is the base class of all classes return super().__new__(cls) 7 Singleton class Singleton(type): _instances = {} def __call__(cls, *args, **kwargs): if cls not in cls._instances: cls._instances[cls] = super().__call__(*args, **kwargs) return cls._instances[cls] class Factory(metaclass=Singleton): def __init__(self, name): self.name = name
def __str__(self): return "
Can be used as parent to supply class methods class Counter(type): class Animal(metaclass=Counter): def __init__(cls, name, def __init__(self, species): bases, attrs): self.species = species cls.counter = 0 Animal.up()
def up(cls): def __del__(self): cls.counter += 1 Animal.down()
def down(cls): def animal_test(): cls.counter -= 1 a = Animal("monkey") print(repr(Animal)) def __repr__(cls): return "
An instance inherits its class’s attributes Also including attributes of super classes of its class A class inherits its metaclass’s attributes Also including attributes of its super classes But – an instance does not inherit metaclass attributes
>> a = Animal(“horse”) >> a.up() # defined in Counter AttributeError: 'Animal' object has no attribute 'up'
10 __del__
Called when instance is about to be deleted Typically used to do additional clean-up E.g. close log files E.g. update global variables (such the counter) E.g. release ownership of resources (such as cache entry) Careful It is not guaranteed to be executed It all depends on the garbage collector Do not confuse with __delete__ (used by descriptor)
11 Class Creation module M __prepare__() class A(metaclass=M): a = 1 dict def foo(self): pass Meta2 __call__(…, dict) __new__(…, dict) Meta2 metametaclass cls
cls.__init__(…, dict) cls
12 Metametaclass
The metaclass of a metaclass Begins the process of creating a new class Via __call__ In contrast, a metaclass’s __call__ function initiates the process of creating a new instance
Usually, type is the metaclass of other metaclasses Unless metaclass is specified when defining a metaclass Similar to instance creation, type.__call__ will execute __new__ and __init__ of the metaclass to create a new class
13 __prepare__
Provides a dictionary-like object to store attributes By default, returns Python dictionary – dict() Exists for performance reasons Special features E.g. ordered dictionary for fast lookup
class Meta(type): @classmethod def __prepare__(mcs, name, bases, **kwargs): return {}
14 __new__ and __init__
Constructor and initializer for the class
class Meta(type): … def __new__(mcs, name, bases, attrs, **kwargs): return super().__new__(mcs, name, bases, attrs)
def __init__(cls, name, bases, attrs, **kwargs): return super().__init__(name, bases, attrs) __init__ is rarely used since __new__ does more However, it is useful when inheritance is involved In contrast, class decorator cannot be subclassed
15 Metaclass Inheritance
A derived class can have many base classes Each base class may have its own metaclass Subclasses inherits base class’s metaclass The inheritance tree of metaclass must be linear!
>> class Meta1(type): pass >> class Meta2(type): pass >> class Base1(metaclass=Meta1): pass >> class Base2(metaclass=Meta2): pass >> class Foobar(Base1, Base2): pass TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases 16 Metaclass Inheritance
Rationale Each class can only have one metaclass Resolving metaclass must be unambiguous Most specialized metaclass is chosen
>> class Meta(type): pass >> class SubMeta(Meta): pass >> class Base1(metaclass=Meta): pass >> class Base2(metaclass=SubMeta): pass >> class Foobar(Base1, Base2): pass >> type(Foobar)
17 Name Resolution
Metaclasses come last in name resolution order After all super classes have been checked i.e. done after method resolution order fails Then metaclasses are checked, in reverse order of inheritance
>> class N(type): foo = 3 # metaclass >> class M(N): attr = 1 # metaclass >> class A: attr = 2 # super class >> class B(A): pass # super class >> class C(B, metaclass=M): pass >> inst = C() >> inst.attr, C.attr, C.foo (2, 2, 3)
18 Metafunction
Metaclass only needs to be callable If __prepare__ is not defined, Python dict is used If inheritance not needed, can use a function! Can also use a functor def MetaFunc(cls, bases, attrs): attrs["hello"] = 5 Caveat return type(cls, bases, attrs) The function is used as a metametaclass class Foo(metaclass=MetaFunc): pass The type is the type >> Foo.hello of the return value 5 of the function >> type(Foo)
With metaclasses, classes can also have their operators overloaded, similar to an instance
class A(type): def __getitem__(cls, i): return cls.data[i] def __getattr__(cls, name): return getattr(cls.data, name) class B(metaclass=A): data = 'spam’ >> B[0] ‘s’ >> B.upper() ‘SPAM’
20 Object Attribute Lookup
cls.__getattribute__(‘x’) default A descriptor has YES ‘x’ in cls.__dict__ __get__ and one or both of NO __set__ and ‘x’ is a descriptor NO __delete__ YES return cls.x.__get__ YES ‘x’ in inst.__dict NO return inst.x YES ‘x’ in cls.__dict__ NO YES ‘x’ has __get__ cls.__getattr__(‘x’) NO default return cls.x.__get__ return cls.x AttributeError 21 Class Attribute Lookup
mcs.__getattribute__(‘x’) default A descriptor YES ‘x’ in mcs.__dict__ has __get__ and one or YES ‘x’ is a descriptor NO NO both of __set__ and return mcs.x.__get__ ‘x’ in cls.__dict YES __delete__ YES ‘x’ has __get__ NO return cls.x.__get__ ‘x’ in mcs.__dict__ NO YES NO return cls.x ‘x’ has __get__ mcs.__getattr__(‘x’) YES NO default return mcs.x.__get__ return mcs.x AttributeError 22 Type Slot
A table of built-in methods Operator overloading methods E.g. __add__, __str__ Attribute interception methods E.g. __getattr__, __setattr__ Attribute descriptors Look up for these methods go through type slots Much simpler and faster Not all built-in methods go through type slots E.g. __prepare__
23 ECE326 PROGRAMMING LANGUAGES
Lecture 18 : Type System
Kuei (Jack) Sun ECE University of Toronto Fall 2019 Type System
A set of types and the rules that govern their use Controls how types affect program semantics Helps reduce possibility of bugs in programs Can also enable certain compiler optimization Statically typed Types of variables checked before runtime Dynamically typed Types are checked at runtime
2 Conversion
Changing an expression from one type to another Type system decides whether it is legal to do so Sometimes it is not safe to do so, but legal Precision may be lost E.g. integer to float 32 -bit integer can store 9 decimal digits, 32-bit float can only store 7 int a = 123456789; float f = (float)a; cout << (int)f << endl; 123456792
3 Conversion
Widening conversion Can include all the values of the original type Always safe Except for possible loss in precision Narrowing conversion Converts to type that cannot store the entire range of value May cause truncation, saturation or overflow
short b = (short)1.25e25; // 32767 (saturation) int a = (int)123456789.9; // 123456789 (truncation) cout << (short)a << endl; // -13035 (overflow)
4 Coercion
Implicit type conversion In safe cases, it is sometimes called a promotion Some type systems allow unsafe conversions implicitly Signed to unsigned Narrowing conversion Generic pointer to typed pointer
unsigned a = 5; void * v = &a; if (a > -1) { # C++ allows this (C does not) int * p = v; # C allows this (C++ does not) } 5 Implicit Conversion
#include
Prevents constructor from implicit conversion
class String { char * buf; public: explicit String(unsigned size); … }; Detail Compile can use single parameter constructor to convert from one type to another to get the right type for argument.
void print(String s); print(5); // this no longer works because constructor is explicit
7 Casts
Explicit type conversion Requires user intervention Usually required for potentially unsafe conversion Narrowing conversion Type punning T echnique that subverts or circumvents the type system Changing type without altering in-memory representation
int a = 5; float * b = reinterpret_cast
Compiler assumes pointers of different types will not alias each others for optimization purposes
int foo(int *x, long *y) { foo: movl $0, (%rdi) *x = 0; xorl %eax, %eax *y = 1; movq $1, (%rsi) return *x; ret } foo is optimized to Without strict aliasing, this can happen always return 0 long l; printf("%d\n", foo((int *)&l, &l)); $ gcc-5 -O2 -o strict strict.c ; ./strict 0 9 Type Safety
Accessing data in only well-defined manner Allows only operation condoned by its type E.g. type-safe code will not access private members Data will always have value appropriate for its type Requires memory safety Data must always be initialized Arbitrary pointer arithmetic cannot be allowed Type error Contravention of type safety Can result in undefined behaviours, including crashes 10 Type Checking
The process of verifying and enforcing type safety Ensures that the operands of an operator are of compatible types O ne that either is legal or is allowed to be implicitly converted Undecidable problem Impossible to construct an algorithm that always leads to a correct yes-or-no answer Requires constraint solving on infinite set of input In practice, type systems are imperfect
11 Static Type Checking
Verifying type safety by analyzing source code Inherently conservative Will reject all incorrect programs May reject some correct programs (w.r.t the type system)
if (always_true()) { /* do something */ } else { /* type error in dead code still causes compile error */ } Limited in what can be feasibly checked E.g. division by zero error Most languages will not check it, even if it’s obvious int b = 0; int a = 5 / b; // C++ compiler will not complain 12 Dynamic Type Checking
Verifying type safety at runtime Incurs performance and/or memory overhead Examples Division by zero Requires runtime exception handling Downcasting Requires runtime type information Array bound checking Requires array to have boundary information (e.g. size of array)
13 Strong Typing
Two definitions Not always clear which one is meant 1. A type-safe language Type errors can always be detected and/or prevented Language does not allow type punning at all Requires specialized code to access in-memory representation E.g. Python struct library 2. Stricter typing rules Limited use of type coercion
14 Weakly Typed
Two definitions 1. A type-unsafe language Allows type punning Allows arbitrary pointer arithmetic Does not perform some essential runtime checks E.g. Array bound check 2. Excessive use of implicit type conversion E.g. JavaScript
15 JavaScript
16 Type Equivalence
If operand one type can be substituted with another Without conversion or coercion Strict form of type compatibility Three main type systems Nominal E.g. C/C++, Java Duck E.g. Python, Ruby Structural E.g. Go, Scala
17 Nominal Typing
Name type equivalence Most restrictive form Variables have same type if their types have same name Requirement All types must be given their own unique name Anonymous structures are given compiler internal names
struct { int a; int b; } x; // x is an object of an anonymous type
18 typedef
typedef allows creating type name alias At a syntactic level The underlying type is still the same
typedef int apple, orange; // apple and orange are alias apple a = 5; // for int orange b = a; // OK – apple and orange are equivalent Can cause loss of type safety when two semantically distinct types share the same primitive type E.g. char [] can be a C-string or an array of bytes Python has distinct types for str, unicode, and bytearray
19 Duck Typing
Least restrictive form “If it walks like a duck and it quacks like a duck, then it must be a duck” Suitability is based only on presence of attribute def delay_appointment(app, num_days): app.date += num_days class FruitBasket: def __init__(self): … self.date = 0 basket = FruitBasket() delay_appointment(basket, 5) # OK – basket has field date 20 Structural Typing
Structure type equivalence Two variables are the same if they have same structure
struct Point { struct Complex { float x; float r; float y; float i; } p = { 0., 0. }; } c = { 0., 0. };
if (p == c) // OK – p and c are structurally equivalent … Some languages restrict it to interface only Sometimes known as “compile-time duck typing”
21 Java Interface
/* a contrived example, Java already has Object.toString() */ interface Stringer { public String to_string(); } class User implements Stringer { String name; User(String name) { this.name = name; } public String to_string() { return "User: name = " + name; } public String toString() { return to_string(); }
public static void main(String[] args) { User user = new User(“foo”); System.out.println(user); // User: name = foo } } 22 Structural Typing
Same example in Go
// in fmt type Stringer interface { String() string } type User struct { name string } // automatically implements interface! func (user User) String() string { return fmt.Sprintf("User: name = %s", user.name) } func main() { user := User{name: "foo"} fmt.Println(user) // User: name = foo } 23 Manifest vs. Inferred
Manifest typing Explicit type required for all variable declaration Inferred typing Also known as type inference Can omit type information on variable declaration
auto a = 5; // a is an integer double foo(); auto b = foo(); // b is a double Sometimes may fail due to other language features Can always fall back on explicit type annotation
24 ECE326 PROGRAMMING LANGUAGES
Lecture 19 : Variance and Data Types
Kuei (Jack) Sun ECE University of Toronto Fall 2019 Enumerated Type
A data type consisting of named values Each named value behaves as a constant A variable of an enumerated type can be assigned any of the named value
enum Suit { Suit suit = SPADE; CLUB, DIAMOND, // can be compared HEART, if (suit > HEART) { SPADE, win(); }; }
2 Enumerated Type
In principle, their in-memory representation should not be exposed by the programming language In practice, C++ treats them like integers (mostly)
int a = CLUB; Suit b = HEART;
cout << a << " " << b << endl; // prints 0 2
b = (Suit)5; // this is OK in C++
Suit * sp = &b; int * ip = sp; // this is not OK without a cast
3 C Enum
Named values can be assigned an integer expression The type’s size can be altered via base specifier If you prefer your enum to take up less memory
enum Suit : char { CLUB = 1,
// 2, always previous value + 1 if not specified DIAMOND, HEART = 5,
// this is very bad but is allowed: DIAMOND == SPADE SPADE = CLUB + 1, }; 4 Enum Class
A more type-safe version of C Enum Disallows coercion to integer and other enums Named values are also “scoped”
enum class Suit { // no longer allowed CLUB, int a = Suit::CLUB; DIAMOND, HEART, enum Foo { A = 1 }; SPADE = 5, enum Bar { B = 1 }; }; // allowed // requires name resolution bool b = A == B; Suit suit = Suit::SPADE; // not allowed b = A == Suit::DIAMOND; 5 Union
Stores different data types in same memory location Type-unsafe construct, but can save memory Can be used in C to implement private members
// size of union is always the largest member // in this case it’s the double (8 bytes) union Mangle { int i; union Private { unsigned char c; const int readonly; double d; int __writeable; }; };
Mangle m = { .i = 0xffff }; cout << (unsigned)m.c << endl; // prints 255 6 Union Cast
Used to bypass strict aliasing rules C/C++ does not type check other union members Used to see memory representation of other types
union Pun { double d; unsigned char c[sizeof(double)]; };
Pun p = { .d = 1.0 }; for (unsigned i = 0; i < sizeof(double); i++) cout << (unsigned)p.c[i] << “ ”; 0 0 0 0 0 0 240 63 7 Tagged Union
A union that has a tag field to indicate which union member is being used Also known as variant type, or algebraic type struct Tagged { enum { INT, STR } tag; union { // anonymous union (members int * i; // can enter parent scope) string * s; }; Tagged(int i) : tag(INT), i(new int(i)) {} Tagged(const char * s) : tag(STR), s(new string(s)) {} ~Tagged() { if (tag == INT) delete i; else delete s; } }; 8 Rust Enums
Natively supports tagged union by mixing it with enum let tree = Tree::Node(2, enum Tree { Box::new(Tree::Node(0, Leaf, Box::new(Tree::Leaf), Node(i64, Box::new(Tree::Leaf), Box
11 Variance
Compatibility of types and their subtypes Substitutability If S is a subtype of T, then objects of type T may be replaced with objects of type S without altering program correctness Complication arises with dealing with complex types Refers to how subtyping relation changes E.g. when used as a parameter in a virtual function E.g. when used as a return type in a virtual function E.g. when used as a parameterized type
12 Variance
Suppose Apple is a subtype of Fruit Is a list of apple a subtype of list of fruit? template
Allows use of more derived type than specified Ordering of types is preserved type can be substituted by subtype int total_calories(const List
For parameterized types, only safe if immutable
List
List
// THIS IS UNSAFE fruits->add(new Orange(“Navel”, 0.5)); However, Java allows the above to occur Will cause a runtime java.lang.ArrayStoreException Unless List
15 Contravariance
Allows use of more generic type than specified Ordering of types is reversed struct Canine { struct Food {}; virtual void eat(Meat *)=0; struct Meat : public Food {}; }; struct Fruit : public Food {}; struct Apple : public Fruit {}; struct Wolf : public Canine { … }; eat(Food *) is a subtype of eat(Meat *). In other words, eat is struct Dog : public Canine { contravariant because the parameter virtual void eat(Food *) override; of its subtype is more generic. }
Dog fido; Apple * apple = new Apple(“Honeycrisp”, 0.33); fido.eat(apple); // contravariant, but NOT allowed in C++ 16 Contravariant Parameter struct Canine { struct Food {}; virtual void eat(Meat *)=0; struct Meat : public Food {}; }; struct Fruit : public Food {}; struct Apple : public Fruit {}; struct Dog : public Canine { virtual void eat(Food *) override; }
Dog fido; Apple * apple = new Apple(“Honeycrisp”, 0.33); fido.eat(apple); // this is type safe, a dog can eat any food
Canine * canine = &fido; // now fido is a canine
// still type safe, fido can only eat meat as a canine, // but meat is also food so a dog can eat it canine->eat(new Meat(“Beef”, “10oz”)); 17 Covariant Parameter struct Canine { struct Food {}; virtual void eat(Food *)=0; struct Meat : public Food {}; }; struct Fruit : public Food {}; struct Apple : public Fruit {}; struct Wolf : public Canine { virtual void eat(Meat *) override; }
Wolf kevin; // this is type safe, a wolf eats meat only kevin.eat(new Meat(“Venison”, “8oz”);
Canine * canine = &kevin; // now kevin is a canine
// NOT TYPE SAFE, wolf cannot eat anything but meat! canine->eat(new Apple(“Red Delicious”, 0.5)); 18 Invariance
Nonvariant Only the specified type is accepted Most conservative, least flexible C++ allows covariant return types
struct AnimalShelter { virtual Animal * adopt(); };
struct CatShelter : public AnimalShelter { // OK – return type is a subtype of Animal virtual Cat * adopt() override; } 19 Liskov’s Substitution Principle
Correctness of function subtyping is guaranteed if: Method parameters are contravariant Method return type is covariant Correctness of behavioural subtyping is guaranteed if: Precondition Condition that must be true before execution of some code Cannot be strengthened in a subtype Postcondition Condition that must be true after execution of some code Cannot be weakened in a subtype
20 Square Rectangle Problem
Base class can incorrectly mutate its derive class Violation of Liskov’s substitution principle Square has a stronger precondition than Rectangle class Rectangle { int width, height; public: Rectangle(int w, int h) : width(w), height(h) {} void set_width(int w) { width = w; } void set_height(int h) { height = h; } subclass cannot }; properly adhere struct Square : public Rectangle { to the interface Square(int s) : Rectangle(s, s) {} of the interface }; Square sq(5); sq.set_width(6); // oops 21 Example
Weakened post-condition
class Shipment { double dimensions[3]; public: Shipment(double w, double h, double d); virtual double cost() const; }; class DiscountShipment : public Shipment { double discount; public: virtual double cost() const override; }; Shipment * package = new DiscountShipment(…);
package->cost(); // -$10.00 !?!? 22 Contract Programming
Language support for specifying precondition, postcondition, errors, and invariants Allows business logic to be written more “aggressively” Without having to type check or verify assumptions Opposite of “defensive programming” Fills the gap that type checking cannot accomplish Some can be done statically Most are done at runtime
23 Assertion
Verifies that an expression is true at runtime
assert(i > 0); // crashes program if expression not true assert(j < 0, “j must be negative”); // supported by D language
Static version available
// syntax in D programming language static assert(Message.sizeof <= 1024, “size of message exceeds maximum packet size”);
// in C++ static_assert(sizeof(Message) <= 1024);
24 Contracts
Defining precondition and postcondition
int fun(ref int a, int b) in { assert(a > 0); assert(b >= 0, "b cannot be negative!"); } out (r) { // r binds to the return value of fun assert(r > 0, "return must be positive"); assert(a != 0); } do { a = (b – 3)*(b + 2) + 1; return b*b; }
25 Invariants
Characteristics of a class that must always be true Except in methods, when temporary violation can occur class Date { int day, hour; this(int d, int h) { day = d; hour = h; } void add_hours(int h) { hour += h; if (hour >= 24) { // does not trip the invariant! d += 1; hour -= 24; } } invariant { assert(1 <= day && day <= 31); assert(0 <= hour && hour < 24, "hour out of bounds"); } 26 } ECE326 PROGRAMMING LANGUAGES
Lecture 20 : C Preprocessor Macro
Kuei (Jack) Sun ECE University of Toronto Fall 2019 Metaprogramming
Writing code that will generate more code Generic programming Writing code with minimum assumption about data types Reflective programming Access and/or modify program structure and/or behaviour Has knowledge of compiler/interpreter’s internals Different approaches Can have overlaps E.g. C++ template programming encompasses all three
2 Metaprogramming
Macro systems Maps certain input sequence into replacement output E.g. text-based replacement “int a = 5;”.replace(“int”, “long long”) Generative programming Purpose of a program is to generate code for another program May be same or different target language Haskell compiler can generate C code from Haskell source code Template programming Parameterized code which can be instantiated upon use
3 C Preprocessor Macro
Rudimentary support for metaprogramming in C/C++ Provides text substitution of tokens Token A lexical unit, comprised of a type and value E.g. int a; int is a “keyword” token, of value “int” Preprocessor C/C++ Compiler a is an “identifier” token, of value “a” Source Object ; is a “separator” token, of value “;” code (.c) File (.o)
Preprocessor Preprocessed code Done before C source code is compiled (Macro-expanded)
4 C Preprocessor Macro
Macro A fragment of code with a name Macro expansion Name replaced by content of macro whenever name is used Preprocessor Scans the source code in multiple passes until no more replacement can be made Has no knowledge of the C language DANGER – can even use C keywords for macro names
5 Macro Constant
Also known as object-like macro Typically used to give name to a special literal
#define BUFFER_SIZE 1024
foo = (char *) malloc (BUFFER_SIZE); // becomes this foo = (char *) malloc (1024); Macros can be used after it is defined, but not before
foo = X; foo = X; #define X 4 bar = X; bar = 4; 6 Macro Function
A macro that takes zero or more parameters Looks like a normal function using parentheses
#define hello() printf(“hello world”) // macro with two parameters #define min(X, Y) ((X) < (Y) ? (X) : (Y))
// function macro can be nested min(min(a, b), c) → min(((a) < (b) ? (a) : (b)), c) → ((((a) < (b) ? (a) : (b))) < (c) ? (((a) < (b) ? (a) : (b))) : (c)) 7 Macro Function
Avoid expressions with side effects when using macro
#define min(X, Y) ((X) < (Y) ? (X) : (Y))
min(a++, b) → ((a++) < (b) ? (a++) : (b)) // a++ got called twice when macro is used // not the same behaviour if min() is a C function
// GNU C only solution (not part of official C standard) #define min(X, Y) \ ({ typeof(X) x_ = (X); \ ({ … }) acts like an expression. typeof(Y) y_ = (Y); \ Its value is the value of the (x_ < y_) ? x_ : y_; }) last statement. Similar to Rust
8 Macro Function
Multiline macro requires use of continuation \ Emulating void functions
#define print_array(array) do { \ unsigned i; \ for (i = 0; i < sizeof(array)/sizeof(*(array)); i++) \ printf("%ld ", (long)*((array)+i)); \ printf("\n"); \ } while(0) do { … } while(0) is necessary to allow short a[] = { 2 , 3, 5, 46, 345, 1, -3 }; natural use of print_array(a); semicolon at end of function. Just { … } will 2 3 5 46 345 1 -3 cause syntax error! 9 Macro Function
do { … } while(0)
#define bad_compound() { \ printf(“hello\n”); \ printf(“world\n”); }
if (x > 0) bad_compound(); else Stray semicolon printf(“x too small”); → if (x > 0) { printf(“hello\n”); printf(“world\n”); }; else printf(“x too small”);
10 Macro Function
Wrap all arguments that can be an expression To avoid problems with operator precedence
// round up an integer division: divroundup(11, 5) = 3 #define divroundup(x, y) (x + y – 1) / y
a = divroundup(b & c, sizeof (int)); → a = (b & c + sizeof (int) – 1) / sizeof (int); /* C’s operator precedence works like this */ a = (b & (c + sizeof (int) – 1)) / sizeof (int);
// better version (also wraps the entire expression) #define divroundup(x, y) (((x) + (y) – 1) / (y))
11 Stringification
Macro functions can turn arguments into a string Use # operator in front of the expression
#define WARN_IF(EXP) do { \ if (EXP) fprintf (stderr, "Warning: " #EXP "\n"); \ } while (0)
WARN_IF(x == 0); → do { if (x == 0) /* C automatically joins string literals */ fprintf (stderr, "Warning: " "x == 0" "\n"); } while (0);
12 Stringification To stringify the value of a macro, use a helper
#define stringify_value(s) stringify(s) #define stringify(s) #s #define FOO 4 stringify_value(FOO) stringify(FOO) → → stringify_value(4) “FOO” → stringify(4) → "4" Macro arguments are expanded before substitution, UNLESS they are stringified or “pasted” 13 Concatenation
Pasting macro argument with another token Use ## operator between macro and another token
#define COMMAND(NAME) { #NAME, NAME ## _command }
struct command commands[] = { struct command { COMMAND (quit), const char *name; COMMAND (help), void (*function)(); … }; }; → struct command commands[] = { { "quit", quit_command }, { "help", help_command }, … }; 14 Variadic Macro
Macro function that takes any number of arguments When you put ## in front of vargs, it deletes a comma in front of it if vargs is empty
#define eprintf(format, vargs...) \ fprintf(stderr, format, ##vargs)
eprintf("success!\n") → fprintf(stderr, "success!\n");
eprintf(“%s:%d: ", input_file, lineno) → fprintf(stderr, "%s:%d: ", input_file, lineno)
15 ECE326 PROGRAMMING LANGUAGES
Lecture 21 : C/C++ Macro Programming
Kuei (Jack) Sun ECE University of Toronto Fall 2019 C Preprocessor Macro
Provides text substitution of tokens Name replaced by content of macro whenever name is used Faster than inline functions Basically same as copy pasting code Requires care as it does not understand host language Preprocessor Done before C source code is compiled Scans the source code in multiple passes until no more replacement can be made
2 X Macro
Technique for maintaining list of tokens
#define ACTIONS \ #define X(e) e, X(STAND) \ enum Action { X(HIT) \ ACTIONS X(SURRENDER) \ }; X(DOUBLE) \ #undef X X(SPLIT) #undef #define X(e) #e, deletes a const char * action_str[] = { ACTIONS }; macro. #undef X
printf(“%s %s\n”, action_str[HIT], action_str[STAND]); HIT STAND 3 Include Directive
Adds content of file to current file E.g. action.xmc
X(STAND) void action_str2(Action e) X(HIT) { X(SURRENDER) if (e == STAND) X(DOUBLE) return “STAND”; X(SPLIT) else if (e == HIT) void action_str2(Action e) return “HIT”; #define X(name) if (e == name) \ … return #name ; else else if (e == SPLIT) #include “action.xmc” return “SPLIT”; #undef X else {} // the last else uses this {} return “ERROR”; return “ERROR”; } } 4 Predefined Macros
__FILE__ The current input file name (where macro is used) __LINE__ The current line number (where macro is used) Can be used to generate descriptive error messages
#define error(fmt, args...) \ fprintf(stderr, “%s:%d – ” fmt, __FILE__, __LINE__, \ ##args)
error(“hello %s”, “world”); macro.c:24 – hello world 5 Predefined Macros
__FUNCTION__ Name of function the macro is in Helpful for debugging __DATE__ A string that represents date of compilation __TIME__ A string that represents time of compilation Use these for serious projects (e.g. system library)
6 For Each
C programmers use macros to emulate foreach loop
struct point { int x, y; };
#define FOREACH(ptr, idx, array, size) \ for ((i) = 0, (ptr) = &array[i]; (i) < (size); \ (ptr) = &array[++(i)])
unsigned i; struct point * p; struct point arr[10]; FOREACH(p, i, arr, 10) { cout << “(” << p->x << “, ” << p->y << “)” << endl; } Note that ptr can temporarily have OOB address
7 Define Function
Can be used to define functions with same arguments
#define DEFINE_COMMAND(name) \ void name ## _command(int nargs, const char * args[])
DEFINE_COMMAND(quit) { // quit_command // exit program with exit code exit(atoi(args[0])); }
DEFINE_COMMAND(get) { // get_command FILE * f = fopen(args[0], “rt”); int c; while((c = fgetc(f)) != EOF) fputc(c, stdout); fclose(f); } 8 Comma in Arguments
Only comma inside parentheses are preserved Brackets or braces do not prevent separating arguments
#define STR(EXP) #EXP cout << STR((1, 2, 3)) << endl; # prints (1, 2, 3)
cout << STR([1, 2, 3]) << endl; error: macro "STR" passed 3 arguments, but takes just 1
// args preserves the comma and spacing between arguments #define VSTR(args...) #args cout << VSTR([1, 2, 3]) << endl; # prints [1, 2, 3]
9 Parenthesis Detection
Checks to see if argument is inside parenthesis
#define SECOND(x, n, ...) n #define CHECK(...) SECOND(__VA_ARGS__, 0,) #define PROBE(x) x, 1, #define IS_PAREN(x) CHECK(IS_PAREN_PROBE x) #define IS_PAREN_PROBE(...) PROBE(!)
IS_PAREN((1, 2)) → CHECK(IS_PAREN_PROBE (1 , 2)) → CHECK(PROBE(!)) → CHECK(!, 1,) IS_PAREN_PROBE → SECOND(!, 1, 0,) is not a macro constant → 1 IS_PAREN(hi) → CHECK(IS_PAREN_PROBE hi) → SECOND(IS_PAREN_PROBE hi, 0,) → 0 10 Self-Referential Macros
Not possible Prevents infinite recursion during macro expansion
#define foo (4 + foo) foo → (4 + foo) # expansion stops here This includes indirect self reference
#define x (4 + y) x → (4 + y) #define y (2 * x) → (4 + (2 * x)) y → (2 * x) → (2 * (4 + y)) 11 Self-Referential Macros
When a macro expands, it is disabled, which prevents further expansion of same macro in the same scan Can cause another macro to not expand E.g. deferred expression #define EMPTY() #define DEFER(x) x EMPTY() #define EXPAND(...) __VA_ARGS__ int A() { return 456; } // not affected A() macro #define A() 123 DEFER(A)() → A EMPTY()() // A cannot expanded here → A () // requires one more scan printf(“%d”, DEFER(A)()); // prints 456 12 Self-Referential Macros
Forcing another scan
#define EMPTY() #define DEFER(x) x EMPTY() #define EXPAND(...) __VA_ARGS__ int A() { return 456; } // not affected A() macro #define A() 123 EXPAND(DEFER(A)()) → EXPAND(A EMPTY()()) → EXPAND(A ()) → A () → 123 printf(“%d”, EXPAND(DEFER(A)())); // prints 123 This behaviour can be used to implement recursion 13 Advanced Concatenation
Can be used to create token that is another macro
#define CAT(a, args...) a ## args #define IFF(c) CAT(IFF_, c) #define IFF_0(t, ...) __VA_ARGS__ #define IFF_1(t, ...) t #define FALSE 0 #define CAN_DO() 1
IFF(FALSE)(5, 9) → IFF(0)(5, 9) → IFF_0(5, 9) → 9 IFF(CAN_DO())(5, 9) → IFF(1)(5, 9) → IFF_1(5, 9) → 5 14 When Statement
Previous example Only works if macro expands to 1 or 0 We want a generalized when statement
WHEN(cond, true-expression, false-expression) Idea: !! operator (double negation) Converts a number to 1 or 0, E.g. !!12 = !0 = 1 Can be achieved using macro’s pattern matching
15 When Statement
Try 1:
#define SECOND(a, b, ...) b #define CHECK(...) SECOND(__VA_ARGS__, 0) #define PROBE() ~, 1 #define NOT(x) CHECK(_NOT_ ## x) #define _NOT_0 PROBE() #define BOOL(x) NOT(NOT(x))
BOOL(123) → NOT(NOT(123)) → CHECK(_NOT_ ## NOT(123)) → CHECK(_NOT_NOT(123)) → SECOND(_NOT_NOT(123), 0) → 0
/facepalm 16 When Statement
Try 2:
#define CAT(a, args...) a ## args #define SECOND(a, b, ...) b #define CHECK(...) SECOND(__VA_ARGS__, 0) #define PROBE() ~, 1 #define NOT(x) CHECK(CAT(_NOT_, x)) #define _NOT_0 PROBE() #define BOOL(x) NOT(NOT(x))
BOOL(123) → NOT(NOT(123)) → NOT(CHECK(CAT(_NOT_,123))) → NOT(CHECK(_NOT_123))) → NOT(SECOND(_NOT_123, 0)) → NOT(0) → CHECK(CAT(_NOT_, 0)) → CHECK(_NOT_0) → CHECK(PROBE()) → CHECK(~, 1) → SECOND(~, 1, 0) → 1 17 When Statement
Joining with previous example
#define CAT(a, args...) a ## args #define SECOND(a, b, ...) b #define CHECK(...) SECOND(__VA_ARGS__, 0) #define PROBE() ~, 1 #define NOT(x) CHECK(CAT(_NOT_, x)) #define _NOT_0 PROBE() #define BOOL(x) NOT(NOT(x)) #define IFF(c) CAT(IFF_, c) #define IFF_0(t, ...) __VA_ARGS__ #define IFF_1(t, ...) t #define WHEN(cond, t, f) IFF(BOOL(cond))((t), (f)) int a = WHEN(12, 5, 7), b = WHEN(0, 3, 8); a = 5, b = 8 18 Optional Compilation
Enable or disable parts of the code Not even compiled at all, won’t make it to final executable
int take_action(Hand hand, Action a) { if (a == SURRENDER) { #ifdef ALLOW_SURRENDER hand.profit = hand.bet / 2.0; hand.state = COMPLETE; return ERR_OK; // action accepted #else return ERR_INVALID; // action rejected #endif } … return ERR_INVALID; } 19 Optional Compilation
Used in header to avoid being included more than once
/* if SHOE_H is not defined */ #ifndef SHOE_H #define SHOE_H
/* declaration of functions and definition of classes */
#endif
#include “shoe.h” // OK – SHOE_H not defined #include “shoe.h” // nothing included this time
Some compilers support #pragma once Same effect, shorter to write, but requires compiler support 20 __cplusplus
A predefined macro Used if mixing C and C++ code This requires lots of care, because C is not a subset of C++ Extern “C” Code within this block are C code, not C++
#ifdef __cplusplus extern “C” { #endif struct hand * alloc_hand(struct shoe * new); #ifdef __cplusplus } #endif 21 Version Control
Integer, comparison, relational operators are supported
#if EASYDB_VERSION > 1 #define ALLOW_SURRENDER #endif int foo() { #if VERBOSE >= 2 printf(“entering foo"); #endif … } #if !(defined __LP64__ || defined __LLP64__) || \ defined _WIN32 && !defined _WIN64 // we are compiling for a 32-bit system #endif 22 Static Assertion
C language does not have static_assert until C11 Useful to check compile-time constants E.g. sizeof(struct packet) <= 1024 Solution: exploit conditional operator in C
#define STATIC_ASSERT(COND,MSG) typedef char \ static_assertion_##MSG[(COND)?1:-1]
STATIC_ASSERT(1, this_should_be_true); STATIC_ASSERT(0, this_will_be_false);
error: size of array ‘static_assertion_this_will_be_false’ is negative 23 Conclusion
C macros provide some metaprogramming capability Uses token based substitution Invoked by compiler as first part of translation Inherently unsafe, requires care Reasonably powerful, when coupled with existing C constructs C preprocessor Helps manage code into files Allows for optional compilation Can be abused – code will become very difficult to read If executable size not a concern, should use inheritance instead
24 ECE326 PROGRAMMING LANGUAGES
Lecture 22 : C++ Metaprogramming
Kuei (Jack) Sun ECE University of Toronto Fall 2019 Constant Expression
Can be evaluated at compile time
const int a = 5 + 7; // compiler would generate a = 12 directly constexpr keyword Declares a compile-time variable, function, or class May not exist at runtime (unlike constant variables) Variable Can only be assigned constant expression Function Arguments must only be constant expression
2 Constexpr constexpr int a[] = { 1, 2, 3, 4 }; constexpr int sum(const int a [], unsigned n) { return (n == 0) ? 0 : a[0] + sum(a+1, n-1); }
// a good compiler should generate x = 10 directly int x = sum(a, sizeof(a)/sizeof(int)); template
Tells compiler to evaluate function at compile time Can significantly increase compile time Compiler must ensure computation cannot crash itself Performs extensive type-checking E .g. Array out of bound check C++11 Restrictive on what’s allowed in a constexpr function No loops – must rely on recursion Exactly one return statement allowed in body No local variables, arguments only
4 Constexpr Examples constexpr int factorial(int n) { return n <= 1 ? 1 : (n * factorial(n - 1)); }
/* lexicographical comparison of two constant strings */ /* returns positive if a > b, negative if a < b, 0 if equal */ constexpr int constcmp(const char * a, const char * b, unsigned i=0) { return (a[0] == '\0') ? (a[0] - b[0]) : ( (a[0] == b[0]) ? constcmp(a+1, b+1, i+1) : a[0] - b[0] ); } constcmp("he", "hello") // -108 constcmp("hello", "hell“) // 111 (ASCII for o) 5 Constexpr Function
Depends on compiler implementation May or may not be turned into a runtime function Depends on argument
print_const
// function is also used at runtime cout << constcmp("hello", argv[0]) << endl; // 58 Upgrade to C++14 Allows loops and local variables!
6 Compile-Time Function
Useful for pre-calculating values E.g. crc64 hash of constant strings Can be used in conjunction with templates Referentially transparent Does not have side effects Note: this is only true if the function is run at compile time. If it is converted to a run time function, it can modify global variables! Haskell does this a lot The entire program may be optimized down to constants
7 Constexpr Class
Its instances can be compile-time objects Same restrictions apply to methods, but can use members
class Rectangle { int _h, _w; public: // a constexpr constructor constexpr Rectangle (int h, int w) : _h(h), _w(w) {} constexpr int area () { return _h * _w; } };
constexpr Rectangle rekt(10, 20); // compile-time print_const
Rectangle rect(5, argc); // runtime Rectangle cout << rect.area() << endl; // 5 (if argc == 1) 8 Static Introspection
Making programming decisions based on types At compile time (hence “static”) Limited support in C E.g. sizeof and typeof (non-standard) C++ template Originally designed for generic programming Its implementation allows for some introspection capability Requires exploiting template substitution rules Originally part of Boost library, now standardized for C++11
9 Type Trait
#include
template 10 SFINAE Substitution Failure Is Not An Error An invalid substitution of template parameters is not an error C++ creates a set of candidates for overload resolution E.g. during function overloading For templates, if parameter substitution fails, then that template will be removed from the candidate list without stopping on compilation error Note: error in template body is not detected before resolution No error is produced if more than one candidate exists 11 SFINAE example struct Test { typedef int foo; // internal type to Test }; template Returns size of an expression at compile time typedef char type_test[42]; type_test& f(); // f() won't actually be called at runtime cout << sizeof(f()) << endl; // 42 Can be exploits by SFINAE Running example Want to check if class has serialize function If yes, call it, otherwise, call to_string() instead 13 Member Function Pointer Similar to function pointer, except must specify class Has a different type than normal functions struct A { string serialize() const { return "I am a A!"; } }; typedef string (A::* afunc_t)(); A a; afunc_t af = &A::serialize; cout << (a.*af)() << endl; // call member function A * ap = &a; cout << (a->*af)() << endl; // call member function 14 Method Check template /* checks if class T really has serialize method (not field) */ template /* class T has serialize */ template /* SFINAE - class t does not have serialize */ template // The constant used as a return value for the test. static const bool value = sizeof(test // 1. NO: type U = string(A::*)() != typeof(&A::serialize) template static yes& test(really // 2. YES: type U = string(A::*)() const == typeof(&A::serialize) template static yes& test( really // 3. YES: this template cannot fail, but has lowest precedence template // 2. YES: type U = string(A::*)() const == typeof(&A::serialize) template static yes& test( really // 3. YES: this template cannot fail, but has lowest precedence template // 1. NO: B::serialize does not exist! template static yes& test(really // 2. NO: B::serialize does not exist! template static yes& test( really // 3. YES: this template cannot fail, but has lowest precedence template // 2. NO: B::serialize does not exist! template static yes& test( really // 3. YES: this template cannot fail, but has lowest precedence template // 1. NO: type U = string(C::*)() != typeof(&C::serialize) template // 2. NO: type U = string(C::*)() const != typeof(&C::serialize) template // 3. YES: this template cannot fail, but has lowest precedence template Lecture 25 : SFINAE (C++98) Kuei (Jack) Sun ECE University of Toronto Fall 2019 Assignment 3 Object Relational Mapping Maps database rows into in-memory objects Relational Database Data are organized into tables (analogous to classes) and rows (analogous to instances) Like classes, format of database tables must be specified The specification is called database schema EasyDB Very simple in-memory database, does not save to disk You will implement a (small) part of it for assignment 4 2 Database Schema EasyDB Schema Language Support four data types User { firstName: string; string (any length) lastName: string; float (64 bits) height: float; age: integer; integer (64 bits) } foreign key Account { Foreign Key user: User; // foreign type: string; Analogous to a pointer balance: float; References another row in } another table 3 Database Client First milestone Write the client code to communicate with the server Over the network, through TCP/IP Requires sending and receiving packets Packet Data sent over the network Requires serialization and deserialization Converting in-memory data to/from network format 4 ORM Layer Second milestone Convert raw formats to/from Python objects Provides object-oriented interface instead of database commands Performs extensive type safety checks Coding requires use of advanced Python features Metaclass Descriptors 5 Database Schema Written in Python for the ORM Can be exported to EasyDB schema language Includes options for more rigorous type checking class User(orm.Table): firstName = orm.String() lastName = orm.String() height = orm.Float(blank=True) age = orm.Integer(blank=True) class Account(orm.Table): user = orm.Foreign(User) type = orm.String(choices=["Savings", "Chequing",], default="Chequing") balance = orm.Float() 6 ORM Interface Underlying database abstracted away Provides user with a way to work with any database # create new object # update existing object >> joe = User(db, firstName="Joe", >> joe.height = 7.4 .. lastName="Harris", age=32) >> joe.save() >> joe.save() # save to database # delete an object # search for objects in database >> result[1].delete() >> result = User.filter(db, .. lastName="Harris") # verify deletion >> result >> User.count(db, [ Most database only supports few data types EasyDB only supports integer, float, and string At the ORM layer, we can support more! Third milestone DateTime Field Corresponds to Python’s DateTime class Coordinate Field A tuple of two values: longitude and latitude 8 SFINAE For C++98 9 Static Introspection Making programming decisions based on types At compile time (hence “static”) Limited support in C E.g. sizeof and typeof (non-standard) C++ template Originally designed for generic programming Its implementation allows for some introspection capability Requires exploiting template substitution rules Originally part of Boost library, now standardized for C++11 10 Type Trait #include template 11 SFINAE Substitution Failure Is Not An Error An invalid substitution of template parameters is not an error C++ creates a set of candidates for overload resolution E.g. during function overloading For templates, if parameter substitution fails, then that template will be removed from the candidate list without stopping on compilation error Note: error in template body is not detected before resolution No error is produced if more than one candidate exists 12 SFINAE example struct Test { typedef int foo; // internal type to Test }; template Returns size of an expression at compile time typedef char type_test[42]; type_test& f(); // f() won't actually be called at runtime cout << sizeof(f()) << endl; // 42 Can be exploits by SFINAE Running example Want to check if class has serialize function If yes, call it, otherwise, call to_string() instead 14 Member Function Pointer Similar to function pointer, except must specify class Has a different type than normal functions struct A { string serialize() const { return "I am a A!"; } }; typedef string (A::* afunc_t)(); A a; afunc_t af = &A::serialize; cout << (a.*af)() << endl; // call member function A * ap = &a; cout << (a->*af)() << endl; // call member function 15 Method Check (C++98) template /* checks if class T really has serialize method (not field) */ template /* class T has serialize */ template /* SFINAE - class T does not have serialize */ template // The constant used as a return value for the test. static const bool value = sizeof(test // 1. NO: type U = string(A::*)() != typeof(&A::serialize) template static yes& test(really // 2. YES: type U = string(A::*)() const == typeof(&A::serialize) template static yes& test( really // 3. YES: this template cannot fail, but has lowest precedence template // 2. YES: type U = string(A::*)() const == typeof(&A::serialize) template static yes& test( really // 3. YES: this template cannot fail, but has lowest precedence template // 1. NO: B::serialize does not exist! &B::serialize fails. template static yes& test(really // 2. NO: B::serialize does not exist! template static yes& test( really // 3. YES: this template cannot fail, but has lowest precedence template // 2. NO: B::serialize does not exist! template static yes& test( really // 3. YES: this template cannot fail, but has lowest precedence template // 1. NO: type U = string(C::*)() != typeof(&C::serialize) template // 2. NO: type U = string(C::*)() const != string * template // 3. YES: this template cannot fail, but has lowest precedence template Current template does not support functors It should, we will fix this in C++11 struct D { struct Functor { string operator()() { return "I am a D!"; } }; Functor serialize; }; D d; cout << d.serialize() << endl; // “I am a D!” cout << has_serialize make_packet function Calls serialize if class has it, otherwise use to_string() template A a; make_packet(a); 23 Applying Method Check It doesn’t work Static type system is conservative. Dead code is still type checked. template 24 Workaround enable_if If condition B is true, typedef T in the struct named type // This struct doesn't define "type" and will trigger SFINAE template // partial specialization if B is true, struct defines “type” to T template // OK enable_if // FAIL - enable_if Use enable_if to switch between function overload template Lecture 26 : Template Metaprogramming (C++11) Kuei (Jack) Sun ECE University of Toronto Fall 2019 decltype Returns type of expression A a; decltype(a.serialize()) test = "test"; // requires ‘a’ decltype(A().serialize()) t2 = "test 2"; // prefer this What if A does not have default constructor? struct Default { int foo() const { return 1; } }; struct NoDefault { NoDefault(const NoDefault&) {} int foo() const { return 1; } }; decltype(NoDefault().foo()) nd = 2; // FAIL 2 Fake Reference Recall in container_of macro #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type, member) ); \ }) We can use this to create a fake reference to avoid constructing objects for the expression #define FAKEREF(T) (*(T *)nullptr) decltype(FAKEREF(NoDefault).foo()) ndf = 5; // OK cout << test << ndf << endl; 5 3 std::declval Template version of FAKEREF Should only be used in unevaluated context Inside sizeof or decltype Use this instead of FAKEREF in C++ decltype(std::declval // error: cannot declare pointer to ‘struct NoDefault&’ decltype(FAKEREF(NoDefault &).foo()) nd3 = 7; 4 Method Check (C++11) Using constexpr, decltype, and declval template template // Argument is used to give precedence to first overload of ‘test’ static constexpr bool value = test New has_serialize now works for functors too! struct A { string serialize() const { return "I am a A!";} }; struct B { int x; }; struct C { string serialize; }; struct D { struct Functor { string operator()() { return "I am a D!";} } serialize; }; cout << has_serialize::value << endl; // 1 – has serialize function cout << has_serialize::value << endl; // 0 – no serialize cout << has_serialize :stdtrue_type: struct true_type { enum { value = true }; }; std:false_type: struct false_type { enum { value = false }; }; Use partial template specialization for precedence template template template template template template Maps a sequence of any types to void Enables partial template specialization Used to detect ill-formed types in SFINAE context template cout << is_incrementable 9 Example Two template parameters Can type T be assigned type U template Lecture 27 : Variadic Template Kuei (Jack) Sun ECE University of Toronto Fall 2019 Variadic Function Function with variable number of parameters E.g. printf, scanf Supported all the way back in C Denoted by the ellipsis syntax int eprintf(const char * fmt, ...); Custom-built variadic functions are type-unsafe Type checking not done at compile time Note: GCC extension __attribute__((format(printf, 1, 2))) 2 cstdarg Provides macro functions to extract arguments Limitation: requires a “pivot” argument i.e. must have at least one known argument #include va_start(va_list ap, T pivot) Initialize ap with the pivot argument (can be of any type) va_arg(va_list ap, T) Retrieves next argument and cast it to type T va_end(va_list ap) End using ap and clean up resource va_copy(va_list dst, va_list src) Copy src to dst in its current state May be halfway through the arguments when copied 4 Example Finds largest number out of n integers int find_max(int n, ...) { va_arg is type- int i, val, largest; unsafe! It assumes va_list vl; the caller is passing va_start(vl, n); in the expected type. largest = va_arg(vl, int); for (i = 1; i < n; i++) { val = va_arg(vl, int); largest = (largest > val)? largest : val; } va_end(vl); return largest; } find_max(7, 702, 422, 631, 834, 892, 104, 772); // 892 5 C Macro Trick Count number of arguments and pass to first argument Note: this macro can be improve to support more arguments #define PP_NARG(...) PP_NARG_(__VA_ARGS__, PP_RSEQ_N()) #define PP_NARG_(...) PP_ARG_N(__VA_ARGS__) /* PP_ARG_N() returns the 10th argument! */ #define PP_ARG_N( _1, _2, _3, _4 _5, _6, _7, _8, _9, N, ...) N /* PP_RSEQ_N() counts from 9 down to 0 */ #define PP_RSEQ_N() 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 #define max(args...) find_max(PP_NARG(args), ##args) max(702, 422, 631, 834, 892, 104, 772) → find_max(PP_NARG(…), 702, 422, 631, 834, 892, 104, 772) → find_max(PP_NARG_(702, 422, …, 772, PP_RSEQ_N()), 702, 422, …) /* 1, 2, …, 7, 8, 9, 10, …*/ → find_max(PP_ARG_N(702, 422, …, 772, 9, 8, 7, …, 2, 1, 0), 702, …) → find_max(7, 702, 422, 631, 834, 892, 104, 772) 6 Variadic Template Template with variable number of parameters template From assignment 1 starter code, shoe.cpp template template /* read next character from file, see if it’s a valid card */ char c = getc(file); if (is_in(c, 'A', 'T', 'J', 'Q', 'K')) { return c; } 8 Example template New method for std::vector in C++11 Builds object directly within the vector Requires neither move or copy In contrast, vector::push_back requires premade objects Requires forwarding arguments to constructor Without a priori knowledge of constructor signature of type T 10 std::forward dSimilar to st ::move, but for variable arguments Syntax requires ... after the variable argument expansion template auto c = make_and_print 11 Example Print the content of template containers E.g. std::vector, std::list These containers usually have an optional second parameter Custom allocators are used for performance reasons /* 1st parameter is a templated class with two parameters */ template class ContainerType, typename T, typename Alloc> void print_container(const ContainerType Print the content of template containers Will take any number of template parameters Works as long as the container supports foreach loop template class ContainerType, typename T, typename... Args> void print_container(const ContainerType Python tuple in C++ Written using variadic template template template Tuple Tuple 16 SFINAE and Variadic Helper template to map index in tuple to its type template // base template - stores type of first element of tuple template // stores type of kth element of tuple (zero-indexed) template 17 Recursive Structure Tuple Similar to peeling an onion For k > 0, it will peel off k template parameters Tuple struct hodor<1, Tuple struct hodor<0, Tuple access Tuple // can modify too, since access returns a mutable reference access<2>(t1) = "world"; cout << access<2>(t1) << endl; // world cout << access<3>(t1) << endl; // error – invalid use of incomplete… 20 enable_if Select between base case or recursion template Lecture 28 : Introduction to Rust Kuei (Jack) Sun ECE University of Toronto Fall 2019 Introduction Designed and developed at Mozilla Research First released in Summer 2010 Stable since Spring 2015 Systems language focused on safety Type safety, memory safety, safe concurrency Performance comparable to C/C++ Compiler performs extensive safety checks Compile time an be much slower than C/C++ compilers Syntactically similar to C/C++ and Haskell 2 Installation Custom installed on UG machines Add RUSTUP_HOME to environment variable setenv RUSTUP_HOME /cad2/ece326f/rust # add to ~/.cshrc export RUSTUP_HOME=/cad2/ece326f/rust # add to ~/.bashrc Run rustc --version Make sure you get this output: rustc 1.38.0 (625451e37 2019-09-23) https://rustup.rs/ Installs latest version of Rust Follow its instruction to install for your home machine 3 Alias in Rust No aliases Cannot have two pointers pointing to same memory address Guarantees memory safety without garbage collection Compiler can deduce when to free memory Ownership All lvalues have unique owners E.g. the owner of local variables is their function When the owner goes out of scope, it frees what it owns Without alias, no cycles can be formed Memory ownership will take the shape of a tree 4 Ownership struct Foo { int * array; main Foo() : array(new int[5]) {} }; int bar(int y){ w bar z Foo f; int x = y + 3; return x + f.array[0]; y f x } int main() { int * w = new int(5); f.array int z = bar(*w); return z; } 5 Ownership struct Foo { int * array; main Foo() : array (new int[5]) {} ~Foo() { delete array;} }; w bar z int bar(int y){ delete Foo f; statements int x = y + 3; y f x return x + f.array[0]; automatically } inserted by compiler after int main() { static analysis f.array int * w = new int(4); of ownership int z = bar(*w); delete w; return z; } 6 Passing Variable struct Foo { int * array; main Foo() : array(new int[5]) {} }; int bar(int * y){ w bar z Foo f; int x = *y + 3; return x + f.array[0]; 5 y f x } int main() { Who owns the int * w = new int(5); heap variable f.array int z = bar(w); “5” now? return z; } 7 1. Takeover (Move) struct Foo { int * array; main Foo() : array(new int[5]) {} }; By default, bar int bar(int * y){ takes over w bar z Foo f; ownership. int x = *y + 3; delete y; 5 y f x return x + f.array[0]; } int main() { f.array int * w = new int(5); int z = bar(w); /* cannot use w anymore */ return z; } 8 2. Borrow struct Foo { int * array; main Foo() : array(new int[5]) {} }; int bar(borrowed int * y){ w bar z Foo f; int x = *y + 3; return x + f.array[0]; 5 y f x } int main() { bar can also int * w = new int(5); “borrow” 5’s f.array int z = bar(w); ownership delete w; from main return z; } 9 Ownership Borrow Lender must outlive borrower Lifetime Interval in which an entity is valid Begins when a variable is created, ends when it’s destroyed Passing variable If variable can be copied (e.g. primitive types), pass by value If parameter declared as borrow, lend variable if possible Otherwise, performs a move (give up ownership) 10 Hello World Like in C/C++, requires a main function Function declared using fn keyword println! is a macro function (denoted by ! symbol) To compile, call rustc -o hello main.rs hello is the name of executable /* main.rs */ // Rust uses same as C/C++ comments fn main() { println!(“hello world”); } 11 Function General syntax fn funcname(argname: argtype…) -> returntype { … statements … returnvalue } In Rust, the last expression (no semicolon) is returned fn foo(a: f64, b: f64) -> f64 { a * b // return value } fn baz(a: f64, b: f64) -> f64 { return a * b; // same as above } 12 Variable Variable declaration in Rust is a statement Type can usually be automatically deduced If assigned a literal, it has the type of the literal If assigned a return value from a function, the return type fn baz(a: f64, b: f64) -> f64 { return a * b; // same as above } let a = 20; // an integer type (e.g. i32 or i64) let b: i64 = 30; // explicitly specified as i64 let c = baz(2.0, 3.0); // f64 by type inference 13 Strongly Typed Does not allow implicit conversion, even if its widening Use as operator to cast between types let a: i32 = 1; let b: i64 = 2; let c = a * b; // FAIL – does not allow implicit conversion error: cannot multiply `i64` to `i32` let c = a as i64 * b; let x = [1, 2, 3]; println!("{}", x[a]); error: cannot be indexed by `i32` 14 Println Format Similar to Python’s string.format() Each set of curly braces is an argument Type conversion automatically done (coerce to string) Argument needs to implement Display or Debug trait For now, it is similar to overriding a virtual function in base class let s = "hello world"; let a = 5; println!("{}, {}", s, a); hello world, 5 15 Immutability By default, variables in Rust are constant (immutable) If you want to change it, use mut keyword let x = 5; println!("The value of x is: {}", x); x = 6; // BAD! error: cannot assign twice to immutable variable let mut x = 5; x = 6; // OK 16 Constant Constant in Rust behaves like constexpr in C++ It is a compile-time expression Can be declared in global scope (unlike let) Type must be specified const MAX_POINTS: u32 = 100_000; underscore in literal has no semantic meaning It helps programmer to more easily read bigger numbers 17 Shadowing Rust allows same name to be used multiple times Previous bindings are “shadowed”, cannot be accessed Useful once code becomes more complex let x = 5; let x = x + 1; let x = x * 2; println!("The value of x is: {}", x); The value of x is: 12 let spaces = " "; let spaces = spaces.len(); // OK to change type as well 18 Primitive Types Boolean Floating Point true or false f32 or f64 Integer signed: i8, i16, i32, i64, isize unsigned: u8, u16, u32, u64, usize usize and isize depends on architecture (either 32 or 64 bits) Character char Can be unicode characters as well. Not just ASCII. i.e. Unlike char in C, not guaranteed to be 1 byte 19 String utf-8 encoded (thus supports unicode characters) String literal The type is &str (reference to a string slice) let s: &str = "hello world"; String slice Reference to immutable string data somewhere String literals are stored in program binary Similar to const char * in C++ 20 String String type mutable string, content can be updated let mut s1 = String::from("foo"); // convert to mutable s.push_str("bar"); // append “bar” to s let mut s2 = "lo".to_string(); // another way to convert s.push('l'); // push a single character let mut s3 = String::new(); // creates an empty string // formatted string (created from a macro function) let s = format!("{}-{}-{}", s1, s2, s3); 21 Tuple Rust has built-in tuple type, similar to Python let tup: (i32, f64, u8) = (500, 6.4, 1); Allows for unpacking let tup = (500, 6.4, 1); let (x, y, z) = tup; Also allows access to each element let a = x.0; let b = x.1; let c = x.2; 22 Array Similar in syntax to C array Performs compile time bound checking Tracks its own length, similar to Java array let a = [1, 2, 3, 4, 5]; let months = ["January", "February", "March", "April", …]; let first = a[0]; let second = a[1]; // array does not support Display trait, must use {:?} println!("{:?}: {}", a, a.len()); [1, 2, 3, 4, 5]: 5 23 Function Does not support function overloading Does not support default parameters Like in C, uses block scoping Blocks are expressions in Rust! let y = { let x = 3; x + 1 }; println!("The value of y is: {}", y); // 4 24 Unit Type () Looks like an empty tuple Is a type of its own It’s purpose is to be “useless” Everything in Rust is an expression Similar to many functional languages A function without return type returns it Similar to void in C/C++ 25 ECE326 PROGRAMMING LANGUAGES Lecture 30 : Control Flow in Rust Kuei (Jack) Sun ECE University of Toronto Fall 2019 Control Flow No parenthesis Conditions in control flow does not need parenthsis Unlike in C/C++ Expressions Everything in Rust is an expression Even loops Their return value is the last expression in each block do not add a semicolon, which makes it a statement All branches must return the same type Otherwise the compiler complains 2 If/Else let n = 5; if n < 0 { ← normal usage print!("{} is negative", n); } else if n > 0 { print!("{} is positive", n); } else { print!("{} is zero", n); use as an expression } ↓ let big_n = if n < 10 && n > -10 { println!(", and is a small number, increase ten-fold"); 10 * n // no semicolon here } else { println!(", and is a big number, halve the number"); n / 2 }; // semicolon here (let is a statement) println!("{} -> {}", n, big_n); 3 Loop An infinite loop Requires break statement to exit loop { count += 1; if count == 3 { println!("three"); // Skip the rest of this iteration continue; } println!("{}", count); if count == 5 { println!("OK, that's enough"); // Exit this loop break; } 4 } Retry Example Loop until “successful” Loop can also be an expression let mut counter = 0; let result = loop { counter += 1; if counter == 10 { break counter * 2; // return value of loop } println!("I only exit on 10"); }; 5 While Loop Loop while condition is true let mut n = 1; while n < 101 { if n % 15 == 0 { println!("fizzbuzz"); } else if n % 3 == 0 { println!("fizz"); } else if n % 5 == 0 { println!("buzz"); } else { println!("{}", n); } n += 1; } 6 For Loop Uses iterator to loop through elements of a collection Range Similar to Python, creates an iterator for integers a..b Loops from a to b-1 for n in 1..101 a..=b Loops from a to b for n in 1..=100 7 Vector Similar to std::vector Element type can be inferred by first element inserted vec! Macro to initialize the vector let mut v = vec![1, 2, 3]; // type of v is Vec for i in &v { // read-only loop println!("{}", i); } for i in &mut v { // read-update loop *i += 10; } 8 Match The switch statement of Rust Except much more powerful and more frequently used Has ability to pattern match Known as “destructuring” Can specify conditions Similar to a set of if…else if statements Compiler requires all possible values to be handled E.g. it is an error to miss one of the enum values 9 Match Syntax match expression { pattern => expression, // this is called an “arm” pattern => expression, … } Example let boolean = true let binary = match boolean { false => 0, true => 1, }; println!("{} -> {}", boolean, binary); 10 Match Match on an integer let number = 13; … is inclusive, .. is println!("Tell me about {}", number); exclusive (e.g. 13..19 match number { matches 13 to 18) // Match a single value 1 => println!("One!"), // Match several values 2 | 3 | 5 | 7 | 11 => println!("This is a prime"), // Match an inclusive range 13...19 => println!("A teen"), // Handle the rest of cases _ is the catch-all _ => println!("Ain't special"), case (same as } default in C/C++) 11 Destructuring Allows matching of value(s) inside tuples let pair = (0, -2); match pair { (0, y) => println!("First is `0` and `y` is `{:?}`", y), (x, 0) => println!("`x` is `{:?}` and last is `0`", x), _ => println!("It doesn't matter what they are"), } Guards (the if conditions) match pair { (x, y) if x == y => println!("These are twins"), (x, y) if x + y == 0 => println!("Antimatter, kaboom!"), (x, _) if x % 2 == 1 => println!("The first one is odd"), _ => println!("No correlation..."), } 12 Enum in Rust Much more powerful than enums in C/C++ Similar to enum class, values must be scoped Unlike C/C++, can never be assigned an integer value enum Coin { Penny, Nickel, Dime, Quarter,} fn value_in_cents(coin: Coin) -> u32 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter => 25, } // no semicolon here so we return what match returns } 13 Variant Type Also known as tagged union in C/C++ Algebraic data type in functional programming languages Data can be placed directly into each variant of enum Each variant can have its own set of data enum IpAddr { V4(u8, u8, u8, u8), V6(String), } let home = IpAddr::V4(127, 0, 0, 1); let loopback = IpAddr::V6(String::from("::1")); 14 Destructuring Access data inside an enum enum Color { RGB(u32, u32, u32), HSV(u32, u32, u32), CMYK(u32, u32, u32, u32), } match color { // binds name r, g, b to each piece of data inside variant RGB Color::RGB(r, g, b) => println!("Red: {}, green: {}, and blue: {}!", r, g, b), Color::HSV(h, s, v) => println!("Hue: {}, saturation: {}, value: {}!", h, s, v), Color::CMYK(c, m, y, k) => println!("Cyan: {}, magenta: {}, yellow: {}, key (black): {}!", c, m, y, k), } 15 Option Commonly used enum Replaces use of null pointer provides safety for “pointers” that can be null enum Option Useful for matching one specific enum Looks much cleaner than match // syntax 1: using match match optional { Some(i) => { println!("This is a really long string and `{:?}`", i); }, _ => {}, // required because match is exhaustive }; // syntax 2: using if let if let Some(i) = letter { println!("Matched {:?}!", i); } else { // Destructure failed. Change to the failure case. println!("Was not a Some!"); } 17 Result Commonly used for error handling enum Result unwrap() / expect(msg) Attempt to access data inside Ok, panic if Err is found let f = File::open("hello.txt").unwrap(); let f = File::open("hello.txt").expect("Failed to open hello.txt"); ? operator Returns the same error inside Err() immediately Requires return type of function to also be Result Lecture 31 : Structures and Generics Kuei (Jack) Sun ECE University of Toronto Fall 2019 Structure Syntax structname { fieldname: type, fieldname: type, … } Examples Field separated struct Point { x: f32, y: f32,} by comma! Definition does struct Rectangle { p1: Point, p2: Point,} not end with Instantiation semicolon! let p: Point = Point { x: 0.3, y: 0.4 }; let rect = Rectangle { p1: Point { x: 0.2, y: 0.5 }, p2: point, }; 2 Destructuring Structures can also be destructured let p: Point = Point { x: 0.3, y: 0.4 }; let pair = (1, 0.1); // destructure a structure let Point { x: my_x, y: my_y } = p; // destructure a tuple let Pair(integer, decimal) = pair; println!("my point at ({}, {})", my_x, my_y); println!("pair contains {:?} and {:?}", integer, decimal); 3 Methods Method definitions are separate from its structure Define methods within an impl block // Implementation block, all Point methods go in here impl Point { fn origin() -> Point { origin and new are Point { x: 0.0, y: 0.0 } static methods. } translate is an fn new(x: f64, y: f64) -> Point { instance method. Point { x: x, y: y } } fn translate(&mut self, x: f64, y: f64){ self.x += x; The type of self is self.y += y; Point and does not } need to be specified. } 4 Methods impl Rectangle { fn perimeter(&self) -> f64 { let Point { x: x1, y: y1 } = self.p1; let Point { x: x2, y: y2 } = self.p2; 2.0 * ((x1 - x2).abs() + (y1 - y2).abs()) } fn translate(&mut self, x: f64, y: f64){ self.p1.translate(x, y); self.p2.translate(x, y); } } let mut square = Rectangle { p1: Point::origin(), p2: Point::new(1.0, 1.0), }; let len = square.perimeter(); println("my perimeter is: {}", len); square.translate(2.0, 3.0); // OK – square mutable 5 Ownership Recall that & operator means borrow Instance methods should always borrow self Except for a “destructor”, i.e. instance destroyed afterwards impl Rectangle { fn destroy(self) { println!("Destroying Rectangle"); } } let rect = Rectangle{ p1: Point::new(.1, .2), p2: Point::new(.3, .4) }; rect.destroy(); // cannot use rect after this point in code 6 Generics Enables generic programming Simpler than C++ template programming Only data types are allowed in type parameter Example struct Point If a type is generic, its methods must also be struct Point impl fn main() { let p = Point { x: 5, y: 10 }; println!("p.x = {}", p.x()); } 8 Specialization Impl block can be specialized for a concrete type Similar to C++ templates Only the concrete type will receive the extra methods impl Point let p = Point { x: 2.5, y: 3.5 }; let dist = p.distance_from_origin(); println!("distance from origin: ", dist); 9 Nested Generics The methods of a generic type can itself be generic struct Point impl let p1 = Point { x: 5, y: 10.4 }; let p2 = Point { x: "Hello", y: 'c'}; let p3 = p1.mixup(p2); println!("p3.x = {}, p3.y = {}", p3.x, p3.y); // p3.x = 5, p3.y = c 10 Trait Bound Constrain type parameters to have certain behaviours fn largest binary operation `>` cannot be applied to type `T note: an implementation of `std::cmp::PartialOrd` might be missing for `T` 11 Trait Bound Constrain type parameters to have certain behaviours fn largest cannot move out of type `[T]`, a non-copy slice 12 Trait Bound Constrain type parameters to have certain behaviours fn largest 13 Traits Shared behaviours across types Similar to mixin, but cannot define member variables Can have default implementation Unlike interface in Java (also known as protocol) Can depend on other traits // pub means public – can be used by other modules pub trait Summary { fn summarize(&self) -> String; } 14 Printing use std::fmt; // Debug can be auto-generated #[derive(Debug)] struct MyType { x: u32, y: u32 } // but not Display impl fmt::Display for MyType { fn fmt(&self, f:&mut fmt::Formatter) -> fmt::Result { write!(f, "x={}, y={}", self.x, self.y) } } let t = MyType{ x:1, y:2}; println!("{}", t); // x=1, y=2 println!("{:?}", t); // MyType { x: 1, y: 2 } 15 Printing use std::fmt; // Debug can be auto-generated #[derive(Debug)] struct MyType { x: u32, y: u32 } // but not Display impl fmt::Display for MyType { Annotationfn fmt(&self, to use fdefault:&mut fmt::Formatter) -> fmt::Result { implementationwrite! of the(f, Debug"x={}, trait y={}", self.x, self.y) } } let t = MyType{ x:1, y:2}; println!("{}", t); // x=1, y=2 println!("{:?}", t); // MyType { x: 1, y: 2 } 16 Printing use std::fmt; // Debug can be auto-generated #[derive(Debug)] struct MyType { x: u32, y: u32 } // but not Display impl fmt::Display for MyType { fn fmt(&self, f:&mut fmt::Formatter) -> fmt::Result { write!(f, "x={}, y={}", self.x, self.y) } } Explicit implementation of the Display trait for MyType let t = MyType{ x:1, y:2}; println!("{}", t); // x=1, y=2 println!("{:?}", t); // MyType { x: 1, y: 2 } 17 Making Copies Copy trait Any move ownership now makes a bitwise copy instead Clone trait More explicit, requires calling clone() method #[derive(Debug,Clone,Copy)] struct Point { x: f32, y: f32, z: f32 } let p = Point{x: 1., y: 2., z: 3. }; let q = p.clone(); // Clone let r = p; // Copy 18 Traits You can add traits to existing types, even primitives use std::convert::TryInto; TryInto is a trait with trait Tetration { default implementation // Self is the type of self (e.g. i64) that raises an error if fn tetration(&self, n: i32) -> Self; narrowing conversion } causes an overflow impl Tetration for i64 { fn tetration(&self, n: i32) -> i64 { if n == 0 { 1 } else { self.pow(self.tetration(n-1).try_into().unwrap()) } } } let v = 3_i64.tetration(3); // 3_i64 is of type i64 println!("tet({}, {}) = {}", 3, 3, v); // tet(3, 3) = 762559748498719 ECE326 PROGRAMMING LANGUAGES Lecture 32 : Ownership and References Kuei (Jack) Sun ECE University of Toronto Fall 2019 RAII Resource Acquisition is Initialization Initialization of object only succeeds if it gets all its resources Other names Constructor Acquires, Destructor Releases Scope-based Resource Management int main() { vector Introduced in C++11 Overloads -> and * (dereference) operator Automatically deletes the heap object it contains #include Transfers ownership of contained object to another void foo(unique_ptr Rust’s unique_ptr Contains a heap allocated object Guarantees contained object exists Unlike unique_ptr, its content may be null Usages To implement recursive data structures (e.g. linked list) To avoid copying (e.g. the contained object implements Copy) To use dynamic dispatch (i.e. trait objects) 5 Recursive Structure enum Tree { Leaf, 2 Node(i64, Box Mutability can change upon ownership transfer let immutable_box = Box::new(5u32); println!("immutable_box contains {}", immutable_box); // *immutable_box = 4; <- cannot do this // *Move* the box, changing ownership (and mutability) let mut mutable_box = immutable_box; // cannot access immutable_box from this point forward println!("mutable_box contains {}", mutable_box); // Modify the contents of the box *mutable_box = 4; println!("mutable_box now contains {}", mutable_box); 7 Return A function can return an object and give ownership fn main() { let s1 = gives_ownership(); let s2 = String::from("hello"); // s2 is moved into takes_and_gives_back, which also // moves its return value into s3 let s3 = takes_and_gives_back(s2); } // Here, s3 goes out of scope and is dropped. s2 goes out of scope // but was moved, so nothing happens. s1 goes out of scope and is // dropped. fn gives_ownership() -> String { let some_string = String::from("hello"); some_string } fn takes_and_gives_back(a_string: String) -> String { a_string } 8 Borrowing Access to data without taking ownership Object is passed by reference Compiler guarantees reference will always be valid This comes with a few restrictions and caveats 1. Cannot move an object if others hold reference to it 2. Only one mutable borrow at a time 3. Cannot mix mutable and immutable borrows 4. Cannot modify mutable object with immutable borrow 9 Valid Reference Object cannot be moved if another holds reference // This function takes ownership of a box and destroys it fn eat_box_i32(boxed_i32: Box Object cannot be moved if another holds reference Solution: Ensure reference goes out of scope first fn eat_box_i32(boxed_i32: Box Object can be borrowed immutably many times But can only be mutably borrowed one at a time The previous mutable borrow must go out of scope first Object cannot be borrowed both mutably and immutably at the same time Cannot borrow immutable objects as mutable Can borrow mutable objects as immutable Cannot modify mutable objects while borrowed immutably 12 Mutability Object can be borrowed immutably many times let mut point = Point { x: 0, y: 0, z: 0 }; let borrowed_point = &point; let another_borrow = &point; // Can access via the references and the original owner println!("Point has coordinates: ({}, {}, {})", borrowed_point.x, another_borrow.y, point.z); // NO! cannot borrow mutably, currently borrowed as immutable let mutable_borrow = &mut point; // NO! cannot modify, currently borrowed as immutable point.x = 3; 13 Mutability Object can only be borrowed mutably one at a time let mutable_borrow = &mut point; // Change data via mutable reference mutable_borrow.x = 5; mutable_borrow.y = 2; // NO! Can't borrow `point` as immutable because it's // currently borrowed as mutable. let y = &point.y; // NO! `println!` takes an immutable reference. println!("Point Z coordinate is {}", point.z); // Ok! Mutable references can be passed in as immutable println!("Point has coordinates: ({}, {}, {})", mutable_borrow.x, mutable_borrow.y, mutable_borrow.z); 14 Freezing Cannot modify a mutable while borrowed immutably Solution: ensure immutably reference go out of scope first let mut _mutable_integer = 7i32; { // Borrow `_mutable_integer` let large_integer = & _mutable_integer; // Error! `_mutable_integer` is frozen in this scope _mutable_integer = 50; println!("Immutably borrowed {}", large_integer); } // `large_integer` goes out of scope // Ok! `_mutable_integer` is not frozen in this scope _mutable_integer = 3; 15 Slices A reference to parts of an object E.g. string slice references a substring E.g. array slice references a part of an array Created using range syntax let s = String::from("hello world"); let hello = &s[0..5]; let world = &s[6..11]; 16 Slices Syntax: Can drop leading zero or trailing length let s = String::from("hello"); let slice = &s[0..2]; let slice = &s[..2]; // same as above let len = s.len(); let slice = &s[3..len]; let slice = &s[3..]; // same as above let slice = &s[0..len]; let slice = &s[..]; // same as above 17 Example Returns first word of a string fn first_word(s: &String) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] } 18 Borrow Slices also borrow – must obey all borrowing rules fn main() { let mut s = String::from("hello world"); // borrowing as immutable here let word = first_word(&s); // error! borrowing as mutable here! s.clear(); } error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable 19 ECE326 PROGRAMMING LANGUAGES Lecture 33 : Lifetime Kuei (Jack) Sun ECE University of Toronto Fall 2019 Lifetime The scope for which a reference is valid Usually implicit and inferred fn main() { let i = 3; // Lifetime for `i` starts. ────────────────┐ { // │ let borrow1 = &i; // `borrow1` lifetime starts. ──┐│ println!("borrow1: {}", borrow1); // ││ } // `borrow1 ends. ──────────────────────────────────┘│ { // │ let borrow2 = &i; // `borrow2` lifetime starts. ──┐│ println!("borrow2: {}", borrow2); // ││ } // `borrow2` ends. ─────────────────────────────────┘│ // │ } // Lifetime ends. ─────────────────────────────────────┘ 2 Borrow Checker Verifies all borrows are definitely valid Lifetime annotation Single quote followed by a letter. E.g. ‘a { let r; // ------+-- 'a r has a lifetime of // | a, x has a lifetime { // | of b. Because b is let x = 5; // -+-- 'b | shorter than a, this r = &x; // | | borrow is rejected } // -+ | // | println!("r: {}", r); // | } // ------+ 3 Return by Reference Functions can take references and return references Example: longest Returns longer of the two string slices fn longest(x:&str, y:&str) -> &str { if x.len() > y.len() { x } else { y } } let string1 = String::from("abcd"); let string2 = "xyz"; let result = longest(string1.as_str(), string2); println!("The longest string is {}", result); // abcd 4 Lifetime What if input has different lifetime? Danger! Returned reference may become invalid However, lifetime information is lost after passing to function fn main() { let string1 = String::from("long string is long"); let result; { let string2 = String::from("xyz"); result = longest(string1.as_str(), string2.as_str()); } // invalid reference if string2 were returned println!("The longest string is {}", result); } 5 Annotation Functions with references require lifetime annotations Generic lifetime parameter Adds lifetime information to function signature fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { longest takes two parameters x with the same lifetime, a, and } returns a reference, also with else { the same lifetime a. y } } 6 Borrow Checker let string1 = String::from("long string is long"); let result; { let string2 = String::from("xyz"); result = longest(string1.as_str(), string2.as_str()); } println!("The longest string is {}", result); error[E0597]: `string2` does not live long enough | | result = longest(string1.as_str(), string2.as_str()); | ------borrow occurs here | } | ^ `string2` dropped here while still borrowed | println!("The longest string is {}", result); | } | - borrowed value needs to live until here 7 Lifetime Elision Automatically inferring lifetime of returned references Rule 1 If function takes one parameter by reference, it has the same lifetime as returned reference fn first_word(s:&str) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } Rule 1 applies. Do not need to } explicitly annotate lifetime. &s[..] } 8 Lifetime Elision Rule 2 A method’s returned reference has the same lifetime as self Compiler error if the method returns other parameters by reference struct ImportantExcerpt { part: String, } impl ImportantExcerpt { fn announce_and_return_part(&self, message:&str) -> &str { println!("Attention please: {}", message); self.part.as_str() } Rule 2 applies. Do not need to } explicitly annotate lifetime. 9 Structure References in structures must be annotated struct ImportantExcerpt<'a> { part: &'a str, The structure’s impl } blocks also needs to have lifetime annotation. impl<'a> ImportantExcerpt<'a> { fn level(&self) -> i32 { 3 } } fn main() { let novel = String::from("Hello World. A long time ago..."); let first = novel.split('.').next().expect(“Split failed"); let i = ImportantExcerpt { part: first }; } 10 Static Lifetime References lives for entire duration of program Static global variables E.g. all string literals If any variant has a reference, the entire enum Either<'a> { enum must have a generic Num(i32), lifetime parameter Ref(&'a i32), StaticRef(&'static str), } let x = 18; let reference = Either::Ref(&x); let number = Either::Num(15); let staticref = Either::StaticRef("bonjour"); 11 Lifetime Coercion Allows returning reference with a shorter lifetime // `<'a: 'b, 'b>` reads as lifetime `'a` is at least as long as `'b`. fn choose_first<'a: 'b, 'b>(first: &'a i32, _: &'b i32) -> &'b i32 { first } fn main() { let first = 2; // Longer lifetime { let second = 3; // Shorter lifetime println!("{} is the first", choose_first(&first, &second)); }; } 12 ECE326 PROGRAMMING LANGUAGES Lecture 34 : Concurrent Programming Kuei (Jack) Sun ECE University of Toronto Fall 2019 Concurrent Programming Multiple tasks execute simultaneously Thread Independent sequence of execution Has its own stack, but shares the heap with other threads Parallel computing C B Threads executing at same physical time instant A Only possible if each thread runs on its own processor Time, dual processor Concurrency C B Threads may interleave on the same processor A Time, uniprocessor 2 Purpose Speed up program Usually with more people, a job can get done faster Criteria for speed up Threads can work relatively independently Seldom need to wait for other threads E.g. to access shared data E.g. to wait for input produced by another thread Threads are often waiting for IO (e.g. read from disk, network) Only important if threads are sharing a processor While one thread waits, other threads can still do work on processor 3 Concurrent Programming Most programming languages provide library support Creating and managing threads done through function calls use std::thread; spawn can take a use std::time::Duration; closure as argument thread::spawn(|| { for i in 1..10 { println!("hi number {} from thread!", i); thread::sleep(Duration::from_millis(1)); } }); Go has language support for creating threads go f(x, y, z); // starts a new thread (aka goroutine) 4 Basics A program always starts with one thread: main main creates new threads, and those can create more Creator should wait for the threads it created to end fn main() { let handle = thread::spawn(|| { for i in 1..10 { println!("hi number {} from thread!", i); } }); for i in 1..5 { println!("hi number {} from main!", i); } handle.join().unwrap(); // wait for created thread to finish } 5 Ownership A thread can potentially live longer than its creator E.g. the creator chooses not to call join before exiting Problem arises if closure references outer variable Therefore, all outer variables must be “moved” into closure By default, closures fn main() { borrow outer variables let v = vec![1, 2, 3]; let handle = thread::spawn(move || { println!("Here's a vector: {:?}", v); }); Must specify move to // you may no longer use ‘v’ here. make closure move // main may exit before thread does! outer variables into it. } 6 Challenge Sharing data Ownership Threads need to jointly own an object Updates to same data can result in race condition Caused by problematic interleaving of threads Depending on timing of thread execution, which is difficult to control Race condition can lead to unexpected and often incorrect results Synchronization Threads may need to communicate with each others One thread may need to wait for another thread to advance 7 Reference Counting A commonly used technique to share an object Analogy First person to walk into living room turns on TV Subsequent people entering can sit down immediately Last person to leave will turn off the TV Reference Counting Creator of object sets reference count to 1 Others will increment count before use Everyone decrements count after use If count is 0, free the object 8 Smart Pointer A wrapper class over a pointer, and acts like a pointer C++ Example unique_ptr Automatically frees pointed-to object when it goes out of scope shared_ptr A reference counting smart pointer Allows multiple threads to share pointed-to object Last reference holder will delete the object May not be the original creator of the object 9 Rc Allows sharing data in single-threaded setting enum List { Cons(i32, Rc 10 Lost Update One potential problem caused by race condition Assume both threads are running on same processor Thread 1 Thread 2 get(x) thread switch 5 get(x) 5 thread switch x = x + 1 (x is now 6) x = x + 1 (x is still 6) Solution: atomic instructions 11 Arc Allows sharing data across different threads A in Arc stands for atomic Atomic instruction A single, uninterruptible instruction on processor Can complete without interference from other threads Generally not used because it takes longer than if split E.g. fetch-and-add function FetchAndAdd(address location, int inc) { int value := *location *location := value + inc return value } 12 Arc Arc Limitations Arc 14 Mutual Exclusion Ensures only one thread can access shared data at once 15 Mutex Provides mutual exclusion When mutex is locked, no other thread can use object Locking mutex creates a MutexGuard use std::sync::Mutex; fn main() { let m = Mutex::new(5); { // num is a MutexGuard around the data let mut num = m.lock().unwrap(); *num = 6; } // num goes out of scope and unlocks m println!("m = {:?}", m); } 16 Mutex Provides interior mutability The mutex is immutable, but the data it contains is mutable Caveat Mutex is not sharable (ownership rule) Must be combined with Arc Same purpose as mutex Optimized for read-mostly objects (i.e. seldom updated) Enables multiple readers, single writer use std::sync::RwLock; If you are not sure, let lock = RwLock::new(5); stick with Mutex Language support in Java for mutual exclusion class ThreadedSend extends Thread { private String msg; Sender sender; // shared among different threads ThreadedSend(String m, Sender obj){ msg = m; sender = obj; } public void run() { // Only one thread can send message at a time. synchronized(sender){ // synchronizing the send object sender.send(msg); } } } 19 synchronized Alternatively, can make an entire method critical region class Sender { // Same effect as previous slide, only one thread can send public synchronized void send(String msg){ System.out.println("Sending\t" + msg ); try { Thread.sleep(1000); } catch (Exception e){ System.out.println("Thread interrupted."); } System.out.println("\n" + msg + "Sent"); } } 20 Polling Also known as busy looping Continuously lock shared data to check on condition in a loop OK on multiple processors if wait time is short On uniprocessor, reduces performance of entire system Example: bounded buffer problem mutex l; char buf[n]; // circular buffer void send(char msg){ lock(l); /* buffer is full, keep checking if space becomes available */ while ((in–out+n)%n == n - 1){ unlock(l); lock(l); } buf[in] = msg; in = (in + 1)% n; unlock(l); } 21 Message Passing Threads communicate by sending message with data Allows threads to synchronize i.e. thread waits for condition to satisfy before continuing Thread sleeps while waiting for message Sleeping thread will not be scheduled to run by OS Another thread can wake it up by sending a message Once woken up, thread can check the message 22 channel Creates a sender and a receiver end, thread safe Must send/receive same data type use std::thread; use std::sync::mpsc; fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let val = String::from("hi"); tx.send(val).unwrap(); // val moves into send() }); let received = rx.recv().unwrap(); println!("Got: {}", received); } 23 mpsc::channel Multiple producer, single consumer iteration on rx finishes when channel is closed i.e. when all senders close their end Can clone tx to allow let (tx, rx) = mpsc::channel(); for multiple producers. rx cannot be cloned! for i in 1..10 { let tx = mpsc::Sender::clone(&tx); thread::spawn(move || { tx.send(String::from("hello")).unwrap(); }); } for received in rx { println!("Got: {}", received); } 24 Monitor Allows for both mutual exclusion and synchronization Allows for multiple producers and multiple consumers Mutual exclusion Provided by a mutex object Synchronization Provided by one or more condition variables Allows program to define arbitrary condition for synchronization i.e. logic for going to sleep, and waking up others 25 Condition Variable Allows thread to relinquish lock and go to sleep Automatically re-acquires lock prior to wake up use std::sync::{Arc, Mutex, Condvar}; let pair = Arc::new((Mutex::new(false), Condvar::new())); let pair2 = pair.clone(); thread::spawn(move|| { let (lock, cvar) = &*pair2; let mut started = lock.lock().unwrap(); *started = true; cvar.notify_one(); // notify that the value has changed. }); let (lock, cvar) = &*pair; let mut started = lock.lock().unwrap(); while !*started { // wait for started to become true started = cvar.wait(started).unwrap(); } 26 Condvar wait(&self, val: MutexGuard) Thread waits until condvar is notified, and re-acquire lock on val before waking up notify_one(&self) Wakes up exactly one thread waiting on the condvar Equivalent to signal() in other literature notify_all(&self) Wakes up all threads waiting on the condvar Equivalent to broadcast() in other literature 27 Deadlock Circular waiting Each thread is holding a resource that the other needs to be able to continue (e.g., two pieces of shared data) Rust cannot prevent deadlocks P1 R2 Possible solutions Lock ordering Always acquire a set of locks in same order R1 P2 Try lock If one of the locks already taken, release all locks you own and restart 28 Example Multiple producer, multiple consumer problem const MAXLEN: usize = 8; struct Bounded { buffer:[i32; MAXLEN], top: usize, bottom: usize,} impl Bounded { fn push(& mut self, val: i32){ self.buffer[self.top] = val; self.top = (self.top + 1)% MAXLEN; } fn pop(& mut self) -> i32 { let val = self.buffer[self.bottom]; self.bottom = (self.bottom + 1)% MAXLEN; val } fn is_empty(& self) -> bool { self.bottom == self.top } fn is_full(& self) -> bool { (self.bottom + 1)% MAXLEN == self.top } 29 } Example Create a monitor around the bounded buffer use std::sync::{Arc, Mutex, Condvar}; struct Monitor Producer threads const NPRODUCER: i32 = 3; const NPRODUCT: i32 = 10; for i in 1..=NPRODUCER { let monitor = monitor.clone(); Sleep here to mix up threads.push(thread::spawn(move || { thread execution order for j in 0..NPRODUCT { let val = i * 10 + j; thread::sleep(Duration::from_micros(1)); let Monitor {mutex, empty, full} = &*monitor; let mut circ = mutex.lock().unwrap(); while circ.is_full() { Wait for the bounded circ = full.wait(circ).unwrap(); buffer to have space. } circ.push(val); empty.notify_all(); Notify consumer } that data is available })); 31 } Example Consumer threads for i in 1..=NCONSUMER { Collect up to 15 pieces let monitor = monitor.clone(); of data and exit. threads.push(thread::spawn(move || { let mut v = vec![]; while v.len() < NCONSUMED { let Monitor {mutex, empty, full} = &*monitor; let mut circ = mutex.lock().unwrap(); while circ.is_empty() { circ = empty.wait(circ).unwrap(); } v.push(circ.pop()); full.notify_all(); } println!("thread {} consumed: {:?}", i, v); })); } 32 Example Wait for all threads to finish before main exits fn main() { let mut threads = vec![]; /* creating producer and consumer threads */ for child in threads { child.join().unwrap(); } } Output thread 1 consumed: [10, 20, 11, 12, 21, 34, 13, 22, 14, 36, 15, 38, 23, 39, 16] thread 2 consumed: [30, 31, 32, 33, 35, 37, 24, 17, 18, 25, 19, 26, 27, 28, 29] 33 ECE326 PROGRAMMING LANGUAGES Lecture 35 : Traits Kuei (Jack) Sun ECE University of Toronto Fall 2019 Trait A collection of methods for an unknown type Trait refers to the type that implements it as Self Type that implements a trait can use its methods Especially useful if the trait has default implementation Helps define shared behaviour abstractly Example pub trait Summary { fn summarize(&self) -> String; } 2 Example pub struct NewsArticle { pub headline: String, pub location: String, pub author: String, pub content: String, } impl Summary for NewsArticle { fn summarize(&self) -> String { format!("{}, by {}", self.headline, self.author) } } pub struct Tweet { pub username: String, pub content: String, pub reply: bool, pub retweet: bool, } impl Summary for Tweet { fn summarize(&self) -> String { format!("{}: {}", self.username, self.content) } } 3 Example pub struct Tweet { pub username: String, pub content: String, pub reply: bool, pub retweet: bool, } impl Summary for Tweet { fn summarize(&self) -> String { format!("{}: {}", self.username, self.content) } } let tweet = Tweet { username: String::from("horse_ebooks"), content: String::from("of course, as you probably already \ know, people"), reply: false, retweet: false, }; println!("1 new tweet: {}", tweet.summarize()); 4 Trait Object Rust’s equivalent of abstract base class Allows for runtime polymorphism nUse dy keyword to use objects as trait objects Must be placed inside a Box A trait that takes type parameter Works the same as other generics Can have trait bounds trait Out impl Out Calls different trait method depending on the type of the variable the method’s return value is assigned to Type inference does not work in this case This means the trait C++ does not support this In impl In // calls ByteArray::In Allows specifying trait bounds more expressively impl // Expressing bounds with a `where` clause impl MyTrait for YourType where A: TraitB + TraitC, D: TraitE {} Can specify bounds that contains the type parameter trait PrintInOption { “Option Defines generic types as internal types And not as parameters Before: trait Contains { // Explicitly requires `A` and `B`. fn contains(&self, _: &A, _: &B) -> bool; } After trait Contains { type A; type B; // Updated syntax to refer to these new types generically. fn contains(&self, &Self::A, &Self::B) -> bool; 9 } Associated Type Using a trait with associated types impl Contains for Container { /* named tuple */ type A = i32; struct Container(i32, i32); type B = i32; // `&Self::A` and `&Self::B` are also valid here. fn contains(&self, number_1: &i32, number_2: &i32) -> bool { (&self.0 == number_1) && (&self.1 == number_2) } fn first(&self) -> i32 { self.0 } fn last(&self) -> i32 { self.1 } } fn difference There’s a trait for every operator that can be overloaded use std::ops; struct Foo; // Unit-like struct: struct Bar; // There's only one value struct FooBar; // This implements Foo + Bar = FooBar impl ops::Add fn add(self, _rhs: Bar) -> FooBar { FooBar } } 11 ECE326 PROGRAMMING LANGUAGES Lecture 36 : Lambdas, Generators and Coroutines Kuei (Jack) Sun ECE University of Toronto Fall 2019 Closure Function that can capture enclosing environment Capture The use of outer variables in a closure Heavy use of type inference to make code succinct Rust let closure_annotated = |i: i32| -> i32 { i + 1 }; let closure_inferred = |i | i + 1 ; No parameter C++11 closures can omit auto func = [] { cout << "Hello world"; }; parantheses () auto printi = [](int val){ cout << val; }; 2 Capture In C++, captures can be specified in detail [] Capture nothing [&] Capture any referenced variable by reference [=] Capture any referenced variable by copy [=, &foo] Capture any ref’ed variable by copy, but capture foo by reference [bar] Capture bar by making a copy. don't copy anything else [this] Capture the this pointer of the enclosing class Closures are implemented as functors Recall that functor is a class that overloads the call operator All captures are stored as member variables 3 Anonymous Function In Rust and C++, closures are also anonymous Name of the closure is unspecified In Python, anonymous functions are called lambdas Closure can be named or nameless in Python >> func = lambda x: x + 1 >> func(2) 3 >> full_name = lambda first, last: "%s %s"%(first, last) >> full_name("Hello", "World") 'Hello World' >> list(map(lambda x: x*x, range(1, 5))) [1, 4, 9, 16] 4 Anonymous Function Other names Lambda expression Function literals Main uses As closure Pass to higher order functions Allows function code to be physically closer to usage def square(x): return x*x map(lambda x: x*x, range(1, 5)) … many lines later … map(square, range(1, 5)) 5 Iterator Revisited To support iteration, implement __iter__ and __next__ class CountdownIterator(object): def __init__(self, count): self.count = count for n in countdown(5): def __next__(self): print(n, end= " ") self.count <= 0: if 5 4 3 2 1 raise StopIteration r = self.count self.count -= 1 return r Returns an iterator object! class countdown(object): def __init__(self,start): self.start = start def __iter__(self): return CountdownIterator(self.start) 6 Generator A function that can pause and resume where it left off Requires its own stack and context to preserve state Similar to a thread Context The data that must be saved to resume a thread after a switch Most important of which are processor registers Generator produces a sequence of results Instead of a single value, without creating temporary lists! Similar to an iterator, but much easier to write 7 Generator Uses yield instead of return to return value Has similar interface to an iterator Not callable, but next() will resume the function Raises StopIteration when the generator finishes def generator(n): >> next(g) yield n+1 4 yield n-1 >> next(g) yield n 2 >> next(g) >> g = generator(3) 3 >> print(type(g)) >> next(g) # exception occurs here Calling a generator creates a generator object The function starts running on the first next() invocation A Generator object cannot be reused must create new one def countdown(n): print("About to launch") while n > 0: Output: yield n n -= 1 About to consume g About to launch g = countdown(5) 5 4 3 2 1 print("About to consume g") for x in g: print(x, end=" ") 9 Yield From Delegates iteration to another iterator Allow you to chain multiple iterators together def countdown(n): def up_and_down(n): while n > 0: yield from countup(n) yield n yield from countdown(n) n -= 1 >> for x in up_and_down(3): def countup(stop): .. print(x) n = 1 1 while n < stop: 2 yield n 3 n += 1 2 1 10 Generator Expression Similar to list expression, but creates generator not list ( P(x) for x in iterable if Q(x) ) Reduces memory footprint if you don’t need the list Python has been moving towards using lazy iterators E.g. range() used to create a list instead of a range object >> a = [1, 2, 3, 4] >> b = (2*x for x in a) >> b Apache web server log file Process the log file to find total bytes send to clients Log file can be huge (in gigabytes) Interpreter may run out of memory with list comprehension Ends with bytes sent or “-” in case of error 81.107.39.38 - ... "GET /ece326.html HTTP/1.1" 200 2359 Use generator here 66.249.72.134 - ... "GET /index.html HTTP/1.1" 200 4447 instead of list! 81.107.39.38 - ... "GET /DoesNotExist/ HTTP/1.1" 404 - with open("access-log") as wwwlog: bytecolumn = (line.rsplit(maxsplit=1)[1] for line in wwwlog) bytes_sent = (int(x) for x in bytecolumn if x != '-') print("Total", sum(bytes_sent)) 12 Pipelining Pulling input through set of data processing elements with open("access-log") as wwwlog: bytecolumn = (line.rsplit(maxsplit=1)[1] for line in wwwlog) bytes_sent = (int(x) for x in bytecolumn if x != '-') print("Total", sum(bytes_sent)) Uses familiar higher order functions No need to create temporary list, enables high performance “access-log” wwwlog bytecolumn bytes_sent sum total creates iterator from file object map filter fold 13 Functional Programming Writing declarative expressions No statements, everything is an expression Prefers immutability Enables pipelining of data from one function to another Uses higher order functions instead of control flow Verdict: Good for processing large amount of data Poor for simulating real life interactions FP requires no side effect, but real life objects are frequently stateful 14 Similar Constructs Mutual recursion Two functions calling each others def bar(n): def foo(n): return print(foo(n-1)) return bar(n-1) if n > 0 else 0 return n Each function always starts from beginning Shares a stack Cooperative thread Thread voluntarily yields, triggers scheduler immediately E.g. by calling thread_yield(next_id) Cannot pass data between threads through interface 15 Coroutines Cooperative threads with two-way communication Generators are only one-way In Python, coroutines are implemented as generators The only difference is that it receives input via send() def recv_count(): r = recv_count() try: # required to “prime” the function while True: r.send(None) n = yield for i in range(5, 0, -1): print(n, end=“ ”) r.send(i) # triggers upon close() # closes the generator object except GeneratorExit: r.close() print("Kaboom!") 5 4 3 2 1 Kaboom! 16 Priming Coroutine Use a decorator def coroutine(func): def start(*args,**kwargs): cr = func(*args,**kwargs) cr.send(None) return cr return start # using decorator the manual way @coroutine def recv_count(): … # don't have to prime it anymore! r = recv_count() for i in range(5, 0,-1): r.send(i) r.close() # still have to close it manually 17 Pipelining Coroutines are frequently used only for sending Generators actively pulls data from its iterable input generator generator generator output next() next() next() Coroutines passively waits for data to be pushed input coroutine coroutine coroutine output send() send() send() 18 Coroutines When next() is called, function stops at yield If send() is called, its return value is the next yield If send() not called before next(), yield returns None def counter(maximum): it = counter(10) i = 0 print(next(it), next(it)) while i < maximum: print(it.send(8)) val = (yield i) print(next(it)) # If value provided, print(next(it)) # change counter if val is not None: 0 1 i = val 8 else: 9 i += 1 StopIteration 19 ECE326 PROGRAMMING LANGUAGES Lecture 37 : Final Review Kuei (Jack) Sun ECE University of Toronto Fall 2019 Final Exam Monday, December 16th 9:30am - 12:00pm BA1180/BA1190 Cheat sheet is allowed Double-sided Letter-sized paper No restriction 9 Questions With sub-questions 2 Breakdown True or False Multiple Choices Short Answers With written responses Will cover entire course content 50% pre-midterm, 50% post-midterm 3 Breakdown Multiple Inheritance Structure layout Template Metaprogramming Variadic template Reflective Programming Descriptor Metaclass Decorator 4 Breakdown Rust Programming Ownership and Lifetime Traits Concurrent Programming Iterators Rust Iterator Adapters Generators Coroutines 5 Hints and Advice Emphasis on assignment 3 and 4 Strong understanding will be very helpful Understand the conceptual topics Type systems Language features Learn how to apply them in different settings E.g. Is it useful to have generators for Rust? E.g. How can we add generators to Rust? 6 ECE326 PROGRAMMING LANGUAGES Lecture 35 : Traits and Iterators Kuei (Jack) Sun ECE University of Toronto Fall 2019 Course Evaluation Available on Quercus Please complete to help improve this course Also to help me with my teaching abilities In-class time to do the evaluation Let me know Your participation is greatly appreciated! 2 Assignment 4 EasyDB database You should already know what it can do Implementation Database contains tables Table contains rows Row contains id, version, and values Think about which data structure you will use Vector? Hashmap? 3 Table Permanent for duration of server Cannot add or remove table after initialization Should keep track of its format from schema::Table Use it to validate values sent from EasyDB commands Think about modularity of your code! Insert and update should use the same function to validate values Hint: Most of the EasyDB commands starts with a table_id From 1 to N where N is the number of tables in schema 4 Row Can be created or destroyed Must keep track which row ids are in use Space-time tradeoff Improves performance (in speed) comes with increased storage (space) usage Cascade drop Scan through an entire table (slow, uses less space) Keep metadata on the external rows referencing this row How will you do this given Rust’s ownership rules? 5 Parallelism To pass parallel test, you only need to correctly add a mutex to the entire database object When one thread is using the database, all other threads must wait due to mutual exclusion This is pretty bad for a commercial database Speedup Test Requires one mutex per table You will run into deadlocks if not careful Most common deadlock Trying to lock the same mutex twice in the same thread 6 Traits and Iterators In Rust 7 Trait A collection of methods for an unknown type Trait refers to the type that implements it as Self Type that implements a trait can use its methods Especially useful if the trait has default implementation Helps define shared behaviour abstractly Example pub trait Summary { fn summarize(&self) -> String; } 8 Example pub struct NewsArticle { pub headline: String, pub location: String, pub author: String, pub content: String, } impl Summary for NewsArticle { fn summarize(&self) -> String { format!("{}, by {}", self.headline, self.author) } } pub struct Tweet { pub username: String, pub content: String, pub reply: bool, pub retweet: bool, } impl Summary for Tweet { fn summarize(&self) -> String { format!("{}: {}", self.username, self.content) } } 9 Example pub struct Tweet { pub username: String, pub content: String, pub reply: bool, pub retweet: bool, } impl Summary for Tweet { fn summarize(&self) -> String { format!("{}: {}", self.username, self.content) } } let tweet = Tweet { username: String::from("horse_ebooks"), content: String::from("of course, as you probably already \ know, people"), reply: false, retweet: false, }; println!("1 new tweet: {}", tweet.summarize()); 10 Trait Object Rust’s equivalent of abstract base class Allows for runtime polymorphism nUse dy keyword to use objects as trait objects Must be placed inside a Box A trait that takes type parameter Works the same as other generics Can have trait bounds trait Out impl Out Calls different trait method depending on the type of the variable the method’s return value is assigned to Type inference does not work in this case This means the trait C++ does not support this In impl In // calls ByteArray::In Allows specifying trait bounds more expressively impl // Expressing bounds with a `where` clause impl MyTrait for YourType where A: TraitB + TraitC, D: TraitE {} Can specify bounds that contains the type parameter trait PrintInOption { “Option Defines generic types as internal types And not as parameters Explicitly requires A and B as type Before: parameters for trait Contains { generic structures fn contains(&self, _: &A, _: &B) -> bool; and functions } fn difference(container: &C) -> i32 where C: Contains { container.last() - container.first() } After trait Contains { type A; type B; fn contains(&self, &Self::A, &Self::B) -> bool; } 15 Associated Type Using a trait with associated types impl Contains for Container { /* named tuple */ type A = i32; struct Container(i32, i32); type B = i32; // `&Self::A` and `&Self::B` are also valid here. fn contains(&self, number_1: &i32, number_2: &i32) -> bool { (&self.0 == number_1) && (&self.1 == number_2) } fn first(&self) -> i32 { self.0 } fn last(&self) -> i32 { self.1 } } fn difference There’s a trait for every operator that can be overloaded use std::ops; struct Foo; // Unit-like struct: struct Bar; // There's only one value struct FooBar; // This implements Foo + Bar = FooBar impl ops::Add fn add(self, _rhs: Bar) -> FooBar { FooBar } } 17 Drop trait Same as a destructor in C++ Use if your structure does something special upon drop Unlikely unless it’s a low level construct drop() function Deletes object immediately struct Droppable { name: &'static str,} impl Drop for Droppable { fn drop(&mut self) { println!("> Dropping {}", self.name); } } fn main() { let a = Droppable { name: "a" }; drop(a); // `a` deleted here } // instead of here 18 Iterator An object which performs the act of iterating An agent which operates on an iterable Iterable An object that can be iterated (e.g. container such as list) Stream Sequence of data made available over time Can have potentially infinite data Example Network connection, Rust range: (x..y), Python range(x, y) 19 Iterator Two requirements 1. A way to retrieve the next element next() function will 2. A way to signal end of iteration retrieve the next element from Python iterator iterator, or raise StopIteration iter() built-in function numbers = [2, 3, 5, 7] it = iter(numbers) Output: while True: 2 try: 3 print(next(it)) 5 except StopIteration: 7 print("End of List") break End of List 20 Iterator trait Rust iterator implements Iterator trait use std::ops::Add; struct Fibonacci Containers are iterables, not iterators But for loop requires an iterator into_iter() function turns containers into iterators for loop is just an syntactic sugar for x in v { /* body */ let v = vec![2, 3, 5, 7]; } let mut iter = v.iter(); loop { match iter.next() { Some(x) => { /* body of for loop */ }, None => break, } } 22 Iterator Adapters Enjoyed by functional programmers Operates on iterators Example: Sieve of Eratosthenes let starter: Vec // each integer in starter was mapped into a vector of i32 let composites = starter.iter().map(|&x| -> Vec [ [4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48], [6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48], [10, 15, 20, 25, 30, 35, 40, 45], [14, 21, 28, 35, 42, 49] ] 24 Iterator Adapters map(closure): transforms each element Can even return a different type filter(closure): keeps element if closure returns true collect(): collects elements in iterator into container With ambiguous integers, must specify type flatten(): turns nested vectors into a flattened vector take(n): only iterate up to n times skip(n): skip the first n iterations 25 fold Known as reduce() in Python fold(accumulator, closure) Accumulator: an aggregate value of a collection E.g. sum, max, min, average, etc. The argument is the initial value of the accumulator Closure takes two argument acc : the accumulator x: each element of the iterator let a = [1, 2, 3]; let sum = a.iter().fold(0, |acc, &x| acc + x); // sum = 6 26 Higher-order Function A function that either/or both: Takes one or more functions as arguments Returns a function as its result Normal functions are called “first-order” functions = vec![]; let mut it = tokens.iter(); loop { let table = match parse_table(&mut it, &tables)? { Some(table) => table, None => break, }; tables.push(table); } if tables.len() == 0 { Err("schema file is empty") } else { Ok(tables) } } 20 ECE326 PROGRAMMING LANGUAGES
), Nil,} use crate::List::{Cons, Nil}; use std::rc::Rc; fn main() { let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); let b = Cons(3, Rc::clone(&a)); let c = Cons(4, Rc::clone(&a)); Each time Rc::clone is } called, reference count of a increases by 1.