8. Subprogram
Subprogram
Abstraction in programming languages
- Data abstraction (i.e, Record type)
- Process abstraction
In form of subprogram
- Reuse โ savings, primarily memory space and coding time
- Abstraction: details are replaced by subprogram calling
- โ hiding the low-level details
- โ increases the readability
Closely related to method of OOP
Difference: way they are called and associations with class
There are various forms of the subprogram in programming languages
//C
int add(int a, int b) {
return a + b;
}
//Java
public class Main {
public static int add(int a, int b) {
return a + b;
}
}
#Python
def add(a, b):
return a + b
//Javascript
function add(a, b) {
return a + b;
}
//Swift
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}
//Rust
fn add(a: i32, b: i32) -> i32 {
a + b
}
//Ruby
def add(a, b)
a + b
end
//Haskell
add :: Int -> Int -> Int
add a b = a + b
Today's Contents
- Fundamentals of subprograms
- Design issues
- Local referencing environment
- Parameter-passing methods
- Parameter that are subprograms
- Indirectly called subprograms
- Overloaded subprograms
- Generic subprograms
- Design issues for functions
- User-defined overloaded operators
- Closures
- Coroutines
Fundamentals of subprograms
- General characteristics
- Single entry point
- ์๋ธํ๋ก๊ทธ๋จ์ ํญ์ ํ๋์ ์ง์ ์ ์์ ์คํ์ ์์ํจ
- One subprogram in execution
- ํธ์ถ ์ ํ๋์ ์ธ์คํด์ค๋ง ํ์ฑ ์ํ
- Control return to the caller after termination
- Most subprograms have names (ํจ์ ์ด๋ฆ์ ํตํด ํธ์ถ)
- Except: anonymous subprograms in C# and Python
//C#
Func<int, int, int> add = (a, b) => a + b;
#Python
add = lambda a, b: a + b
Alternatives 1(coroutines): ํ๋ ๋ฃจํด, ์ฆ ์์ ๊ฐ ์ ํ์ด ๋ช ์์ , ํ ๋ฃจํด์ด ๋ค๋ฅธ ๋ฃจํด์๊ฒ ์ ์ด๋ฅผ ๋๊ฒจ์ฃผ๋ฉฐ ๋์๊ฐ๋ฉฐ ์คํ
Alternatives 2(concurrent): ์ฌ๋ฌ ์์ ์ด ๋์์ ์คํ๋จ (๋ ผ๋ฆฌ์ ์ผ๋ก ๋๋ ์ค์ ๋ก), ๋์ ์คํ์ ์งํฅ
Basic definitions
- Subprogram definition describe Interface and actions
- Subprogram is active after subprogram call
- Two kinds of subprograms: procedure and function
- Subprogram header
- Specifies that the following syntactic unit is a subprogram definition
- Provides a name
- Specify a list of parameters (optional)
- Body contains actions
[Python]
def adder (parameters):
bodyโฆ
[C]
void adder (parameters){
bodyโฆ.
}
| ํญ๋ชฉ | ์ค๋ช |
|---|---|
| Subprogram | ํ๋์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์ฝ๋ ๋ธ๋ก์ผ๋ก, ํธ์ถ๋๋ฉด ํน์ ๋์์ ์ํ |
| Subprogram Definition | ์ธํฐํ์ด์ค(์ด๋ฆ, ๋งค๊ฐ๋ณ์)์ ๋์(๋ด์ฉ)์ ๊ธฐ์ |
| ํ์ฑ ์์ | ํธ์ถ(Call)์ด ๋ฐ์ํ ํ์ ํ์ฑ(active) ์ํ๊ฐ ๋จ |
| ์ข ๋ฅ | ๋ ๊ฐ์ง ์ฃผ์ ์ ํ: Procedure์ Function |
| Header (ํค๋) | ์ด ์ฝ๋ ๋ธ๋ก์ด subprogram์์ ๋ช ์ํ๋ฉฐ, ์ด๋ฆ๊ณผ (์ต์ ) ๋งค๊ฐ๋ณ์๋ฅผ ์ ์ธ |
| Body (๋ณธ๋ฌธ) | ์ค์ ์ํํ ๋์์ ์ ์ |
- Basic definitions
- [Python]
defstatement can be executed - Assign name to function body (์ด๋ฆ์ ํจ์ ๋ณธ์ฒด์ ํ ๋นํ๋ ๊ฒ๊ณผ ๊ฐ์)
- No need to declare in advance, declare and use when needed
- ํจ์๋ ์ผ๊ธ ๊ฐ์ฒด(first-class citizen): ํจ์๊ฐ ๋ค๋ฅธ ๊ฐ์ฒด๋ค๊ณผ ๋์ผํ ๋ฐฉ์์ผ๋ก ๋ค๋ค์ง ์ ์๋ค๋ ๊ฒ์ ์๋ฏธ
- [Python]
if condition:
def add(a, b):
return a + b
operation = add # ๋ณ์์ ์ ์ฅ
print(operation(2, 3)) # ์ธ์๋ก ์ ๋ฌ, ๋ฐํ๊ฐ์ผ๋ก ์ฌ์ฉ, ๊ฒฐ๊ณผ: 5
def fun(x):
return x + 1
else:
def fun(x):
return x - 1
print(fun(3))
# ์คํ ์ค ์ด๋ค fun์ด ์ ์๋๋์ง๊ฐ ๊ฒฐ์ ๋จ
- Basic definitions
- [Ruby] Ruby์์๋ ๋ชจ๋ ๊ฒ์ด ๊ฐ์ฒด์ด๊ณ , ๋ฉ์๋๋ ์์ธ๊ฐ ์๋
- Can also be defined outside class definitions (ํด๋์ค ์ธ๋ถ์์๋ ๋ฉ์๋ ์ ์ ๊ฐ๋ฅ)
- Considered as method of root object (Object) (์ด ๊ฒฝ์ฐ Object ํด๋์ค์ ์ธ์คํด์ค ๋ฉ์๋๊ฐ ๋จ)
- Called as if function in C-based (C ์ธ์ด ์คํ์ผ์ฒ๋ผ ํจ์ ํธ์ถํ๋ฏ ์ฌ์ฉํ ์ ์์)
def greet(name)
puts "Hello, #{name}"
end
greet("Ruby") # Object์ ๋ฉ์๋๋ก ํธ์ถ๋จ
ํจ์๋ผ๋ ๊ฐ๋ ์ด ์์
์ ๋ถ ๊ฐ์ฒด์ ๋ฉ์๋
Basic definitions
- [Lua] Lua๋ ํจ์๋ฅผ ๊ฐ(value)์ผ๋ก ์ทจ๊ธ
- Nameless function
- ํจ์๋ ์ด๋ฆ ์๋ ๋ฆฌํฐ๋ด(function literal)
function(x) return x * x * x end
- Function can be assigned to variable
- ํจ์๋ ๋ณ์์ ํ ๋น๋๊ณ , ์ธ์๋ก ์ ๋ฌ๋๋ฉฐ, ๊ฒฐ๊ณผ๋ก ๋ฐํ๋ ์ ์์
- ํจ์ ์ ์์ ํ ๋น์ ๋์ผํ ์๋ฏธ (์ ์ ์์ฒด๊ฐ expression)
function cube(x) return x * x * x end
cube = function (x) return x * x * x end
- Python: ํจ์๋ ๊ฐ์ฒด์ง๋ง
def๋ statement - Ruby: ํจ์ ์์ โ ์ ๋ถ "๋ฉ์๋ (๊ฐ์ฒด ์์)"
- Lua: ํจ์๋ ์์ ํ ๊ฐ (expression ๊ธฐ๋ฐ)
| ํญ๋ชฉ | Python | Lua | Ruby |
|---|---|---|---|
| ํจ์ ์กด์ฌ | ์์ | ์์ | ์์ (๋ฉ์๋๋ง) |
| ํจ์ = ๊ฐ | O | โ (๋ ๊ฐํจ) | โณ (Method object๋ก๋ ๊ฐ๋ฅ) |
| ์ ์ ๋ฐฉ์ | statement | expression | - |
| ์์ | ๋ ๋ฆฝ | ๋ ๋ฆฝ | ํญ์ ๊ฐ์ฒด์ ์ํจ |
| ํธ์ถ ๋ฐฉ์ | f() | f() | ๊ฐ์ฒด.method |
Basic definitions
- Protocol of a subprogram: parameter profile + return type
- Parameter profile: number, order, and types
- ์ด ์ ๋ณด๋ฅผ ํตํด ์ปดํ์ผ๋ฌ๋ ํธ์ถ์๊ฐ subprogram์ ์ด๋ป๊ฒ ์ฌ์ฉํ ์ ์๋์ง ํ๋จ
- Declarations provide the subprogram's protocol but do not include their bodies
- Declarations are needed for static type checking
- Definition includes actual implementations
Two ways to access to values:
- Direct access to nonlocal variables
- Parameter passing (more flexible)
Two ways to access to values:
- Direct access to nonlocal variables
- Extensive access to nonlocals can reduce reliability
- ์ธ๋ถ ์ํ์ ์์กดํ๊ฒ ๋์ด ์ฌ์ฌ์ฉ์ฑ, ์์ธก ๊ฐ๋ฅ์ฑ, ๋๋ฒ๊น ๋์ด๋ ์ฆ๊ฐ
- ๋ณ๊ฒฝ ๊ฐ๋ฅ์ฑ์ด ํฌ๋ฉด side effect ๋ฐ์ ์ํ ์์
- Changing nonlocals and class variables
- Can make side effect (should be avoided)
- Reliable problem
- Functional language does not have mutable data
- Unable to change memory
- Direct access to nonlocal variables
Two ways to access to values:
- Parameter passing (more flexible)
- ๋ ์ ์ฐํ๊ณ ์์ ํ ์ ๊ทผ ๋ฐฉ์
- ๋ฐ์ดํฐ๋ฅผ ํจ์๋ก ๋ช ์์ ์ผ๋ก ์ ๋ฌํ์ฌ, ์ธ๋ถ ์ํ์์ ์์กด์ ์ค์
- ํ๋ผ๋ฏธํฐ๋ฅผ ํตํด ํจ์์ ๋์์ ์ ๋ ฅ์๋ง ์์กดํ๊ฒ ๋ง๋ค ์ ์์
- Parameter passing (more flexible)
Parameters
- Transmit computations
- Name of subprogram is used as parameter
- ๋ค๋ฅธ ์๋ธํ๋ก๊ทธ๋จ(ํจ์)์ ์ด๋ฆ์ ์ธ์๋ก ์ ๋ฌํด์, ๋ด๋ถ์์ ํธ์ถ๋๋๋ก ํจ
# Python
def square(x):
return x * x
def compute_and_print(func, value):
print(func(value))
compute_and_print(square, 5) # โ 25
# Ruby
def square(x)
x * x
end
def compute(func, value)
func.call(value)
end
compute(method(:square), 5) # โ 25, square ๋ฉ์๋๋ฅผ Method ๊ฐ์ฒด๋ก ๋ณํ
-- Lua
function square(x)
return x * x
end
function compute(func, value)
print(func(value))
end
compute(square, 5) -- โ 25
- Parameters (Python)
- Formal parameter
- Parameters in header
- Thought of as dummy (ํธ์ถ ์ ์ ๋ฌ๋ ๊ฐ์ "์๋ฆฌ๋ง ์ฐจ์งํ๋ ๋ณ์")
- Formal parameter
def greet(name): # โ 'name'์ formal parameter
print("Hello,", name)
- Actual parameter
- List of parameters in subprogram call statements
- ํจ์ ํธ์ถ ์ ์ค์ ๋ก ์ ๋ฌ๋๋ ๊ฐ
- ํจ์๊ฐ ํธ์ถ๋ ๋, ์ด ๊ฐ๋ค์ด formal parameter์ ๋ฐ์ธ๋ฉ๋จ
greet("Alice") # โ "Alice"๊ฐ actual parameter
- Parameters (Python)
- Positional parameter
- Binding of actual parameters to formal parametersโis done by position
- Positional parameter
def subtract(a, b):
return a - b
print(subtract(10, 3)) # a=10, b=3 โ ๊ฒฐ๊ณผ: 7
- Keyword parameter
- Binding of actual parameters to formal parametersโis done by name
print(subtract(b=3, a=10)) # ๊ฒฐ๊ณผ: 7
- [Mix of position and keyword]
sumer(my_length, sum = my_sum, list = my_array)
- Parameters (Python)
- Default value
- Used if no actual parameter is passed to the formal parameter
- Default value
def compute_pay(income, exemptions = 1, tax_rate)
- Absent actual parameter
- Skip (exemptions)
pay = compute_pay(20000.0, tax_rate = 0.15)
- Parameters (C++)
- No keyword parameter
- Default parameter must appear last
float compute_pay(float income, float tax_rate, int exemptions = 1)
pay = compute_pay(20000.0, 0.15);
If there is no default
- the number of formal and actual parameters should be the same
Parameters (C#)
- Variable number of parameters (same type)
params(์ฌ๋ฌ ๊ฐ ์ธ์๋ฅผ ๋ฐ์์ ๋ฐฐ์ด๋ก ๋ง๋ค์ด์ฃผ๋ ๊ธฐ๋ฅ)- ๋ฐ๋์ ๋ง์ง๋ง ํ๋ผ๋ฏธํฐ์ฌ์ผ ํจ
- ํ๋๋ง ์ฌ์ฉ ๊ฐ๋ฅ
- ๊ฐ์ ํ์
๋ง ๊ฐ๋ฅ (
int[]๊ฐ์)
public void DisplayList(params int[] list) {
foreach (int next in list) {
Console.WriteLine("Next value {0}", next);
}
}
Myclass myObject = new Myclass;
int[] myList = new int[6] {2, 4, 6, 8, 10, 12};
myObject.DisplayList(myList);
myObject.DisplayList(2, 4, 1, 17);
- Parameters (Ruby)
- Use array formal parameter
*(only one in the parameter) - ์ค๊ฐ์๋ ์ฌ ์ ์์ (C#์ ๋ถ๊ฐ๋ฅ)
- Use array formal parameter
list = [2, 4, 6, 8]
def tester(p1, p2, p3, *p4)
...
end
tester('first', mon => 72, tue => 68, wed => 59, *list)
# p1 is 'first'
# p2 is {mon => 72, tue => 68, wed => 59}
# p3 is 2
# p4 is [4, 6, 8]
- Parameters (Lua)
- Use ellipsis (
...): treated as an array or as a list of values ipairsis an iterator for arrays{...}is an array of the actual parameter values- ์ธ์ ๊ฐ์ ์ ํ ์์
- Use ellipsis (
function multiply (...)
local product = 1
for i, next in ipairs{...} do
product = product * next
end
return sum
end
print(multiply(2, 3, 4))
function doIt (...)
local a, b, c = ...
...
end
doIt(4, 7, 3)
| ์ธ์ด | ๋ฌธ๋ฒ | ๋ณธ์ง |
|---|---|---|
| C# | params int[] | ๋ฐฐ์ด ์์ฑ (์ ์ ) |
| Ruby | *args | ๋ฐฐ์ด๋ก ์์ง (pack) (๋์ ) |
| Lua | ... | ๊ฐ ๋ฆฌ์คํธ (not array) |
- Subprograms are collection of statements
- Procedures: does not return
- Can change variable (side effect ๋ฐ์ ๊ฐ๋ฅ)
- Visible in procedure and calling program (ํธ์ถํ ์ชฝ ํ๋ก๊ทธ๋จ์์ ๊ณต์ ๋๋ ๋ณ์์ ์ ๊ทผ ๊ฐ๋ฅ)
- Formal parameters that allow the transfer of data to the caller
- Formal parameter๋ ํจ์์ ํธ์ถ์ ์ฌ์ด์์ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ๋ ์ธํฐํ์ด์ค ์ญํ
- ์ํ๋ฅผ ๋ฐ๊พธ๋ ์ฉ๋ ๊ฒฐ๊ณผ๋ "๊ฐ"์ด ์๋๋ผ ๋ณ์ ๋ณํ
- Functions: return value
- Modeled on mathematics
- No side effect ideally
- Can be defined as operator
- Usually, function can be used as procedures by not defining return
- Procedures: does not return
Design issues for subprograms
- Choice of one or more parameter-passing methods
- Pass-by-value
- Pass-by-reference
- โฆ
- Type checking
- Static type checking
- Dynamic type checking
- Static or dynamic local variables in subprograms
- Can be nested
def outer():
def inner():
print("nested")
inner()
- Can be passed as parameters
- ์๋ธํ๋ก๊ทธ๋จ์ ๋ค๋ฅธ ํจ์์ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌ ๊ฐ๋ฅ (first-class function ์ง์ ์ธ์ด)
- When subprograms are nested and passed, what is the correct referencing environment
- ํจ์ ์์์ ์ ์๋ ํจ์๋ฅผ ๋ค๋ฅธ ๊ณณ์ ๋๊ธธ ๋, ๊ทธ ์ธ๋ถ ํ๊ฒฝ ์ ๋ณด๋ ํจ๊ป ์ ๋ฌ
- Can be overloaded or generic
- Overloading: ๊ฐ์ ์ด๋ฆ ๋ค๋ฅธ ์๊ทธ๋์ฒ โ ์ ์ ๋คํ์ฑ
- Generic: ํ์ ํ๋ผ๋ฏธํฐํ
- Closure supported?
- ํจ์์ ๊ทธ ํจ์๊ฐ ์ ์ธ๋ ํ๊ฒฝ์ ์กฐํฉ
Local referencing environments
Local variable
- Inside subprogram (์๋ธํ๋ก๊ทธ๋จ ์์์ ์ ์ธ๋ ๋ณ์)
- Static
- ์ ์ธ ์ ๋ฉ๋ชจ๋ฆฌ์ ๊ณ ์ ๋ ์์น์ ํ ๋น, ํ๋ก๊ทธ๋จ ์ข ๋ฃ๊น์ง ์ ์ง๋จ
- [C] keyword
staticis used for static local variable - ํจ์๊ฐ ์ฌ๋ฌ ๋ฒ ํธ์ถ๋๋๋ผ๋ ์ด์ ๊ฐ ์ ์ง
- Stack dynamic (default setting for most languages)
- ํจ์ ํธ์ถ ์ ์คํ ํ๋ ์์ ์์ฑ๋๊ณ , ๋ฆฌํด๋๋ฉด ์๋ฉธ
- [Pro] Flexible, each recursive calls have their own local variable
- [Pro] If it is in active subprograms, it can be shared
- [Con] Cost of time for allocation, initialization, deallocation
- [Con] Must be indirectly accessed (determined only during execution)
- [Con] Cannot be history sensitive
Global variable
- Defined outside subprogram
- Can be referenced in the method without declaring
- If the name of a global variable is assigned in a method
- Implicitly declared to be a local
- Does not disturb the global (์๋์ shadowing ํ์)
Nested subprograms (ํ ํจ์ ์์ ๋ ๋ค๋ฅธ ํจ์๋ฅผ ์ ์ํ๋ ๊ตฌ์กฐ)
- Create hierarchy of logic and scopes
- Can be used when it is only needed in another subprogram
- Want to hide it from others โ ์บก์ํ
- Allowed in Algol, Pascal, Ada, JavaScript, Python, Ruby and Lua + most Functional languages
- Not allowed in many descendants of C
def outer():
def inner():
print("I'm nested!")
inner()
Parameter passing methods
Semantics models
Implementation models
Implementing parameter passing methods
Parameter passing methods in common languages
Type checking parameters
Multidimensional arrays as parameters
Design considerations
Examples
Semantics models (formal parameter)
- in mode: receive data
- out mode: transmit data
- inout mode: can do both
- [EX] list1: in mode, list2: inout mode, list3: out mode
def function(list1, list2):
list2 = list1 + list2
list3 = list1 + list2
return list2, list3
Implementation models
- Pass-by-value
- Pass-by-result
- Pass-by-value-result
- Pass-by-reference
- Pass-by-name
Implementation models
- Pass-by-value
#include <stdio.h>
void add(int in){
in = in + 10;
}
void main(){
int a = 20;
add(a);
printf("a: %d", a); // 20
}
Actual parameter is used to initialize formal parameter
Can implement in-mode (caller โ callee)
Implemented by copy (usually)
Because the copied value is passed into the function without affecting the original value, it can be used in cases where the original value should not be changed
Pro: fast in both linkage cost and access time for scalars
Cons
- Additional storage is required for copy
- Copy operations and storage can be costly when parameter is large
Implementation models
- Pass-by-result
// Assume pass-by-result
#include <stdio.h>
void add(int in){
in = 10;
}
void main(){
int a = 20;
add(a);
printf("a: %d", a); // 10
}
Implementation for out-mode parameters (callee โ caller)
No value is transmitted to the subprogram
์ค์ ๋งค๊ฐ๋ณ์์ ์ด๊ธฐ๊ฐ์ ์๋ธํ๋ก๊ทธ๋จ ๋ด์์ ์ฌ์ฉ๋์ง ์๊ณ , ์ค์ง ์๋ธํ๋ก๊ทธ๋จ ์ข ๋ฃ ํ ๊ฒฐ๊ณผ๊ฐ์ด ๋ณต์ฌ๋ผ ๋์๊ฐ๋ ๊ตฌ์กฐ
Its value is transmitted back to the caller's actual parameter
Implemented by copy (usually)
ํธ์ถ ์ : ์๋ฌด ๊ฐ๋ ๋ณต์ฌ๋์ง ์์
ํธ์ถ ํ: ์๋ธํ๋ก๊ทธ๋จ ์ข ๋ฃ ์, ๋ด๋ถ ๋ณ์์ ๊ฐ์ ์ค์ ๋งค๊ฐ๋ณ์ ์์น๋ก ๋ณต์ฌ
Ensuring that the initial value of the actual parameter is not used in the called subprogram
(+) ๋ด๋ถ ๋ก์ปฌ ๋ณ์๋ก ์ฒ๋ฆฌ๋๊ธฐ ๋๋ฌธ์ ์ด๊ธฐ๊ฐ ์ค๋ฅ ๊ฐ๋ฅ์ฑ์ด ๋ฎ์
(-) ์ด๊ธฐ๊ฐ์ ์ฐธ์กฐํด์ผ ํ๋ ๊ฒฝ์ฐ์๋ ๋ถ์ ํฉ
(-) ์ฌ๋ฌ ๊ฐ์ ์ด๋ฆ(alias)์ด ๊ฐ์ ์ค์ ๋งค๊ฐ๋ณ์๋ฅผ ๊ฐ๋ฆฌํค๋ ๊ฒฝ์ฐ, ์์ธก ๋ถ๊ฐ๋ฅํ ๊ฒฐ๊ณผ๊ฐ ๋ฐ์ํ ์ ์์ (ํนํ pass-by-reference์ ๋น๊ตํ์ ๋)
Implementation models
- Pass-by-result
- Cons
- Actual parameter collision
- 17 or 35 can be assigned to a
- So, order dependent
- Cons
- Pass-by-result
[C# ์ฝ๋]
void Fixer(out int x, out int y) {
x = 17;
y = 35;
}
...
f.Fixer(out a, out a);
- Implementation models
- Pass-by-result
- Cons
- Can choose between two different times to evaluate (time of call or return)
- ํ๊ฐ ์์ (call vs return)์ ๋ฐ๋ผ ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง โ ์ ๋ขฐ์ฑ์ด ๋จ์ด์ง
- In function DoIt,
list[sub]=list[3]orlist[5]? - Unportable between an implementations: evaluation at beginning and evaluation at the end
- Cons
- Pass-by-result
public static void DoIt(out int x, out int y)
{
y = 5;
x = 17;
}
public static void Main(string[] args)
{
int sub = 3;
int[] list = new int[6]{4, 9, 1, 0, 21, 12};
DoIt(out list[sub], out sub);
System.Console.WriteLine("{0} {1} {2}", list[3], list[sub], sub);
}
- Call-time evaluation (ํธ์ถ ์์ ์ ์ฃผ์ ํ๊ฐ): `17 12 5`
- `// sub[3]` changed to 17, thus the value of x(17) return to `sub[3]`, not `sub[5]`
- `//โ` return address is decided during time of call, not return
- Return-time evaluation (๋ณต๊ท ์์ ์ ์ฃผ์ ํ๊ฐ):
- `list[sub]` = `list[3]` โ sub ๋ ์ด๋ 3
- sub = 5 โ DoIt() ์์ ๋ณ๊ฒฝ๋จ
- โ x = 17 โ `list[3]` = 17
- sub = 5 ๊ฐ ๋จผ์ ์ ์ฉ๋ ๋ค `list[sub]` = `list[5]`
- โ x = 17 โ `list[5]` = 17
- โ ์ฆ, `list[sub]`๋ `list[3]`์ผ๋ก ํ๊ฐ๋์๋ค๋ ๊ฒ.
- โ ์ฃผ์๊ฐ ํธ์ถ ์์ (call time)์ ํ๊ฐ๋์์์ ์๋ฏธํจ.
Implementation models
- Pass-by-value-result
- Implementation for inout-mode parameters
- Pass-by-value + pass-by-result
- Called by pass-by-copy
- Pass-by-value-result
Implementation models
- Pass-by-reference
#include <stdio.h>
void add(int &in){
in = in + 10;
}
void main(){
int a = 20;
add(a);
printf("a: %d", a); // 30
}
- Implementation for inout-mode parameters
- Transmits an access path (address)
- Value of the original variable can be changed
- Efficient (time and space), no copy
- Cons
- Slow to access to formal parameter due to indirect addressing
- Inadvertent and erroneous changes may be made
- Aliases can be created โ providing access to nonlocal variables โ reliability problem
void fun(int &first, int &second)
fun(total, total)
// first and second can be aliases
Implementation models
- Pass-by-name
- Implementation for inout-mode parameters
- ์ฝ๋ ์กฐ๊ฐ(์ด๋ฆ)์ ํต์งธ๋ก ์ ๋ฌํด์, ๊ทธ๋๊ทธ๋ ์คํํด๋ณด๋ ๋ฐฉ์
- ์ค์ ๋งค๊ฐ๋ณ์์ ์ด๋ฆ ์์ฒด๋ฅผ ์๋ธํ๋ก๊ทธ๋จ์ ์ ๋ฌํ๋ ๊ฒ์ฒ๋ผ ๋์ํจ
- ๋งค๊ฐ๋ณ์ ํํ์์ด ๋งค ํธ์ถ๋ง๋ค ์ฌํ๊ฐ๋จ (lazy substitution)
- ์ผ์ข ์ ํ ์คํธ ์นํ(textual substitution) ๋๋ ๋งคํฌ๋ก์ฒ๋ผ ๋์
- ๋์ ๋ฐฉ์
- Works like pass-by-reference when variable is passed
- Works like pass-by-value when constant value is passed
- Complex to implement and inefficient
- Pass-by-name
Implementation models
- Pass-by-name
DoIt(x, y)
x := x + 1
y := y + x
DoIt(a[i], i)
์ฌ๊ธฐ์ a[i]๋ i๋ ๊ทธ๋๋ก ์ ๋ฌ๋จ (๊ฐ์ด ์๋๋ผ ์ด๋ฆ์ฒ๋ผ)
์คํ ํ๋ฆ (pass-by-name):
x := a[i] + 1 โ a[i]๊ฐ ๋ณ๊ฒฝ๋จ
y := i + a[i] โ i ๋ณ๊ฒฝ๋จ
๋ค์ a[i]๋ฅผ ๋ณด๋ฉด ์ด๋ฏธ ๊ฐ์ด ๋ฐ๋์์ ์ ์์
โ ํ ์ค์ด ๋๋๋ฉด ๋ณ์ ๊ฐ์ด ๋ฌ๋ผ์ ธ์ ๋ค์ ์ค ํด์์ด ๋ฐ๋
๋๋ฌด ๋ณต์กํ๊ณ , ์ค์ํ๊ธฐ ์ฝ๊ณ , ์๋๋ ๋๋ ค์ โ ์์ฆ์ ๊ฑฐ์ ์ ์. Algol 60 ์์ ์ฌ์ฉ
Implementing parameter passing methods
- Run-time stack using run-time system takes care for parameter transmission
| ํ๋ผ๋ฏธํฐ | ์ ๋ฌ ๋ฐฉ์ | ์ค๋ช |
|---|---|---|
| w | pass-by-value | ๊ฐ ์์ฒด๋ฅผ ๋ณต์ฌํ์ฌ ์ ๋ฌ๋จ |
| x | pass-by-result | ํจ์ ์ข ๋ฃ ์ ๊ฒฐ๊ณผ๋ฅผ ๋ณต์ฌํด์ ์ ๋ฌ |
| y | pass-by-value-result | ํธ์ถ ์ ๊ฐ ์ ๋ฌ, ์ข ๋ฃ ์ ๊ฒฐ๊ณผ ๋ณต์ฌ |
| z | pass-by-reference | ์ฃผ์(์ฐธ์กฐ) ์ ๋ฌ, ์๋ณธ ์ง์ ์์ ๊ฐ๋ฅ |
| ํ๋ผ๋ฏธํฐ | Stack ๋์ | ํจ์ ๋ด๋ถ์์ |
|---|---|---|
| a (w) | ํธ์ถ ์ w์ ๊ฐ ๋ณต์ฌ โ a ์ ์ ์ฅ | ์ฝ๊ธฐ ๊ฐ๋ฅ, ๋ณ๊ฒฝํด๋ w์ ์ํฅ ์์ |
| b (x) | ๋ณ๋ ์ ์ฅ์(์คํ)์ ๊ณต๊ฐ ๋ง๋ค๊ณ , ํจ์ ์ข ๋ฃ ํ b ๊ฐ์ x์ ๋ณต์ฌ | ํจ์ ๋ด๋ถ์์ b๋ ๋ก์ปฌ ๋ณ์์ฒ๋ผ ์ฐ๊ณ , ๋ฆฌํด ์ ๊ฐ ๋ณต์ฌ |
| c (y) | ํธ์ถ ์ y ๊ฐ ๋ณต์ฌ โ ํจ์ ์ข ๋ฃ ์ c ๊ฐ โ ๋ค์ y์ ๋ณต์ฌ | ์๋ฐฉํฅ ๊ฐ ๋ณต์ฌ (์ง์ , ์ข ๋ฃ ์ ๋ชจ๋) |
| d (z) | z ์ ์ฃผ์๋ฅผ ์ ๋ฌ โ ํจ์ ๋ด๋ถ์์ ์ง์ ์๋ณธ ์ ๊ทผ ๊ฐ๋ฅ | ๋ณ๊ฒฝ ์ z ๋ ๋ฐ๋ก ๋ฐ๋ |
- Parameter passing methods in common languages
- [C++] Pass-by-value but pass-by-reference can be achieved by pointer and reference types
void fun(const int &p1, int p2, int &p3) { ... }
- p1: pass-by-reference but cannot be changed, p2: pass-by-value, p3: pass-by-reference
- Constant parameter vs in-mode parameter
- Constant parameter can never be assigned but in-mode parameter can
- [Java] Pass-by-value but objects are passed by reference
- No pointer type, scalar cannot be passed by reference
- [C#] Pass-by-value but pass-by-reference can be achieved by
ref
void sumer(ref int oldSum, int newOne) { ... }
...
sumer(ref sum, newValue);
- Parameter passing methods in common languages
- [Python, Ruby] Pass-by-assignment
- All data are object
- [EX]
x = x + 1- Takes object referenced by x, increments by 1, then create new object, finally assign new object to x
string = "Hello"
string[0] = 'W' # X
string = "Wello" # O
- [EX] Scalar cannot be changed in subprogram
- Reference method of the object is determined depending on the object being passed
- Mutable Object(list, dict, set) โ Call by reference
- Immutable Object(str, int, tuple) โ Call by value
# [immutable] call-by-value
def func(x):
x = x + 1
a = 1
func(a)
# [mutable] call-by-reference
def func(x):
x.append(5)
a = [1, 2, 3, 4]
func(a)
# [mutable] ??
def func(x):
x = [1, 2]
a = [1, 2, 3, 4]
func(a)
- Type checking parameters
- Most languages require type checking
- Early programming languages (Fortran 77 and original version of C) did not require
double sin(x) // avoid type check
double x;
{ ... }
double sin(double x) // do type check (with coercion), prototype method
{ ... }
- ํจ์ ์ ์ธ๊ณผ ํ์ ์ ๋ณด๊ฐ ๋ถ๋ฆฌ๋์ด ์์์
- In C89, formal parameters can be defined in two ways
- In C99 and C++, prototype form but avoided by ellipsis
int printf(const char* format_string, ...);
- Type checking parameters
- In C#,
refactual parameter needs to be the same with formal parameter - In Python, Ruby, objects have types, but variables do not
- Formal parameters are typeless
- No type checking of parameter
- In C#,
| ์ธ์ด | ํ์ ๊ฒ์ฌ | ์ค๋ช |
|---|---|---|
| C# | ์๊ฒฉํ ํ์ ๊ฒ์ฌ | ํ์ ๋งค๊ฐ๋ณ์(formal parameter)์ ์ค์ ์ธ์(actual argument)์ ํ์
์ด ๋ฐ๋์ ์ผ์นํด์ผ ํจ. ref, out ํค์๋๋ฅผ ์ธ ๊ฒฝ์ฐ, ํธ์ถ๋ถ์๋ ๋ช
์์ ์ผ๋ก ๋ถ์ฌ์ผ ํจ โ ํ์๊ณผ ํค์๋๊น์ง ์ผ์นํด์ผ ์ปดํ์ผ ํต๊ณผ |
| C/C++ | (C89 ์ดํ) | C89๋ถํฐ ํจ์ ํ๋กํ ํ์
์ด ๋์
๋๋ฉฐ ํ์
๊ฒ์ฌ ๊ฐ๋ฅ. void f(int); f("abc"); โ ์ค๋ฅ ๋ฐ์. C++์์๋ ๋ ์๊ฒฉํ๊ณ ํจ์ ์ค๋ฒ๋ก๋ฉ๋ ์ง์ |
| Python | ๋ณ์๋ ํ์ ์์ | ๊ฐ์ฒด๋ ํ์ ์ ๊ฐ์ง์ง๋ง, ๋ณ์ ์์ฒด๋ ํ์ ์ด ์์. ํจ์์ ํ์ ๋งค๊ฐ๋ณ์๋ ํ์ ์ ์ธ์ด ์์ (๊ธฐ๋ณธ์ ์ผ๋ก) โ ํ๋ผ๋ฏธํฐ์ ๋ํ ํ์ ๊ฒ์ฌ๋ ํ์ง ์์ |
| Ruby | Python๊ณผ ๋์ผ | ๋ณ์๋ ์ด๋ค ํ์ ์ ๊ฐ์ฒด๋ ๊ฐ๋ฆฌํฌ ์ ์์. ํจ์ ์ ์ ์ ํ๋ผ๋ฏธํฐ์ ํ์ ์ ๋ช ์ํ ์ ์์ โ ๋ฐํ์์์ ์๋ชป๋ ํ์ ์ฌ์ฉ ์ ์ค๋ฅ ๋ฐ์ (๋์ ํ์ ) |
- Multidimensional arrays as parameters
- In C and C++, pointer can be used for passing
- Inclusion of pointer arithmetic
- Dimension of matrix (row, column) can be passed as parameters
void fun(float *mat_ptr,
int num_rows,
int num_cols);
- Can access element by
*(mat_ptr + (row * num_cols) + col) = x;- Or macro
#define mat_ptr(r,c) (*mat_ptr + ((r) * (num_cols) + (c)))
mat_ptr(row,col) = x;
- Multidimensional arrays as parameters
- In C# and Java, arrays are object
- Formal parameter for a matrix appear with
[][] - Use
length(Lengthin C#) to get row and column sizes
float sumer(float mat[][]) {
float sum = 0.0f;
for (int row = 0; row < mat.length; row++) {
for (int col = 0; col < mat[row].length; col++) {
sum += mat[row][col];
} //** for (int row ...
} //** for (int col ...
return sum;
}
- Design considerations
- Efficiency
- ์คํ ์๋์ ์์ ์ฌ์ฉ์ ๊ณ ๋ คํด ๋ถํ์ํ ๋ฐ์ดํฐ ์ ๊ทผ์ ์ค์ด๋ ๊ฒ์ด ์ค์ํจ
- ์๋ธํ๋ก๊ทธ๋จ ๋ด๋ถ์์ ์ธ๋ถ ๋ฐ์ดํฐ์ ์์ฃผ ์ ๊ทผํ์ง ์๋๋ก ์ค๊ณ
- ํ์ํ ๋ฐ์ดํฐ๋ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌ๋ฐ๊ณ , ๋ด๋ถ์์๋ง ์ฒ๋ฆฌ
- ๋ณต์ฌ ๋น์ฉ์ด ํฐ ๊ฐ์ฒด๋ ํฌ์ธํฐ๋ ์ฐธ์กฐ๋ก ์ ๋ฌ (C/C++)
- Minimize functional side effect
- ์๋ธํ๋ก๊ทธ๋จ์ด ์ธ๋ถ ์ํ๋ฅผ ์๊ธฐ์น ์๊ฒ ๋ฐ๊พธ๋ ๊ฒ์ ํผํด์ผ ํจ
- ์ ๋ ฅ๊ฐ๋ง ์ฌ์ฉํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๋ ์์ ํจ์(pure function) ์งํฅ
- ์ธ๋ถ ๋ณ์(global variables), ์ฐธ์กฐ ํ๋ผ๋ฏธํฐ ๋ฑ์ ๊ฐ๊ธ์ ์ฐ์ง ์๊ธฐ
- ๋ช ํํ๊ณ ์์ธก ๊ฐ๋ฅํ ํจ์๋ก ์ ์ง
- Efficiency
# ๋ถ์์ฉ ๋ฐ์: ํจ์๊ฐ ์ธ๋ถ ๋ฆฌ์คํธ๋ฅผ ๋ฐ๊ฟ
def add_item_bad(lst):
lst.append(100)
# ๋ถ์์ฉ ์์: ์๋ก์ด ๋ฆฌ์คํธ๋ฅผ ๋ฐํ
def add_item_good(lst):
return lst + [100]
- Examples
- Compare pass-by-value-result and pass-by-reference
void fun (int first, int second) {
first += first;
second += second;
}
void main() {
int list[2] = {1, 3};
fun(list[0], list[1]);
}
Assume that there is no accumulation
Pass by value: No changes
Pass by reference: list โ {2,6}
Pass by value-result: list โ {2,6}
Examples
- Compare pass-by-value-result and pass-by-reference
int i = 3; /* i is a global variable */
void fun(int a, int b) {
i = b;
}
void main() {
int list[10];
list[i] = 5;
fun(i, list[i]);
}
When pass-by-value-result is used, i and a are not alias
- global variable i is changed within fun
- But, formal parameter back to caller
When pass-by-reference is used, i and a are alias
- global variable i is changed within fun
- Formal parameter not back to caller โ i remains 5
Examples
void swap(int a, int b) {
int temp;
temp = a;
a = b;
b = temp;
}
void main() {
int value = 2, list[5] = {1, 3, 5, 7, 9};
swap(value, list[0]); // (1)
swap(list[0], list[1]); // (2)
swap(value, list[value]); // (3)
}
- Assume that there is no accumulation
- Pass by value: No changes after (1),(2),(3)
- Pass by reference:
- (1) value โ 1, list โ {2,3,5,7,9}
- (2) value โ 2, list โ {3,1,5,7,9}
- (3) value โ 5, list โ {1,3,2,7,9}
- Pass by value-result:
- (1) value โ 1, list โ {2,3,5,7,9}
- (2) value โ 2, list โ {3,1,5,7,9}
- (3) value โ 5, list โ {1,3,2,7,9} (when addr is computed at time of call)
- If addr is computed at time of return
- value โ 5, list[value==5] โ 2 (out of range error)
Parameter that are subprograms
In a certain situation, subprograms needs to be sent as parameters
[Issue 1] Type checking function's protocol
- ์ ๋ฌํ๋ ค๋ ํจ์๊ฐ ์ฌ๋ฐ๋ฅธ ํ์(๋งค๊ฐ๋ณ์ ๊ฐ์, ํ์ , ๋ฐํ ํ์ )์ ๊ฐ๊ณ ์๋์ง ํ์ธํด์ผ ํจ
- ๊ทธ๋ ์ง ์์ผ๋ฉด ์ปดํ์ผ๋ฌ๊ฐ ํธ์ถ ์ ํ์ ์ค๋ฅ๋ฅผ ํ์งํ์ง ๋ชปํจ
- โ ๋ฐํ์ ์ค๋ฅ ๋ฐ์ ์ํ
- ํจ์ ํฌ์ธํฐ(Function Pointer) ํ์
๋ช
์ (C/C++)
int compute(int x, int (*func)(int))func๋int(int)ํ์ ํจ์์ฌ์ผ ํจ โ ํ์ ์ฒดํฌ ๊ฐ๋ฅ
[Issue 2] Function would be nested โ how to decide referencing environment
- ํจ์๊ฐ ์์ ์ ๊ฐ์ผ ์ธ๋ถ ์ค์ฝํ์ ๋ณ์๋ฅผ ์ฐธ์กฐํ๊ณ ์๋๋ฐ,
- ๊ทธ ํจ์๊ฐ ๋ค๋ฅธ ํจ์์ ์ ๋ฌ๋๋ฉด ์ธ๋ถ ๋ณ์์ ์ ๊ทผํ ๋ ์ด๋ค ํ๊ฒฝ์ ๋ฐ๋ผ์ผ ํ ์ง ๋ชจํธํจ
- Shallow binding: environment of the call statement that enacts the passed subprogram (Used for dynamic-scoped languages)
- Deep binding: environment of the definition of the passed subprogram (Suitable for static-scoped languages)
- Ad hoc binding: environment of the call statement that passed the subprogram as an actual parameter
- x is bound to local x in sub1
- x is bound to local x in sub3
- x is bound to local x in sub4
Calling subprograms indirectly
- In a certain situation (event handling in GUI), subprograms must be called indirectly
- Unknown which subprogram will be called until runtime
- Done by pointer or reference to subprogram
float (*pfun)(float, int);pfun์ ๋งค๊ฐ๋ณ์(float, int)๋ฅผ ๋ฐ๊ณfloat์ ๋ฐํํ๋ ํจ์๋ฅผ ๊ฐ๋ฆฌํค๋ ํฌ์ธํฐ
#include <stdio.h>
float multiply(float a, int b) {
return a * b;
}
float divide(float a, int b) {
return a / b;
}
int main() {
float (*pfun)(float, int);
int condition = 1; // ๋ฐํ์ ์กฐ๊ฑด
if (condition)
pfun = multiply;
else
pfun = divide;
float result = pfun(10.0, 2); // ๊ฐ์ ํธ์ถ
printf("Result: %.2f\n", result); // 20.00
return 0;
}
- Called by
(*pfun2)(first, second); // ์ ํต์ ์ธ ํฌ์ธํฐ ๋ฐฉ์
pfun2(first, second); // C์์ ํ์ฉ๋๋ ๊ฐ๋จํ ๋ฌธ๋ฒ
- [C#] delegate
- Reference to method for pass it as parameter
- delegate๋ ํจ์ ํฌ์ธํฐ์ ๋น์ทํ ๊ฐ๋
- ํ๋ ์ด์์ ๋ฉ์๋๋ฅผ ์ฐธ์กฐํ ์ ์๋ ํ์
- ๋ฉ์๋๋ฅผ ๋ณ์์ฒ๋ผ ๋๊ธฐ๊ณ ํธ์ถํ ์ ์์
public delegate int Change(int x); // ๋ธ๋ฆฌ๊ฒ์ดํธ ํ์
์ ์ธ,
// int์ ๋ฐ์ int๋ฅผ ๋ฐํํ๋ ๋ฉ์๋ ์๊ทธ๋์ฒ์ ๋์ผํ ํ์
์ delegate๋ฅผ ๋ง๋ฆ
static int fun1(int x) { return x + 1; }
static int fun2(int x) { return x * 2; } // fun1, fun2๋ ๋ชจ๋ Change delegate์ ์๊ทธ๋์ฒ๊ฐ ๊ฐ์
Change chgfun1 = new Change(fun1); // fun1์ ์ฐธ์กฐํ๋ delegate ์์ฑ
chgfun1 += fun2; // fun2๋ ์ถ๊ฐ โ ๋ฉํฐ์บ์คํธ delegate
int result = chgfun1(5); // fun1(5), fun2(5) ์์ผ๋ก ์คํ๋จ
Overloaded subprograms
- The same name and referencing environments as another
- But, unique protocol (number, order types of parameters and return types)
- Meaning of call is determined by actual parameters
- [Issue 1] Coercion is allowed?
- When there is no exact match, should we allow the best match? and how?
- [Issue 2] The same parameter but different return types
- How to choose return type?
- The most common multiple versions of subprogram: constructors
// [C++]
int func(int first, int second) {
return first + second;
}
float func(float first, float second) {
return first - second;
}
func(1, 2); // 3
func(1.0f, 2.0f); // -1.0
func(1.0, 2.0); // error: call of overloaded 'func(double, double)' is ambiguous
func(1.0, 2.0f); // -1.0, coercion to func(float,float)
func(1.0, 2); // 3, coercion to func(int,int)
- ์ ํํ ์ผ์นํ๋ ํ์ ์ด ์ต์ฐ์
- ์์์ ๋ณํ ๊ฐ๋ฅํ๋ฉด ๊ณ ๋ ค๋จ
- ๋ ๊ฐ ์ด์์ด ๋ชจ๋ ์์์ ๋ณํ ๊ฐ๋ฅ์ด๋ฉด โ ๋ชจํธ์ฑ ์ค๋ฅ(ambiguous)
// [C++] ambiguous return type
int func(int first, int second) {
return first + second;
}
float func(int first, int second) {
return first > second ? first : second;
}
// error: ambiguating new declaration of 'float ...
// [Java]
public static int func(int first, int second) {
return first + second;
}
public static float func(float first, float second) {
return first - second;
}
func(1, 2); // 3
func(1.0f, 2.0f); // -1.0
func(1.0, 2.0); // no suitable method found for func(double,double), argument mismatch; possible lossy conversion from double to int(float)
func(1.0, 2.0f); // no suitable method โฆ. lossy โฆ
func(1.0, 2); // no suitable method โฆ. lossy โฆ
// [Java] If not lossy conversion
func('A', 2); // 67, coercion to func(int, int)
func('A', 2.0f); // 63.0, coercion to func(float,float)
func('A', 'B'); // 131, coercion to func(int,int)
// [Java] ambiguous return type
public static int func(int first, int second) {
return first + second;
}
public static float func(int first, int second) {
return (float) (first - second);
}
// error: method func(int,int) is already defined
Generic subprograms
Reusability is important for productivity
Polymorphism!
- Ad hoc polymorphism
- Overloaded subprograms (need not behave similarly each other)
- Subtype polymorphism
- OOP, variable of type T can access any object of type T and derived from T
- Parametric polymorphism
- Takes generic parameters
- Parametrically polymorphic subprograms are called generic subprograms
- Ad hoc polymorphism
Generic functions in C++
template function
// Overloaded subprograms
int max(int first, int second) {
return first > second ? first : second;
}
float max(float first, float second) {
return first > second ? first : second;
}
double max(double first, double second) {
return first > second ? first : second;
}
// With template function
template <class Type>
Type max(Type first, Type second) {
return first > second ? first : second;
}
int x1 = 2, x2 = 3;
max(x1, x2) // dynamically bound to the types
- Generic functions in C++
- If overloaded?
template <class Type>
Type max(Type first, Type second) {
return first > second ? first : second;
}
float max(float first, float second) {
return first > second ? -first : -second;
}
double max(double first, double second) {
return first > second ? 2*first : 2*second;
}
max(1, 2) // 2
max(1.0f, 2.0f) // -2
max(1.0, 2.0) // 4
// โ specific functions have higher precedence than generic function
- Generic functions in C++
- With standard library in
std?
#include<iostream>
using namespace std;
template <class Type>
Type max(Type first, Type second) {
return first > second ? first : second;
}
max(1, 2) // error: call of overloaded 'max(double, double)' is ambiguous
// โ conflict max() in std
#include<iostream>
using namespace std;
int max(int first, int second) {
return first > second ? first : second;
}
max(1, 2) // 2
// It's okay to define function for specific type
- Generic functions in C++
- template with different types?
template <class Type>
Type max(Type first, Type second) {
return first > second ? first : second;
}
// max(1, 2.0) // error: no matching function for call to 'max(int, double)
// โ Single type can cover one type only
template <class Type1, class Type2>
Type1 max(Type1 first, Type2 second) {
return first > second ? first : second;
}
max(1, 2) // 2
// Need to define different Type
- Generic functions in C++
- How about array?
template <class Type1, class Type2>
double max(Type1 first, Type2 second, int index) {
return first[index] > second ? first[index] : second;
}
int x[3] = {1,2,3};
max(x, 2.0, 2); // 3
// โ array (pointer) can be also covered
- Generic functions in C++
- template function vs macro
template <class Type>
Type max(Type first, Type second) {
return first > second ? first : second;
}
#define max(a, b) ((a) > (b)) ? (a) : (b)
- Macro can be used but problem when there is side effect
max(x++, y)
โ ((x++) > (y) ? (x++) : (y))
// x is increased twice
Design issues for functions
- Side effect allowed?
- To prevent it, enforce im-mode parameters
- [Imperative] have side effects
- [Functional] have no variable, so no side effects
- What type can be returned?
- Mostly (Python, Ruby, and Lua), any type can be returned, passed as parameters
- [C] Arrays and functions are handled by pointer
- [Java, C#] Method (class function) is used, any type and class can be returned
- How many values can be returned
- Ruby, Lua, Python can return multiple values
a, b, c = fun()- Tuple is used
User-defined overloaded operators
- Operator can be also overloaded
- [C++] Several operators can be overloaded
- Exceptions:
.,.*,::,?: - Define operator as a method in class
- Exceptions:
class Complex
{
public:
Complex(int real, int img){
this->real = real;
this->img = img;
}
Complex operator+(const Complex& right){
int real = this->real + right.real;
int img = this->img + right.img;
return Complex(real, img);
}
private:
int real;
int img;
};
Complex x1 = Complex(1,1);
Complex x2 = Complex(2,2);
Complex x3 = x1 + x2;
- Operator can be also overloaded
- [Python] For addition for complex numbers
- Define addition operation in special method(
__add__)
- Define addition operation in special method(
def __add__ (self, second):
return Complex(self.real + second.real, self.imag + second.imag)
Closures
- Function is also object
- Can be pass to other function, returned by function and stored in data structure
# Function can be passed
def funcO(func):
return func()
def funcI():
return "function inside"
print(funcO(funcI))
# Function can be stored in list
def add(a, b):
return a + b
def subtract(a, b):
return a - b
def multiply(a, b):
return a * b
def divide(a, b):
return a / b
func_lst = [add, subtract, multiply, divide]
n = 1
m = 2
for func in func_lst:
print(func.__name__, ":", func(n, m))
# add : 3
# subtract : -1
# multiply : 2
# divide : 0.5
- Function can be nested
inner()can only be called insideouter()
def outer():
print("Here is outer region.")
def inner():
print("Here is inner region.")
inner()
outer()
- A closure is a function returned by a function
def addNumber(fixedNum):
def add(number):
return fixedNum + number
return add
func = addNumber(10)
func(20) # 30
func(30) # 40
- Each of these calls returns a different version of the closure because they are bound to different values of
fixedNum - lifetime of the version of
fixedNumcreated whenaddNumberis called must extend over the lifetime of the program - This subprogram can be called in other place
- โ Subprogram needs to remembers the referencing environment in which it was defined
- Benefits
- Global variables can be reduced.
- Similar types of code can increase the reuse rate
def addNumber(fixedNum):
def add(number):
return fixedNum + number
return add
func1 = addNumber(10)
func2 = addNumber(20)
print(dir(func1))
print(func1.__closure__[0].cell_contents)
print(func2.__closure__[0].cell_contents)
['__annotations__', '__builtins__', '__call__', '__class__', '__closure__', '__code__',
'__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__',
'__ge__', '__get__', '__getattribute__', '__getstate__', '__globals__', '__gt__',
'__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__',
'__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__',
'__type_params__']
(<cell at 0x7fb35f401240: int object at 0x564e4ecb58c8>,)
10
20
Since function is also an object, we can check its properties through the
dir()function, and we can see that there is__closure__in the checked properties.__closure__is tupleWe can check information of closure
A closure can model high-level module such as neural network
def nonlinear_activation():
e = 15
def thre(x):
return 1 if x > e else 0
return thre
def linear_model():
a = 3
b = 5
def mul_add(x):
return a * x + b
return mul_add
lm = linear_model()
print(lm(1), lm(2), lm(3), lm(4), lm(5))
na = nonlinear_activation()
print(na(lm(1)), na(lm(2)), na(lm(3)), na(lm(4)), na(lm(5)))
# 8 11 14 17 20
# 0 0 0 1 1
Neural network consists of huge amount of linear model + nonlinear activation
Each model has similar structure (process) with only different learnable parameters
A closure with lambda
def linear_model():
a = 3
b = 5
return lambda x: a * x + b
lm = linear_model()
print(lm(1), lm(2), lm(3), lm(4), lm(5))
# 8 11 14 17 20
- Change local variable of closure
def linear_model():
a = 3
b = 5
sum = 0
def mul_add(x):
nonlocal sum
result = a * x + b
sum += result
print("sum:%d" % sum)
return result
return mul_add
lm = linear_model()
print(lm(1), lm(2), lm(3), lm(4), lm(5))
# sum:8
# sum:19
# sum:33
# sum:50
# sum:70
# 8 11 14 17 20
Coroutines
Special subprogram
In coroutine, caller and called coroutines are more equitable
In common subprogram, caller and callee have master-slave relationship
Coroutines can have multiple entry points
Controlled by symmetric unit control model.
Resume: secondary executions of a coroutine often begin at points other than its beginning
History sensitive
co1() โ co2(2) โ co1() โ co3() โ co1()Only one coroutine is actually in execution at a given time โ quasi-concurrency
Related to the way multiprogramming operating systems
Usually, master unit handle. EX. card game simulation
(a) execution of coroutine A is started by the master unit
(b) execution of coroutine B is started by the master unit
Coroutine execution sequence with loops
def coroutine1():
print('callee 1')
x = yield 1
print('callee 2: %d' % x)
x = yield 2
print('callee 3: %d' % x)
task = coroutine1()
i = next(task) # print callee 1, i = 1
i = task.send(10) # print callee 2:10, i = 2
task.send(20) # print callee 3: 20, then StopIteration exception
# callee 1
# callee 2: 10
# callee 3: 20
# StopIteration

