4. 순수함수 - Pure functions
• 함수의 결과값이 오직 입력 인자 값들에 의해서만 결정
• 특정 입력 값에 대해서 결과값이 결정적으로 대응되는 함수
• 부작용(Side effects)이 없는 함수
int abs(int n);
int atoi(const char *str);
int getDefaultPort(bool https) {
return https ? 443 : 80;
}
int rand();
time_t time (time_t* timer);
bool g_https = false;
int getDefaultPort() {
return g_https ? 443 : 80;
}
5. 부작용 - Side effects
• 상태에 영향을 받거나 변화를 주는 모든 동작
• 화면출력
• 소켓통신
• 디스크 I/O
• 예외 catch
• 현재 시각 읽기
• 난수 발생
6. 참조 투명성 - Referential transparency
• 어떤 표현식을 그 표현식의 결과값으로 교체해도 전체
프로그램의 실행결과에 영향을 주지 않는 성질
• 컴파일러에게 다양한 최적화 기회를 제공
기억 – Memoization
- 입력값 / 함수명을 알면 결과값은 항상 같음
지연연산 - Lazy evaluation
- 실제로 결과값이 필요할 때만 함수를 평가
7. • 코드에서 가능한 순수함수의 비율이 많아지도록 하자
- 글로벌 변수, 멤버 변수 의존도를 줄이자
- 상태를 없애거나 줄이고 부작용을 격리시키자
• 함수는 인자를 받아서 값을 리턴하는 형태로
- 레퍼런스 인자 보다는 값을 리턴
- 여러 개의 값을 리턴할 때는 tuple을 사용
FP 실천사항 #1 - 순수함수
9. Java String is immutable
String str = "Welcome to NDC2016!!!";
String newString =
str.replace(“2016”, “2017”);
System.out.println(newString);
String str = "Welcome to NDC2016!!!";
str.replace(“2016”, “2017”);
System.out.println(str);
Welcome to NDC2016!!! Welcome to NDC2017!!!
10. 불변객체의 특징
• 내용의 변경은 새로운 객체를 생성할 때만 가능
• 생성, 테스트, 사용법이 단순하고 쉬움
• 진정한 의미의 불변객체는 그 자체로 Thread-safe
• 쉬운 캐쉬 – 이름이 같으면 내용도 같다
• Temporal coupling 줄임
• Identity mutability problem 없음
15. • 사용하는 객체를 불변으로 일단 바꾸어 놓고 코드를
고쳐보자
- Setter들 대신에 생성자에서 인자로 초기화
- 객체를 변경하지 않는 멤버함수들은 const로
• 다양한 분산 빅데이터 알고리즘들을 조사해보자
- Immutability changes everything -
http://queue.acm.org/detail.cfm?id=2884038
• 영속형 자료 구조에 대해서 알아보자
FP 실천사항 #2 - 불변객체
20. Unix 명령어들의 합성 - PIPE
history | awk '{a[$2]++}END{for(i in a){print a[i] " " i}}' | sort -rn | head
47 ls
26 cd
19 sudo
17 clang++
10 clang
7 history
7 ./a.out
5 ./compose
4 gedit
2 top
21. 예제 – 몬스터에게 범위공격
• 특정 거리 r 이상 R 이하에
있는 대상을 공격
• 방어막이 있으면 방어막의
세기만큼 피해 감소
Dev-art by TH. Ahn
22. Monster class
class Monster {
public:
Monster();
void setId(int id);
void setHP(int hp);
void setArmor(int armor);
void setLocation(const Point& pt);
int getHP() const;
int getId() const;
bool getArmor() const;
Point getLocation() const;
void gotDamage(int damage);
private:
int id_;
int hp_;
int armor_;
Point pt_;
};
using MonsterPtr = std::shared_ptr<Monster>;
using Monsters = std::vector<MonsterPtr>;
void Monster::gotDamage(int damage) {
hp_ = hp_ - std::min(hp_, damage - armor_);
}
int calc_distance(const Point& pt1, const
Point& pt2);
23. Monsters doSplashDamage(const Point& my_location, const Monsters& monsters,
int damage, int range_from, int range_to) {
for (auto i = monsters.begin(); i != monsters.end(); ++i) {
auto pt = *i;
Monster& target = *pt;
int distance = calc_distance(my_location, target.getLocation());
if (distance >= range_from && distance <= range_to) {
target.gotDamage(damage);
}
}
Monsters alive_monsters;
for (auto i = monsters.begin(); i != monsters.end(); ++i) {
auto pt = *i;
Monster& target = *pt;
if (target.getHP() > 0) {
alive_monsters.push_back(pt);
}
}
return alive_monsters;
}
전형적인 절차형 코드
24. 우리가 하고 싶은 것들
• 객체를 불변데이터로 다루고 싶다
• 가능한 순수함수를 쓰고 싶다
• 알고리즘 구현 부분과 사용 부분을 코드상에서 분리하고
싶다
• 임시변수들을 없애고 싶다
• 함수들을 쉽게 합성하고 싶다
25. 불변 Monster class와 피해계산 순수함수
class Monster {
public:
Monster(int id, int hp,
int armor, const Point& pt);
int getHP() const;
int getId() const;
int getArmor() const;
Point getLocation() const;
static int calc_damaged_hp(int hp,
int damage, int armor);
private:
int id_;
int hp_;
int armor_;
Point pt_;
};
int Monster::calc_damaged_hp(int hp,
int damage, int armor) {
return hp - std::min(hp, damage - armor);
}
26. 주요 문장들을 함수로 쪼개기
bool is_in_range(const Point& my_location, int range_from, int range_to, const MonsterPtr& pm);
MonsterPtr damaged_monster(int damage, const MonsterPtr& pm);
bool is_alive(const MonsterPtr& pm);
32. Most effective STL using ranges
std::vector<int> numbers = { 1, 2, 3, 4, 5, 6, 7 };
std::vector<int> output;
auto is_even = [](int i) { return i % 2 == 0; };
auto mul_2 = [](int i) { return i * 2; };
std::copy_if(numbers.begin(), numbers.end(), std::back_inserter(output), is_even);
std::transform(output.begin(), output.end(), output.begin(), mul_2);
int sum = std::accumulate(output.begin(), output.end(), 0);
auto rng = numbers
| ranges::view::filter(is_even)
| ranges::view::transform(mul_2);
int sum = ranges::accumulate(rng, 0);
// numbers is still {1, 2, 3, 4, 5, 6, 7};
33. ranges::view로 algorithm을 합성하기
auto doSplashDamage_one_liner(const Point& my_location, const Monsters& monsters,
int damage, int range_from, int range_to) {
return monsters
| ranges::view::transform(do_damage_(my_location, damage, range_from, range_to))
| ranges::view::filter(is_alive);
}
“누가 한 줄로 짤 코드를 열줄로 만들어 놨어요?”
34. 합성으로 구현한 코드의 손쉬운 변경
auto doSplashDamage_one_liner(const Point& my_location, const Monsters& monsters,
int damage, int range_from, int range_to) {
return monsters
| ranges::view::transform(do_damage_(my_location, damage, range_from, range_to))
| ranges::view::transform(apply_healing_(hp, range_from, range_to))
| ranges::view::filter(is_alive);
}
“기존의 코드는 건드리지 조차 않아도 되니 안심!”
35. ranges의 지연연산
auto doSplashDamage_top_3(const Point& my_location, const Monsters& monsters,
int damage, int range_from, int range_to) {
return monsters
| ranges::view::transform(do_damage_(my_location, damage, range_from, range_to))
| ranges::view::filter(is_alive)
| ranges::view::take(3);
}
쓰지 않을 객체는 만들지도 말고, 필요 없는 계산은 하지도
말자!
36. std::ranges가 되고 싶은 Ranges TS
• STL을 더욱 효과적으로 활용
• 불변 컨테이너
• 합성 / 지연연산 / Monad *
• https://github.com/ericniebler/range-v3
• https://github.com/Microsoft/Range-V3-VS2015
37. • 고차함수를 사용해서 코드를 간결하게 만들자
- 간결한 코드 -> 가독성 -> 적은 버그
• 알고리즘의 구현 부분과 사용 부분을 분리하자
- 로직 구현에 실제로 관련 없는 임시변수와 상태들을
없애자
• 함수를 합성 가능하게 만들어서 재활용성을 높이자
- 검증된 재활용이 쉬운 Building block들을 합성
• 지연연산을 활용해서 최적화를 시도하자
- 무한리스트에 대해서 알아보자
FP 실천사항 #3 – 고차함수와 합성
39. 타입의 활용
• 메모리상에 저장된 데이터가 쓰일 방법을 정의
• 가능한 빠른 단계에서 오류 발견
• 코드의 의미 함축성 증가
• 코드 작성중의 힌트
• 로직 설계의 도구
• 지원하지 않는 동작은 표현 자체가 불가능 하도록
• 합성 동작의 연결을 위한 접착제
40. std::optional in C++17
void optional_demo() {
std::optional<std::string> name;
assert(!name);
if (name) std::cout << *name << std::endl;
std::optional<std::string> a_name("Anonymous");
assert(a_name);
assert(a_name.value() == "Anonymous");
assert(*a_name == "Anonymous");
if (a_name) std::cout << *a_name << std::endl;
}
• null – “The worst mistake of computer science”
• Type safe / no dynamic allocation
41. std::variant in C++17
std::variant<std::string, bool, int> v = std::string("Hello!");
assert(std::get<std::string>(v) == "Hello!");
std::variant<std::string, bool, int> b = true;
assert(std::get<bool>(b) == true);
try {
std::cout << std::boolalpha << std::get<bool>(v) << std::endl;
} catch (std::bad_variant_access&) {
std::cout << "exception..." << std::endl;
}
• Discriminated union
• Never-empty guarantee
• Type safe / no dynamic allocation
42. Quiz #1
struct Human {
unsigned char age_;
bool alive_;
};
다음 타입은 몇 개의 서로 다른 값(상태)를 나타낼 수
있을까요?
256 * 2 = 512
using A_Tuple_Type = std::tuple<unsigned char, bool>;
43. Quiz #2
enum Gender : int {
MALE,
FEMALE,
UNKNOWN
};
struct Human {
Gender gender_;
unsigned char age_;
bool alive_;
};
Human은 몇 개의 서로 다른 값(상태)를 나타낼 수
있을까요?
3 * 256 * 2 = 1536
44. Quiz #3
using Another_Tuple_Type = std::tuple<Gender, unsigned char, bool>;
다음 타입은 몇 개의 서로 다른 값(상태)를 나타낼 수
있을까요?
3 * 256 * 2 = 1536
45. 동등한 타입
struct Human {
Gender gender_;
unsigned char age_;
bool alive_;
};
std::tuple<Gender, unsigned char, bool>;
47. Quiz #5
struct Alien { bool alive_; };
std::variant<Human, Alien>;
다음 타입은 몇 개의 서로 다른 값(상태)를 나타낼 수
있을까요?
1536 + 2 = 1538
48. 대수적 자료형 - Algebraic data types (ADTs) in
C++
• 타입도 합성
• Product types (*) – std::pair, std::tuple, struct/class
• Sum types (+) – std::optional, std::variant
49. ADTs로 표현 한다면? – 타입의 합성
class HttpRequest {
//...
private:
Url url_;
SecureUrl url_ssl_;
bool ssl_;
std::string id_;
std::string password_;
bool need_auth_;
};
class HttpRequest {
//...
private:
std::variant<Url, SecureUrl> url_;
using Credential =
std::tuple<std::string, std::string>;
std::optional<Credential> auth_;
};
• 유효한 상태의 개수보다 데이터 타입의 Cardinality가
크다면?
무효한 상태를 가질 가능성 -> 버그!
50. • 타입을 최대한 사용해서 안전한 코드를 만들자
- 불가능한 동작은 표현 자체가 불가능하도록
• 타입이 표현가능한 상태의 개수(Cardinality)를 계산해보자
- Product 타입, Sum 타입
• 유효하지 않은 상태를 가질 수 없는 타입을 정의하자
FP 실천사항 #4 – 타입 시스템
54. 조금 단순화된 if else
문들
std::string do_download(const std::string& url) {
auto body = download_fn(url);
if (body) {
auto verified = verify_fn(*body);
if (verified) {
auto decompressed = decompress_fn(*verified);
if (decompressed)
return *decompressed;
}
}
return std::string();
}
55. template <typename A>
std::optional<A> unit_(A a) {
return std::make_optional(a);
}
template <typename F>
std::optional<std::string> bind_(const std::optional<std::string>& v, F func) {
return v ? func(*v) : std::nullopt;
}
Define two functions
• 이 두개의 함수를 이용해서 표준적인 방법으로 입출력
타입이 호환되는 함수들을 합성하자!
56. Using Maybe Monad
void monad_demo() {
auto download_ = [](const std::string& url) {
auto r = download(url);
int status_code = std::get<int>(r);
const std::string& body = std::get<std::string>(r);
return status_code == 200 ? unit_(body) : std::nullopt;
};
auto verify_ = [](const std::string& body) {
bool verified = verify(body);
return verified ? unit_(body) : std::nullopt;
};
auto decompress_ = [](const std::string& body) {
std::string decompressed;
bool success = decompress(body, decompressed);
return success ? unit_(decompressed) : std::nullopt;
};
auto m = [=](auto v) {
return bind_(bind_(bind_(unit_(v), download_), verify_), decompress_);
};
auto result = m(std::string("http://ndc.nexon.com/files/testdata.txt"));
if (result) std::cout << *result << std::endl;
}
57. auto m = [=](auto v) {
return bind_(bind_(bind_(unit_(v), download_), verify_), decompress_);
};
마지막으로 연산자를 오버로딩 하면
• ranges::view 들을 합성하던 것과 같은 방법!
auto m = [=](auto v) {
return v | download_ | verify_ | decompress_;
};
58. Monad
A monad is created by defining a type constructor M and two operations, bind and return
(where return is often also called unit):
• The unary return operation takes a value from a plain type (a) and puts it into a container
using the constructor, creating a monadic value (with type M a).
• The binary bind operation ">>=" takes as its arguments a monadic value with type M a and
a function (a → M b) that can transform the value.
• The bind operator unwraps the plain value with type a embedded in its input monadic
value with type M a, and feeds it to the function.
• The function then creates a new monadic value, with type M b, that can be fed to the next
bind operators composed in the pipeline.
https://en.wikipedia.org/wiki/Monad_(functional_programming)
59. Monad in C++
• Maybe monad using std::optional
• Ranges TS - std::ranges in C++20
• Composable std::future in C++20
60. 요점정리
• 상태가 필수적인 경우가 아니라면 가능한 순수함수를
만들자
• 불변데이터와 관련 알고리즘들을 이해하고 활용하자
• 고차함수를 사용해서 코드를 간결하게 하자
• 알고리즘의 구현과 사용 부분을 코드상에서 분리하자
• 타입을 적극 활용해서 안전한 코드를 만들자
• 합성을 사용해서 코드의 변경을 쉽게 하고 재활용성을
높이자
• 함수형 프로그래밍 언어를 한가지 공부해보자 - Haskell
강추!