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
  1. define two data types representing rectangles (rect) and ellipses (ellipse)
  2. define functions or methods that compute their areas
  3. have an array or a similar container that mixes both rectangles and ellipses
  4. some languages require you to define another type (something like shape or Shape 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, or class 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 (or Rect) and ellipse (or Ellipse) 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 or Shape) 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 type shape with two constructors (perhaps Rect and Ellipse). Similarly, you can use enum with multiple constructors in Rust.

  • In this exercise, however, use class/object of OCaml or struct of Rust to define each of rectangle and ellipse as a separate type

  • Note: 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

In [ ]:
BEGIN SOLUTION
END SOLUTION

Go

In [ ]:
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
In [ ]:
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
In [ ]:

Rust

In [ ]:
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 for rect and ellipse that computes its area

解答セル/Answer Cell

In [ ]:
BEGIN SOLUTION
END SOLUTION

Go

In [ ]:
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

In [ ]:
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, and ry), 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
In [ ]:
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

In [ ]:
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 or shapes 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
  • 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

In [ ]:
BEGIN SOLUTION
END SOLUTION

Go

  • Note : you define an interface, shape, which says all classes (struct in Go) having area() method returning float64 implement this interface
  • You don't have to say rect or ellipse implements shape and can just put them in an array of shapes
In [ ]:
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 and ellipse in the same array
  • But unlike OCaml, this is because Julia gives up static type checking
In [ ]:
shapes = [Rect(0, 0, 100, 100), Ellipse(0, 0, 100, 50)]

OCaml

  • Note : you don't have to do anything special to put rect and ellipse in the same array
  • This is because OCaml realizes that both rect and ellipse have exactly the same signature ($\approx$ method names and their types)
In [ ]:
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 have area() method returning f64
  • Each time you define a struct, like rect or ellipse, whose instances you want to assign to a variable of shape type, you have to explicitly say this struct implements shape (impl Shape for Rect / Ellipse does that)
In [ ]:
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

In [ ]:
BEGIN SOLUTION
END SOLUTION

Go

In [ ]:
func sum_area(shapes []shape) float64 {
        sa := 0.0
        for _, s := range shapes {
                sa += s.area()
        }
        return sa
}
sum_area(shapes)

Julia

In [ ]:
function sum_area(shapes)
    sa = 0.0
    for (i, s) in enumerate(shapes)
        sa += area(s)
    end
    sa
end

sum_area(shapes)

OCaml

In [ ]:
let rec sum_area shapes =
  match shapes with
    [] -> 0.0
  | h::r -> h#area +. (sum_area r)
;;
sum_area shapes
;;

Rust

In [ ]:
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