목차
Learning Objectives
- Explain what an object is
- Explain how objects are typed in Scala
- Explain what the "self" is in object-oriented programming
- Explain what delegation and late binding is in object-oriented programming
- Desugar a language with simple objects into functions and mutation
- Explain what the Expression Problem is, and how objected-oriented programming and functional programming solve different aspects of it
Functional vs. Object-Oriented Programming
Functional programming abstracts away irrelevant details of e.g. memory management
This lets us program at a high level of abstraction and enables equational reasoning about programs
But it also entails a performance overhead in many cases
Object-oriented programming lets us define stateful abstractions that encapsulate state
Object-oriented programming typically provides programmers with more control over run-time memory of programs than functional programming
Example Object: Counter
Definition of Object
An object is meant to encapsulate in a coherent whole a piece of state together with some behavior that relies on that state.
The state is usually called fields, and the behavior is provided as a set of methods.
Calling a method is often considered as message passing: we send a message to an object, and if it understands it, it executes the associated method.
Objects, Messaging, and Encapsulation
Object Typing and Sub-Typing
Object Types in Scala
Object types in Scala uniquely determine what fields and methods the object contains
For example, the fields and methods accessible on instances of the following class is statically enforced by Scala's type system
(new A).x
→ 1 returned
(new A).y
→ not a Runtime error, but error from type checking
→ This says variable y exists but you cannot access it
(new A).z
→ Not Found Error
Subtyping in Java: inheritance (e.g. class C extends D)
Structural Subtyping in Scala 3
Method f takes input parameter called obj which has a structural type. Anything
f(new A)
→ returns val res1: Int = 1
→ As long as this scope contains method called x that returns Int, it works (as long as this has something named x that returns Int)
만약에 x를 y로 바꾸면 No Such Method Error
만약에 z면 Type Mismatch Error
r.f()
→ 1 (r implements a structural type that has a field f. Anonymous class that has a single method that returns 1.
s.g()
→ We cast to a supertype, the only thing that type checks it whether to check the object has a method f().
→ Not Found Error: value g is not a member of Object( f() : Int}
Self
Example that Returns Self
Objects in Scala can reference themselves via the keyword this
this: reference to the object we are calling the method on
Whenever we are calling the method, we are implicitly initializing this keyword
val res = Point(0, 0)
val p2 = Point(1, 2)
p2.compare(res) → p2 returned (더 큰 Point return)
Delegation and Late Binding of Self
Delegation
Delegation: way of supporting code reuse in OOP languages
In order to support reuse, objects can forward messages to delegatees
We can think of inheritance as a kind of delegation
Prototype delegation is also possible in e.g. JavaScript
Inheritance in Scala
Calling a method defined in a super-class is well-defined
Example
(new B).f
→ 42
Late Binding in Scala
Late binding of self provides better support for reuse
Because I have defined the getPrice method such that it calls the overhead method of this, I can define subclasses/subobjects that override the overhead method but still allow me to call getPrice() to multiply the overhead of the subclass to the price of the superclass
Late Binding of Self: I pass the subobject the method was originally called on.
(new ProdPro).getPrice()
→ 3 (this.overhead() is from the ProdPro so this is ProdPro).
getPrice()에 this 키워드 없어도 똑같이 작동 (implicitly has this keyword)
def 가 아니라 val 로 해도 똑같은 값 반환.
Prototype Delegation in JavaScript
The JavaScript language runtime allows the "delegate" of objects - i.e. the object to forward messages to - to be dynamically assigned during runtime
Prototype based delegation: The objects are dynamically typed (no typechecker that checks the type of objects)
obj.foo() → 1
두번째에서는 obj.foo() → 2
Late Binding and Prototype Inheritance in JavaScript
Late binding of self provides better support for reuse.
pp.getPrice() → 3
pp.__proto__ = { getPrice: () => 123 }
pp.getPrice() → 123
What are Objects?
The Essence of Objects
Objects are structured collections of memory and functions that live on the heap
Objects: co-data and co-recursive functions, with state
→ we can define objects by desugaring them into functions
Javascript: count.js
class Counter {
#count = 0 // private fields are prefixed with #
foo = 123
inc() { // method
this.#count = this.#count + 1
return this.#count
}
dec() { // method
this.#count = this.#count - 1
return this.#count
}
}
(new Counter()).#count
// Uncaught Syntax Error: Private field '#count' must be declared
(new Counter()).foo;
// 123
(new Counter()).inc();
// 1
(new Counter()).inc();
// 1 - Creating a new object
// Objects as closures
// -------------------
// OOPLAI:
//
// “An object is meant to encapsulate in a coherent whole a
// piece of state (possibly, but not necessarily, mutable)
// together with some behavior that relies on that state.”
function CounterClo() {
var v = 1
return [ () => { v = v + 1; return v }
, () => { v = v - 1; return v } ]
}
// try:
// CounterClo()[0]() --> 2
// var p = clo()
// p[0]() --> 2
// p[0]() --> 3
// p[1]() --> 2
// p.v --> 2
// Methods as messages
// -------------------
// Methods are invoked by sending messages to the closure
function CounterObj() {
var v = 1
return (msg, args) => {
if (msg == "inc") {
v = v + 1
return v
} else if (msg == "dec") {
v = v - 1
return v
} else if (msg == "incBy") {
v = v + args[0]
return v
} else {
throw new Error("The object only understands the following messages: inc/0, dec/0, incBy/1")
}
}
}
// q = CounterObj()
// q("inc", []) --> 2
// q("inc", []) --> 3
// q("dec", []) --> 2
// q("incBy", []) --> NaN
// q("incBy", [20]) --> 22
// Methods as messages, with closures
// ----------------------------------
// Methods are returned when sending a message to the closure. What you need to do in the lab
function CounterObjClo() {
var v = 1
return (msg) => {
if (msg == "inc") { return () => {
v = v + 1
return v
} } else if (msg == "dec") { return () => {
v = v - 1
return v
} } else if (msg == "incBy") { return (n) => {
v = v + n
return v
} } else {
throw new Error("The object only understands the following messages: inc/0, dec/0, incBy/1")
}
}
}
// if msg == "inc", it returns a closure that does not take any input.
// r = CounterObjClo()
// r("inc")() --> 2
// foo = r("inc")
// foo() --> 3
To deal with delegation, forward messages to delegatees:
// Methods as messages, with closures
// ----------------------------------
// Objects the message "foo"
function FooObj() {
return (msg) => {
if (msg == "foo") { return () => return "foo" }
else {
throw new Error("Dont know")
}
}
}
function CounterObjCloSup(superobj) {
var v = 1
return (msg) => {
if (msg == "inc") { return () => {
v = v + 1
return v
} } else if (msg == "dec") { return () => {
v = v - 1
return v
} } else if (msg == "incBy") { return (n) => {
v = v + n
return v
} } else {
return superobj(msg)
}
}
}
s = CounterObjCloSup(FooObj())
s("inc")() --> 2
s("dec")() --> 1
s("foo")() --> "foo"
Javascript: proto.js
Function: lambda, closure: run-time representation of a lambda, method:
// Late binding of `this` affords reuse of functionality
// with self-awareness
// -----------------------------------------------------
class Prod {
#price = 2
overhead() { return 1.0 }
getPrice() { return this.overhead()*this.#price } // sends a message to "self"
}
class ProdPro extends Prod {
overhead() { return 1.5 }
}
// try:
// var p = new Prod
// p.getPrice() --> 2
// var q = new ProdPro
// q.getPrice() --> 3
Even though I'm calling getPrice() on the superclass, the notion of self which is "this" is the instance in the subclass.
// Objects as closures with late binding of self
// ---------------------------------------------
// First take: try using same encoding as previously.
function ProdObjClo0() {
var price = 2
var self
self = (msg) => {
if (msg == "overhead") { return () => 1.0 }
else if (msg == "getPrice") {
return () => self("overhead")()*price }
else {
throw new Error("The object only understands the following messages: overhead/0, getPrice/0")
}
}
return self
}
function ProdProObjClo0() {
var delegate = ProdObjClo0()
var self
self = (msg) => {
if (msg == "overhead") { return () => 1.5 }
else { return delegate(msg) }
}
return self
}
// try:
// var p = ProdObjClo0()
// p("getPrice")() --> 2
// var q = ProdProObjClo0()
// q("overhead")() --> 1.5
// q("getPrice")() --> 2
// Second take: pass self as explicit parameter when
// invoking methods
// -------------------------------------------------
function ProdObjClo() {
var price = 2
return (self) => (msg) => {
if (msg == "overhead") { return () => 1.0 }
else if (msg == "getPrice") { return () => self(self)("overhead")()*price }
else {
throw new Error("The object only understands the following messages: overhead/0, getPrice/0")
}
}
}
function ProdProObjClo() {
var delegate = ProdObjClo()
return (self) => (msg) => {
if (msg == "overhead") { return () => 1.5 }
else { return delegate(self)(msg) }
}
}
// try:
// var p = ProdObjClo()
// p(p)("getPrice")() --> 2
// var q = ProdProObjClo()
// q(q)("getPrice")() --> 3
// sugar for invoking a message, passing self:
function invoke(receiver, msg) {
return receiver(receiver)(msg)
}
The Expression Problem
The goal is to define a datatype by cases, where one can add new cases to the datatype and new functions over the datatype, without recompiling existing code, and while retaining static type safety (e.g. no casts)
The Expression Problem - Functions + Algebraic Data Types
ADTs
Extending with a New Function (typeOf)
Extending with a New Case
The Expression Problem - Object Oriented Programming
Extending with a New Case (If)
Extending with a New Function (typeOf)
Summary
'학교 > CPL' 카테고리의 다른 글
CPL 5: Parallel and Concurrent (0) | 2024.04.09 |
---|---|
Lecture 11: Type Checking (1) | 2024.04.06 |
Lecture 9-10: Parallel and Concurrent (0) | 2024.04.06 |
CPL 4: Mutation and State (0) | 2024.04.05 |
Lecture 7-8: Mutation and State (0) | 2024.04.01 |
댓글