Functional programming?
- ml에서의 패러다임임. 프로그램 쓸 때 어떤 생각으로 하냐는 의미
- mutation을 막음
- bug가 생길 가능성을 낮춰줌
- functon을 value로 취급
- == pass function as argument just like data
- instead of data you can pass function to data
- map, reduce같은.. high order function: big data, large data를 처리해야할 때 instead of moving data, move the code to data: 더 빠름
- 데이터는 크고 code가 작으니까
- pass code in data를 할 수 있다
- recursion을 많이 사용
- 수학적인 정의에 가깝게 구현 가능
- ex) fact
vs. oop는 handle difference in same manner를 설명, 다른 행동을 하는 애들을 같이 하도록 하는 패러다임
- ex) dog, cat → treat same way either dog, cat
- 좋은 이유? : 만약에 mouse를 더하고 싶으면 원래 코드를 수정하지 않고 추가할 수 있으니까!
→ fp는 abstraction을 다르게 할 수 있음
First class function
- 정의
- value가 쓸 수 있는 모든 곳에 function을 사용할 수 있다
- ex) argument, result, part of tuple, bound to variable, carried by datatype constructor or exceptions ….
- value가 쓸 수 있는 모든 곳에 function을 사용할 수 있다
fun double x = 2*x
fun incr x = x+1
val a_tuple = (double,incr,double(incr 7))
- 대부분은 다른 함수에서의 argument나 result으로 함수를 많이 사용
- 이 때 다른 함수를 higher-order function이라고 부름
- common functionality를 factor out하는 강력한 방법 중 하나
ex) all animal eat something
oop-eat method를 만들어서 상속해서 eat함수를 가지도록 했을 것, extract common property를 해서 factor out 그 속성을 했음. data로 factor out함
→ higher order function은 common function(code)를 factor out하는 것!
Function Closures
function value와 비슷하지만 한단계 더 간 개념
- laziness를 사용
- ex) lazy list
- function은 function definition 안에 있는 것도 사용할 수 있지만 밖에서 binding된 변수들을 사용할 수 있음
- function이 defined된 스콥에서
return the function and then call later, 하지만 밖에 정의된 변수를 여젼히 사용할 수 있다면 아주 파워풀해질 것!
→ first class function을 더 강력하게 해줌
- first class function과 function closure 차이점
- first class function과 비슷하지만 function closure은 function + variable binding not define inside but outside
Function as arguments
- 한 함수를 다른 함수의 인자로 보낼 수 있다!
fun f (g,... ) = ....g(...)... (*함수를 인자로 보내는 f*)
fun h1... = ...
fun h2... = ...
.....f(h1,...) ....f(h2,..)
- f 함수는 g를 인자로 받아서 그 함수를 부를 수 있음
- f: higher order function, 함수를 인자로 받으니까
→ common code를 factoring out하는데 아주 유용
즉, 비슷한 일을 하는 함수 n개를 n개의 다른 작은 function만을 argument로 보내서 처리하는 한개의 함수로 대체할 수 있다!!
ex) n similar function, for loop는 같은데 안에 하는 일이 다른 경우
→ for loop를 다른 데 두고 body를 pass해서 실행
fun n_times(f,n,x) =
if n=0
then x
else f(n_times(f,n-1,x)) (*tail recursive아님! tail call이 아니여서*)
fun double x = x+x
fun increment x = x+1
val x1 = n_times(double,4,7)
val x1 = n_times(increment,4,7)
val x1 = n_times(tl,2,[4,8,12,16])
fun double_n_times(n,x) = n_times(double,n,x)
fun nth_tail(n,x) = n_times(tl,n,x);
- n_times의 타입
- n=0이 있으니까 int이고 f의 리턴이 n_times의 리턴이니까 둘이 같은데 n_times의 결과를 f의 인자에 넣으니까 n_times의 리턴과 x, f의 인자도 같게 됨
- (’a → ‘a) * int * ‘a → ‘a
- n_times는 tail recursive한가?
- Tail recursive가 아님. 왜냐면 function call e1 e2이고 recursion이 e2이기에 리턴해도 해야할 일이 있으니까 tail recursive가 아님
- 이런걸 tail recursive로 하려면??? -> (n_times (f, n-1, f x))
Q. c++함수 포인터와 first class function과 다른점?
A. 함수포인터는 first class function과 거의 같지만 closure은 아님, binding이 안되면 할 수 있는 게 제한됨. 다만 function pointer는 Low level이여서 잘못동작할 수도 있다
Relation to types
- 고차함수는 대부분 polymorphic type으로 generic이고 reusable 인데 그 이유는 다양하게 작동하기 위함
- 물론 아닌 고차함수도 존재하긴 함!
- 또한 non-higher-order function(first-order, 즉 매개변수로 함수를 받지 않는 함수)의 경우에도 polymorphic한 경우도 존재함
→ higher order를 더 유용하게 하기 위해서는 generic type인게 좋음
→ x가 int면 해당 함수는 ‘a에서 int로 instantiate되고 x가 int list면 해당함수는 ‘a에서 int list로 instantiate된다 : 즉, polymorphism이 일어나 더욱 유용해짐!!
Polymorphism and higher-order functions
- 많은 고차함수들은 some type에 대해 재사용가능하기 때문에 Polymorphic임
- some polymorphic function은 not higher-order function인 경우 존재
- ex) length: ‘a list → int
- some higher order function이지만 polymorphic하지 않는 경우도 존재
fun times_until_0(f,x) =
if x=0 then 0 else 1+time_until_0(f,f x)
Anonymous function
- 함수를 정의할 때 이름을 정하기 어렵거나 정할 필요도 없는 경우 이름 없이 선언
(* naive한 방법 *)
fun triple x = 3*x
fun triple_n_times (n,x) = n_times(triple, n,x)
(*let in end 안에 함수 선언*)
fun triple_n_times(n,x)=
let fun trip y = 3*y
in n_times(trip,n,x)
end
(* 더 작게 let in end 안에 함수 선언*)
fun triple_n_times(n,x) =
n_times(let fun trip y = 3*y in trip end, n ,x)
(* function binding은 Expression이 아니므로 파라미터에 넣는 건 안됨!! *)
fun triple_n_times(n,x) =
n_times((fun trip y = 3*y), n ,x)
(* 익명함수를 쓰자! *)
fun triple_n_times(n,x) =
n_times(**(fn y => 3*y)**, n ,x)
⇒ anonymous function: 함수를 선언하면서 바로 호출
- expression이기에 argument로 pass 가능
- fun → fn, = → ⇒, 이름 없음(그냥 argument pattern) 의 차이점
- 사용하는 곳
- higher order function의 인자로 주로 많이 사용됨
→ 따라서 익명함수를 variable binding을 하게 하면 function binding과 동일함(fun binding이 syntatic sugar임을 알 수 있음!!(val binding + anonymous function))
fun triple x = 3*x
val triple = fn y => 3 * y
val x1' = n_times(double, 4,3); // 여러번 사용될 경우 이게 나음
val x1' = n_times(fn x-> x+x, 4,3); // 한번만 쓰면 이게 나음
if x then true else false // 이렇게 하지 말고
x // 이렇게 할 것
n_tiems((fn y => tl y), 3, x) //마찬가지 그냥 함수만 호출하는 거라면 이렇게 하지 말고
n_times(tl,3,x) // 이렇게 할 것!
Map
- 약간의 공간을 절약하고 더 중요한 것은 수행 중인 작업을 전달함
- 각 요소에 f함수를 적용하는 것
- xs = [x1,x2…] ⇒ [f(x1), f(x2) …. ]
fun map(f,xs) =
case xs of
[] => []
|x::xs' => f x :: map(f,xs');
val intlist = [1,2,3,4,5];
map(fn x => x mod 2, intlist); // [1,0,1,0,1], 여기는 알파 베타 모두 int임
List.map (fn x => x mod 2) intlist; // 같은 의미를 curry function으로
- ML에는 List.map이라는 비슷한 predefined curry function 존재
Filter
- XS 리스트에서 f함수가 true인 애들만 필터링하는 함수
fun filter(f,xs) =
case xs of
[] => []
|x::xs' => if f x then x::filter(f,xs') else filter(f,xs');
filter((fn x => x mod 2=0), intlist) ; // [2,4]
List.filter (fn x => x mod 2 = 0) intlist;
- ML에는 List.filter이라는 비슷한 predefined curry function 존재
Q. 고차함수가 데이터를 전달하냐 함수를 전달하냐인 거 같은데 왜 쓰지?
A. 리스트를 반복하면서 해야할 일이 많을 때 그부분을 extract해서 모듈러한거다. 데이터를 캡슐화하는 건 많은데 for loop 안을 모듈러화해서 하는 거
cf. 알파 베타 순서는 먼저 나오는 generic type부터 알파 베타 타입인거고 그냥 이름임.
Generalizaing
- first class function
- 한 함수를 다른 함수의 인자로 보낼 수 있음
- ex) process number or list
- function을 argumente로 보낼 수 있음
- data structure에 function을 넣을 수 있음
- result로 return function을 할 수 있음
- higher-order function을 쓸 수 있게 되어서 data structure을 순회할 수 있음
- 한 함수를 다른 함수의 인자로 보낼 수 있음
→ what to compute with를 abstract하고 싶다면 유용하게 사용 가능!
Returning functions
fun double_or_triple f =
if f 7
then fn x => 2 *x
else fn x => 3 * x
- 이 때 REPL은 위 함수의 타입을 괄호 없이 (int → bool) → int → int로 표시
- last to first여서 필요 없음
- ex) t1→t2→t3→t4 == (t1 → (t2 → (t3 → t4)))
Other data structures
higher order function은 리스트나 숫자뿐만 아니라 own data type에도 적용가능하다!
datatype expr = Constant of int
| Negate of expr
| Add of expr * expr
| Multiply of expr * expr;
(*higher order function 사용 x *)
fun all_even(e) =
case e of
Constant i => i mod 2 = 0
| Negate e2 => all_even(e2)
| Add (e1, e2) => all_even(e1) andalso all_even(e2)
| Multiply (e1, e2) => all_even(e1) andalso all_even(e2);
fun all_odd(e) =
case e of
Constant i => i mod 2 = 1
| Negate e2 => all_odd(e2)
| Add (e1, e2) => all_odd(e1) andalso all_odd(e2)
| Multiply (e1, e2) => all_odd(e1) andalso all_odd(e2);
(*두개를 합쳐보쟝!, 같은 거를 묶고 다른 거만 함수로 넘길 것*)
fun all(test,e) =
case e of
Constant i => test(i)
| Negate e2 => all(test, e2)
| Add (e1, e2) => all(test, e1) andalso all(test, e2)
| Multiply (e1, e2) => all(test, e1) andalso all(test, e2)
fun all_even(e) = all(fn x=> (x mod 2) = 0, e)
fun all_odd(e) = all(fn x=> (x mod 2) = 1, e)
(*이건 해보래 *)
fun any(test, e) =
case e of
Constant(e1) => test(e1)
| Negate(e1) => any (test,e1)
| Add (e1,e2) => any (test,e1) orelse any (test,e2)
| Multiply(e1,e2) => any (test,e1) orelse any (test,e2);
fun any_even(e) = any(fn x=> (x mod 2) = 0, e)
fun any_odd(e) = any(fn x=> (x mod 2) = 1, e)
val a = Add(Negate(Constant 2), Multiply(Constant(8), Negate(Constant(3))));
any(fn x => x mod 2 = 0 ,a); // true
all_odd(a); //false
all_even(a); // false
any_even(a);// true
any_odd(a);// true
→ redundency를 제거할 수 있으니까 higher order function을 사용하쟝!
oop는 상속으로 해결하는 거고 fp는 higher order function으로 해결하는 것
Type Synonyms == type alias
intintint를 date로 부르고 싶다면??
type date = int * int * int;
(* 이건 내가 그냥 든 예시 *)
datatype suit = Club | Diamod | Heart | Spade;
datatype card_value = Jack | Queen | King | Ace | Num of int;
datatype rank = Rank;
(*datatype으로 suit * rank 타입을 만드는 법*)
datatype card_datatype = Card of suit * rank;
val a: card_datatype = **Card(Club, Rank)**; (***card** 타입*)
(*type으로 suit * rank의 별칭을 만드는 법*)
type card_type = suit * rank;
val a: card_type = **(Club, Rank)**; (*card2는 단지 별칭이기 때문에 실질적인 type은 **suit * rank**!*)
fun printSuit(x: **card_datatype**) =
case x of
Card(s, _) => s;
printSuit(Card(Club, Rank));
fun printSuit(x: **card_type**) = #1 x;
printSuit(Club,Rank);
type vs datatype
- datatype: 새로운 Type을 생성
- type: 새로운 데이터타입을 만들지말고 별칭 생성
Q. 왜 type을 사용하면 유용할까??
Type Generality
- generic type
fun append(xs, ys) =
case xs of
[] => ys
|a::b => a::append(b,ys);
- string list라는 타입을 쓰다가 타입을 지워도 여전히 동일하게 작동하지만 함수의 타입이 ‘a list로 바뀌게 됨
Q. string list이 ‘a list로 바뀌어도 왜 될까??
A. ‘a가 string의 general version이기에 string은 ‘a의 subset이 됨
→ more general types can be used as any less general type
more general rule
t1을 take한 후에 type variable로 일관되게 대체해서 t2를 얻을 수 있다면 ⇒ t1이 t2보다 더 general하다
- t1이 더 일반적이여서 t1에서 t2로 바꾼 후 실행하면됨
- 앞에 예시에서는 t1: ‘a, t2: string 이 되고 t1을 type value consistently하게 replace한 후 ‘a를 string으로 모두 바꿀 수 있으니까 general하다라고 볼 수 있다.
cf.int랑 real은 type generality와 subtype 관계 모두 없음.
Subtype 좀 더 semantic
S와 T 타입이 있는데 S가 T의 subtype이라면(S <: T) S의 인스턴스는 T 인스턴스가 쓰이는 어느 곳에서든 safely used가 가능
- ml은 subclass는 없고 subtype과 generic type만 제공
- c++은 subclass(class inheritance)과 generic type(template) 제공
subtype vs subclass in semantic
ex) 인스턴스 t로 쓴 코드(exception 없었음), 근데 거기에 s를 넣게 된다면?? s 안에 있던 메소드에서는 runtime exception을 던진다면?? 걔는 subtype처럼 safe하다라고 볼 수 없음!
→ subtype과 subclass가 다름을 알 수 있음
ex) subtype관계: record와 tuple
Subtype example
자바 디자인할때에는 잘못했었음 array 타입이 잘못되었는데… 이건 나중에 커버할거래
위가 type generality, 아래가 subtype