Programming Languages (3) --- Object-Oriented Programming Basics¶
Enter your name and student ID.
- Name:
- Student ID:
1. Choose your language¶
Choose a language you want to work on for today from the following.
- Go --- designed as a "better C"
- Julia --- popular for scientific computing
- OCaml --- a practical functional language
- Rust --- a system programming language with memory safety without garbage collection
Declare your choice in the following cell (leave the one you choose). In this notebook, I will work on
BEGIN SOLUTION END SOLUTION
2. Roadmap¶
- Below, you are going to learn the basics of object-oriented programming, a cornerstone of all modern programming languages
- define two data types representing rectangles (
rect
) and ellipses (ellipse
) - define functions or methods that compute their areas
- have an array or a similar container that mixes both rectangles and ellipses
- some languages require you to define another type (something like
shape
orShape
encompassing both rectangles and ellipses)
- Note: follow case conventions/requirements about type names of your language; Go, Julia and Rust conventionally capitalize them and OCaml requires them to be in lowercase
Problem 1 : Read documents¶
- Before you start, spend some time to go through relevant sections of your language manual
Go | Data |
Methods | |
Interfaces and other types | |
Julia | Composite Types |
Methods | |
OCaml | Objects in OCaml |
Data types | |
Rust | Using Structs to Structure Related Data |
Method Syntax | |
Traits: Defining Shared Behavior |
3. Class definition¶
- A class is a way to define a composite type along with some functions associated with it (methods)
- Different languages call the mechanism differently; it is called
struct
in C++, Go, Julia, and Rust, orclass
in C++, Java, Python and OCaml - No matter how it's called in your language, it generally defines what kind of fields or functions (typically called methods) data of that class have
Problem 2 : Define data structure representing a rectangle and an ellipse¶
- Define two new data types
rect
(orRect
) andellipse
(orEllipse
) representing a rectangle and an ellipse, respectively - For simplicity, you may assume sides of rectangles and axes of ellipses are parallel to x- or y-axis
- We will later define a type that encompasses both of them (which might be named
shape
orShape
) if necessary and other shapes such as lines, polygons, etc. - Relevant constructs and related sections in the documentation
Go | struct | Data |
Julia | struct | Composite Types |
OCaml | class | Objects in OCaml |
Rust | struct | Using Structs to Structure Related Data |
Note: In OCaml, you could use
type
construct to define just a single typeshape
with two constructors (perhapsRect
andEllipse
). Similarly, you can useenum
with multiple constructors in Rust.In this exercise, however, use
class
/object
of OCaml orstruct
of Rust to define each of rectangle and ellipse as a separate typeNote: when using
class
/object
of OCaml, you need to define its methods at the same time. You can do it here or the problem below
解答セル/Answer Cell
BEGIN SOLUTION
END SOLUTION
Go
type rect struct {
x, y, width, height int
}
type ellipse struct {
x, y, rx, ry int
}
Julia
- Note : type of fields (
:: Int
) are optional for Julia
struct Rect
x :: Int
y :: Int
width :: Int
height :: Int
end
struct Ellipse
x :: Int
y :: Int
rx :: Int
ry :: Int
end
OCaml
- In OCaml, you define both types and methods in a single construct, class
- We will do both in the next problem
Rust
struct Rect {
x : i32,
y : i32,
width : i32,
height : i32
}
struct Ellipse {
cx : i32,
cy : i32,
rx : i32,
ry : i32
}
4. Methods¶
- Methods are similar to functions, except that a method of the same name can be defined (i.e., implemented differently) for different classes
- When you call a method, which of the different implementations of the same name gets called is determined by the type(s) of its argument(s) (dynamic dispatch)
Go | func | Methods |
Julia | function | Methods |
OCaml | method | Objects in OCaml |
Rust | impl | Method Syntax |
Problem 3 : Define a method that computes the area of rect/ellipse¶
- Define
area
method forrect
andellipse
that computes its area
解答セル/Answer Cell
BEGIN SOLUTION
END SOLUTION
Go
import "math"
func (r rect) area() float64 {
return float64(r.width * r.height)
}
func (e ellipse) area() float64 {
return float64(e.rx * e.ry) * math.Pi
}
Julia
function area(r :: Rect)
r.width * r.height
end
function area(e :: Ellipse)
e.rx * e.ry * pi
end
OCaml
- Note :
area
method can reference variables given to the constructor (width, height, rx
, andry
), which makes OCaml implementation particularly concise - You don't have to write a separate data structure definition or a boilerplate code copying variables given to the constructor to the data structure
class rect x y width height = object
method area = float(width * height)
end
;;
let pi = (atan2 1.0 1.0) *. 4.0
;;
class ellipse x y rx ry = object
method area =
float(rx * ry) *. pi
end
;;
Rust
impl Rect {
fn area(&self) -> f64 {
(self.width * self.height) as f64
}
}
impl Ellipse {
fn area(&self) -> f64 {
std::f64::consts::PI * ((self.rx * self.ry) as f64)
}
}
5. Subtypes and similar concepts (interface, trait, etc.)¶
- Now that we have defined two different methods with the same name
area
, but it wouldn't be that useful, unless you can have a variable that holds values of both types at different points of an execution - For example, we like to have an array (or any container data) that has both rectangles and ellipses and iterate over them, assigning a variable elements of different types over time, e.g., like this in Python
for s in shapes:
s.area()
- An issue is, what should be the type of the variable
s
orshapes
above? How to declare their types? - Intuitively, we need a type that encompasses both rectangles and ellipses (and perhaps other shapes defined in future), perhaps called
shape
- Approaches differ between languages
- some languages (e.g., Java, C/C++) let you define
shape
explicitly and extend it to define rectangles and ellipses - Go and Rust introduce interface or trait, that look like types but do not allow creation of actual data that belong to them (some would call them abstract types); they basically specify what kind of methods must be implemented for any data to claim a particular interface or trait
- some languages (e.g., Java, C/C++) let you define
- Superficially, it does not appear to be an issue in languages that do not require any type declarations (e.g., Python, Julia, or OCaml)
- It is true to some extent, but a more profound issue is whether the language can guarantee, prior to execution, that type errors do not happen during runtime
Go | interface | Interfaces and other types |
Julia | ||
OCaml | ||
Rust | trait | Traits: Defining Shared Behavior |
Problem 4 : Create an array/a list/a vector/a slice ¶
Create an array-like data structure that has both rectangle(s) and ellipse(s)
For simplicity, create a two-element array whose
- first element : a rectangle whose lower left corner is (0,0) and upper right corner is (100,100)
- second element : an ellipse whose center is (0,0) and the radius along x-axis 100 and the radius along y-axis is 50
Depending on the language you chose, you may have to define a type encompassing rect and ellipse and perhaps have to redefine rect and ellipse too
Some languages have idiosyncrasies for arrays and similar containers, which you might find confusing
Some pointers about ways to create arrays or similar containers in each language
Go | array | new | Arrays |
Go | slice | make | Slices |
Julia | array | [a,b,..] | Array literals |
OCaml | list | [a;b;..] | Data types |
OCaml | array | [|a;b;..|] | Imperative features |
Rust | array | [a,b,..] | The Array Type |
Rust | slice | &array | The Slice Type |
Rust | Vec | Vec::new | Storing Lists of Values with Vectors |
解答セル/Answer Cell
BEGIN SOLUTION
END SOLUTION
Go
- Note : you define an interface,
shape
, which says all classes (struct
in Go) havingarea()
method returningfloat64
implement this interface - You don't have to say
rect
orellipse
implementsshape
and can just put them in an array of shapes
type shape interface {
area() float64
}
var shapes []shape = []shape{
rect{0, 0, 100, 100},
ellipse{0, 0, 100, 50},
}
Julia
- Note : like OCaml, you don't have to do anything special to put
rect
andellipse
in the same array - But unlike OCaml, this is because Julia gives up static type checking
shapes = [Rect(0, 0, 100, 100), Ellipse(0, 0, 100, 50)]
OCaml
- Note : you don't have to do anything special to put
rect
andellipse
in the same array - This is because OCaml realizes that both
rect
andellipse
have exactly the same signature ($\approx$ method names and their types)
let shapes = [new rect 0 0 100 100; new ellipse 0 0 100 50]
;;
Rust
- Note : you define an interface,
shape
, which says classes (struct
in Rust) implementing this interface must havearea()
method returningf64
- Each time you define a struct, like
rect
orellipse
, whose instances you want to assign to a variable ofshape
type, you have to explicitly say this struct implementsshape
(impl Shape for Rect / Ellipse
does that)
trait Shape {
fn area(&self) -> f64;
}
impl Shape for Rect {
fn area(&self) -> f64 {
(self.width * self.height) as f64
}
}
impl Shape for Ellipse {
fn area(&self) -> f64 {
std::f64::consts::PI * ((self.rx * self.ry) as f64)
}
}
#[allow(unused_variables)]
fn mk_shapes() {
let r = Rect{x: 0, y:0, width: 100, height: 100};
let e = Ellipse{cx: 0, cy: 0, rx: 100, ry: 50};
let shapes : Vec<&dyn Shape> = vec![&r, &e];
return;
}
6. Working on mixed collections¶
Problem 5 : Scan an array of shapes¶
- Write a function, named
sum_area
, which scans an array of shapes and returns the sum of each shape's area - Apply
sum_area
to the list created above
Go | for | For |
Julia | for | Repeated Evaluation: Loops |
OCaml | List | Module List |
OCaml | for | Loops |
Rust | for | Looping Through a Collection with for |
解答セル/Answer Cell
BEGIN SOLUTION
END SOLUTION
Go
func sum_area(shapes []shape) float64 {
sa := 0.0
for _, s := range shapes {
sa += s.area()
}
return sa
}
sum_area(shapes)
Julia
function sum_area(shapes)
sa = 0.0
for (i, s) in enumerate(shapes)
sa += area(s)
end
sa
end
sum_area(shapes)
OCaml
let rec sum_area shapes =
match shapes with
[] -> 0.0
| h::r -> h#area +. (sum_area r)
;;
sum_area shapes
;;
Rust
fn sum_area(shapes : Vec<&dyn Shape>) -> f64 {
let mut sa = 0.0;
for (_, s) in shapes.iter().enumerate() {
sa += s.area()
}
sa
}
fn main() {
let r = Rect{x: 0, y:0, width: 100, height: 100};
let e = Ellipse{cx: 0, cy: 0, rx: 100, ry: 50};
let shapes : Vec<&dyn Shape> = vec![&r, &e];
println!("{}", sum_area(shapes));
}
7. Static vs. dynamic type checking¶
- What if you pass an array some of whose element do not have
area
method? - Some languages simply allow such a program to start execution and raise an error (type error) at runtime (dynamic type checking), while others do not allow such programs to compile, by detecting such errors prior to execution (static type checking)
- A profound language design issue is how to statically type-check, while allowing maximum flexibility