Inteligence

지식의 양이 지능을 높여주진 않는것같다. 단지 지식의 많으면 지능의 효율을 높여주는 느낌이다. 즉 당연한 이야기지만 같은 양의 지식으로도 도출되는 결과물의 질이 틀리다.

스스로도 A,B,C의 지식으로 A’의 결론을 냈을때 어떻게 A’를 도출했는지 설명하기가 어려울때가 있다. 나중에 A’를 위한 A,B,C를 짜맞추는 형태가 되기 쉽다.

말마따나 결론을 내고 과정을 찾는 방식인데 많은 ,또 직관적이고 아름다운 수식이 이렇게 나왔다.

지능은 추상화 능력이고 , 추상화 능력이 통섭을 이야기한다면 , 지능은 지식을 통섭하는 능력이라고 생각한다.

Advertisements
Inteligence

Functional Programming In Scala . Chapter 1

1. 함수형 프로그램이란?
전제조건 – 프로그램을 순수 함수(pure function)들로만 구축한다는 뜻 (side effect가 없는 함수)

. 변수를수정한다
. 자료 구조를 제자리에서 수정한다
. 객체의 필드를설정한다.
. 예외(exception)를 던지거나 오류를 내면서 실행을 중단한다. 콘솔에 출력하거나사용자의 입력을 읽어들인다.
. 파일에 기록하거나 파일에서 읽어들인다.
. 화면에 그린다

이런 일들을 할수 없는 상태에서 프로그램을 짠다는것.

함수형 프로그램은 , 프로그램을 작성하는 방식에 대한 제약.
표현 가능한 프로그램의 종류에 대한 제약이 아님.

순수함수로만 이루어진 프로그램은 모듈성이 높아지고 , test, 재사용,병렬화,일반화,분석이 쉬워진다.

1.1 FP의 이점 (예제)

class Cafe {
 def buyCoffee(cc : CreditCard) : Coffee = { #buyCoffee는 함수명 , cc는 인수 
 val cup = new Coffee()
 cc.charge(cup.price) #부수효과가 발생하는 코드 (신용카드를 실제로 청구)
 cup #return
 }
}

– 함수의 목적은 Coffee를 되돌려주는 작업을 함.
– 그외의 모든 동작(사양)은 부수적으로 일어남 (on the side)
– test가 힘든 상황. (실제 대금을 청구해야하기때문)

개선1

class Cafe {
 def buyCoffee(cc : CreditCard , p : Payments) : Coffee = {
 val cup = new Coffee()
 p.charge(cc, cup.price)
 cup
 }
}

– 테스트가 힘들었던 결제(대금을 청구하는)로직을 변수로 받아오도록 수정
문제
– 커피가 생각했던 가격으로 결제가 되었는지 확인하려면? ※즉 외부환경의 변이를 체크해야함
– 만약 한 손님이 커피를 동시에 12잔을 신청했다면? 루프를 통해 12번 buyCoffee를 호출, 결제또한 12번 호출이 됨. 카드수수료도 12배.

해결방법
– buyCoffes 라는 새로운 함수를 작성
– 하지만 결제코드가 복잡해진다면 이런 방식의 처리는 한계가 있고, 코드자체의 재사용성에도 좋지 않음.

1.1.2 함수적 해법 : 부수 효과의 제거

개선2

class Cafe {
 def buyCoffee(cc : CreditCard) : (Coffee , Charge) = { #buyCoffee는 Coffee와 Charge의 pair를 돌려준다. 이제 결제와 이 함수와는 무관해짐
 val cup = new Coffee()
 (cup , Charge(cc, cup.price))
 }
}

– buyCoffee함수에서 청구(구매요청)을 ‘처리’‘연동’의 문제로 분리되었음.

case class Charge (cc : CreditCard , amount : Double) { #case class는 자료구조 (swift의 struct)
 def combine(other : Charge) : Charge = 
 if (cc = other.cc )
 Charge(cc , amount + other.amount) #case class 는 new 키워드 없이 생성가능.
 else
 throw new Exception(“Can’t combine charge to different cards”)
}

– Charge는 CreditCard와 amount 변수를 가지고 있는 자료형
– combine란 함수를 통해 CreditCard가 동일 할 경우에 청구를 취합할수가 있음

개선3

class Cafe {
 def buyCoffee (cc : CreditCard) : (Coffee, Charge) = … #생략
 def buyCoffees (cc : CreditCard , n : Int ) : (List[Coffee],Charge) = {
 val purchases: List[(Coffee , Charge)] = List.fill(n)(buyCoffee(cc)) #List.fill(n)(x)는 x의 복사본 n개로 이루어진 List를 생성
 val (coffees , charges) = purchases.unzip (coffees,charges.reduce((c1,c2) => c1.combine(c2))) 
 #unzip은 pair의 List를 List의 pair로 분리
 #charge.reduce는 청구(Charge) 2건을 combine을 이용해서 하나로 결합하는 과정을 반복. 최종적으로 하나의 청구건으로 환원(reduce)
 }
}

– buyCoffee를 재활용해서 buyCoffees함수를 정의할수가 있음.
– Payments 인터페이스를 구현하지 않아도 쉽게 테스트가 가능
– Cafe는 Charge가 어떻게 결제되는지 모름. 또는 알 필요가 없음

케이스
– 커피숍에서 몇시간 일하면서 음료수를 여러잔 주문했을때 , Charge가 first-class이기 때문에 쉽게 재활용이 가능해짐. (함수를 인수로 주고 받음)

def coalesce (charges : List[Charge]) : List[Charge] = charges.groupBy(_.cc).values.map(_.reduce(_ combine _)).toList

– 같은 CreditCard의 청구를 하나의 List[Charge]로 취합해준다.
– _.cc 와 _ combine _는 익명 함수(anonymous function)를 위한 구문

– 처음엔 읽기 어려울수 있지만 , 적응되면 쉽게 읽을수 있음.
– 재활용 및 테스트가 쉽다.

– 위와 같은 기능을 처음의 코드로 구현한다고한다면?

– FP는 좋다고 생각하는 기능을 논리의 극한까지 밀어붙이는 , 또 그 기능의 적용이 어려워 보이는 상황에서도 적용하는 규율
– FP의 스타일은 다른 프로그래밍의 방법과 상당히 다르지만 , 아름답고 응집력있는 프로그래밍의 접근방식

Etc
– 실제 세계에서 위와 같이 부수효과가없는 코드를 작성할수가 있을까?

– 일반적으로 부수효과가 있는 임의의 함수에 이런 종류의 변환을 적용함으로서 부수효과를 프로그램의 외부계층으로 밀어낼수가 있다!

– 언젠가는 외부에 실제로 효과를 가해야한다. 예를들면 Charge를 외부의 시스템에 의해 처리되게 만들 필요가 있다.
– 하지만 부수효과가 필요한 프로그램에도 함수적 코드로서 짜는 방식이 가능하다.
– 즉 부수효과가 발생하지만 ‘관찰되지 않도록’ 코드의 구조를 짜는 방법은 찾는것이 가능하다.
– 예를들면 함수에서 지역적으로 선언된 자료가 함수외부에서 참조되지 않는다는것을 보장할수있다면 , 변이가 가능
– 함수를 감싸는 다른 함수가 알아채지 못한다면, 함수 안에서 화일에 무엇인가를 기록할 수 있다.

Functional Programming In Scala . Chapter 1

함수형 프로그래밍 단상

함수형 프로그래밍의 핵심 또한 관심사의 분리라고 생각했다.

기존 객체지향에서는 다형성으로 “대상”과 “행동”을 분리해왔고
AOP(Aspect-oriented programming)에서는 “행동”과 “언제”를 분리했다.

함수형 프로그래밍에서는 입력값에서 “계산”로직과 “처리”를 분리했는데,
“처리”가 부수효과(부작용)를 만들기 때문 (처리의 전후에 따라 변수의 내용이 바뀌기때문) ,
계산로직을 분리해서함수를 통해 독립성 ,모듈성을 높이고 있다.

때문에 개념상 객체지향과 상충될 이유는 없다고 보지만 일부 함수합성같은 기능들은 first class가
아니면 안되는 부분이 많으니 함수의 장점을 100%활용할수는 없다고 본다

함수형 프로그래밍 단상

Functional Programming

이 글은 , “함수 프로그래밍 입문” 이란 책의 0장을 읽고 정리한 글입니다.
関数プログラミング実践入門 ──簡潔で、正しいコードを書くために

0. [입문] 함수 프로그래밍

 

함수 프로그래밍에서 얻을수 있는 개선

코드 양이 적어진다

같은 기능을 가진 프로그램을 구현한다고할때 , 적은 행수로도 끝낼수 있다.
이것은 함수프로그래밍에서 “수학”의 개념을 적용 했기 때문 (수학은 고도로 추상화된 분야)
코드 양이 적다는것은 메인터넌스성이 높다.

최적화하기 쉽다

최적화는 많은 프로그래머가 노력을 들이는 부분 , 또 많은 부분을 컴파일러에서 자동으로 해줌
컴파일러가 최적화를 위해 사용할수있는 유용한 특징을 가지고 있을수록 좋음.
함수프로그래밍의 추상화 능력이 컴파일러의 최적화에 도움을 준다.

병행/병렬화하기 쉽다

CPU의 진화의 방향은 기존 동작주파수를 높이는 것에서 코어를 늘리는 것으로 변화됨
CPU가 빨라지면 프로그램도 빨라지던 과거와는 달리 , 병행/병렬화된 프로그램이 아니면 새로운 CPU라도 빨라진 효과를 느끼기 어려워짐.
함수타입 프로그래밍은 병행/병렬화 하기 쉽기때문에 많은 효과를 얻을수 있다.

버그를 내기 어렵다

일부의 함수타입언어에서는 제약조건을 ‘타입’로 표현하고 , 그 제약조건을 지키고있는지 컴파일시간등에 조사하는것이 가능.
이러한 제약조건으로 메인터넌스,가독성이 높은 코드를 쓰는 것이 가능

문서화가 적어진다

위의 높은 제약조건이 컴파일시등에 검사되기 때문에 일부러 제약조건에 대해서 문서화를 하지않아도 됨
문서화 하지 않아도 되기때문에 코드를 작성할 시간을 확보할수 있다 .
또 문서를 통해 제약을 거는것보다는 코드를 통해 제약을 거는 편이 효율적

함수란 무엇인가?


//C++
// say.cpp
//문자열을 표준출력
void say(const std::string& something) {
     std::count << something << std::endl; // 외부와의 입출력을 행하고 있음
}

위의 코드는 함수타입 코드가 아님
외부에 문자를 출력하는 행위가 외부의 환경에 의존하기 때문에 항상 같은 결과가 나온다고 말할수 없기 때문.


# Ruby
# current.rb
# 현재시간을 취득
def current
 Time.now #부여된 입력값이외로부터 출력값이 정해짐
end

위의 코드에서 current는 외부로부터 시각을 취득.
current는 인수가 없기 때문에 입력으로부터 출력이 정해지는 함수적의 의미에서는 항상 같은 값이 된다고 말할수없음.
실제로 current를 실행할때마다 매번 다른 결과가 출력됨


//C++
// show.cpp
// 정수값n을 문자열로 교환한다
// show(1234) => “1234"
std::string show(const int n ) {
  std:ostringstream oss;
  oss <<n;
  return oss.str();
}


//Ruby total.rb
//numbers 의 값을 전부 더한다
total ([1,2,3]) => 6
def total(numbers) numbers.inject(:+) end

위와 같은 코드는 같은 값을 입력하면 항상 같은 값을 취득할수 있기때문에 함수타입 언어라고 말할수 있음.

부작용

프로그래밍에서 상태의 참조 또는 상태에 변화를 주는 것으로 이후의 결과까지 영향을 주는 효과를 부작용이라고 부름.

  • assign
    • 변수의 상태가 참조하는 타이밍에 따라 변하기 때문.
  • 입출력
    • 화일에 쓰기 또는 읽기
    • 물리디스크로부터 읽는 행위는 물리적 동작을 발생하기하고
    • 그 동작에따라 물리디스크가 파손되었을경우 , 다음의 읽기에 영향이 있음

부작용을 가진 프로세스는 함수가 아님.
함수 프로그래밍에 관해 있어서 대상으로하는 처리내용에 부작용이 있는지 없는지를 구분하는것은 중요.

B84C8D42-3DAB-4B2C-8647-748AE0DA8BA5

함수 프로그래밍이란 무엇인가?

프로그래밍의 패러다임

명령타입 프로그래밍의 패러다임

  • 프로그래밍은 계산기가 행해야하는 명령의 열

오브젝트 지향 프로그래밍의 패러다임

  • 프로그램은 오브젝트와 메세징 , 프로그래밍은 문제에서 어떤 오브젝트가 있는지 분류해 정의
  • 오브젝트를 생성/관리하고 오브젝트끼리의 메세지를 주고받음으로 문제를 해결하고, 기능을 구현

함수프로그래밍의 패러다임

“프로그램이란 함수이다.”라고 보는 방법.
큰 프로그램은 작은 프로그램의 조합으로서 만들어진다.
(프로그램을 조합하는것은 함수합성(function composition)이라고 함.)

f(x), g(x) -> h(x) = f(g(x))

조합하기 쉬운 , 부품화하기 쉬운 독립성을 modularity라고 하고 함수와 함수합성이 가진 성질은 높은 modularity를 보장.

함수타입언어란것은?

함수타입언어의 조건

  • 함수가 제일급의 대상이 된다. (first class)

함수가 언어에서 단순한 값과 같이 아래와 같은 취급을 해야함.

일급대상이란?(first class)

  • 함수의 리터럴이 있음
    • 코드에 하드코딩된 숫자나 문자열처럼 , 함수의 표현이 존재
  • 함수를 실행시에 생성
    • 함수합성의 의해서 생성이 가능, 부분적용,고차적용 등에 의해서 작성됨
  • 변수에 assign이 가능
  • 절차와 함수에 인수로서 부여가 가능
  • 절차와 함수의 결과로서 돌려주는것이 가능

함수타입언어와 명령타입언어

명령타입언어

어떤 결과를 내기위해서 CPU또는 처리계의 낮은 레벨의 동작을 드러나도록 코드를 작성할 필요가 있다.

  • 값을 어딘가 저장할수있는가(assign)
  • 어디로부터 값을 가져올수 있는가(참조)
  • 다음으로 어떤 절차를 넘길수있는가(철자의 호출)

기본적으로 변수는 어떤 값을 저장하는 상자의 개념
이 상자에 값을 넣는 횟수에 어떤 제약이 있지는 않다.
한번 assign한 변수에 다른 값을 넣는 “파괴적 assign 조작”이 가능.

함수타입언어

함수타입언어에서는 assign자체가 없거나 , 한정되어 있어서 기본적으로 한번 변수의 값을 정하면 변경할수 없는 속박(binding)이 되어버림.

  • 값이 항상 들어있다
  • 값이 변경되지 않는다

assign이 없이 속박이 되어버림으로서 얻을수있는것은

  • null check같은 본질적이지 않은 처리를 할 필요가 없어진다.
  • 코드의 가독성증가
  • assign 코드의 최적화가 쉬워짐

함수타입언어의 특징적 기능

타입과 타입없음 (typed , untyped)

타입없음의 경우 값과, 계산결과를 특별히 구별하지 않기때문에 ,계산결과가 어떤타입이 될지는 예상할수가 없다.
전부 인간의 일이 되어버림. (이 부분을 type system으로 맡기느냐 아니냐의 차이)

정적타입 , 동적타입

컴파일타임에 타입을 붙이는가 , 실행시에 타입검사를 해서 붙이는가에따라 구별

순수
같은 식은 언제 평가를 하더라도 같은 결과가 되는 것 , 참조 투과성(referential transparency)이라 불림

타입검사

타입검사(type checking)란 타입의 정합성을 컴파일시등에 검사하는 기능. 검사에 실패할경우 에러를 표시
강한 타입, 약한 타입

안전성 – 언어사양에 정의되지 않은 동작을 일으키지않는것 (ex – c언어의 메모리침범)
타입검사후에 안정성을 확보할수 있는것을 강한 타입(strong typing) , 그렇지 않은 것을 (weak typing)
일반적으로 정적타입은 강한 타입인 경우가 많다.

타입추론

프로그래머가 부여한 타입의 정보로부터 부여되지않은 타입을 추론하는 기능.
(ex- 숫자와 문자열의 합성은 문자열로서 판단될수있음.)
의존타입(dependent type)

다른 타입에 의존한 타입 또는 값에 의존해 타입을 만드는 기능
(ex – Dictionary<String,Number>)

평가 전략

어떤순서로 식을 평가하는가,
적극평가와 지연평가가 존재.
적극평가(eager evaluation) -> 인수가 전달되는 때에 평가 (계산할수 있는것은 전부 미리 계산)
지연평가(lazy evaluation) -> 실제 필요할때까지 평가를 하지 않는 것


int tarai (int x , int y , int z) {
return (x <= y) ? y :
(tarai (tarai(x-1, y,z),
tarai(y-1,x,z),
tarai(z-1,x,y)));
}

적극평가의 경우 x가 y보다 작을경우에 필요하지 않은 z의 결과까지 계산하기때문에 느려짐.
이 경우는 특별한 경우로서, 대부분의 경우 적극평가의 경우가 빠른경우가 압도적으로 많다.

42364961-020C-4028-B366-58D3935E79B2

왜 지금에서 함수타입언어인가?

추상화

보통 추상화라고 한다면

  • 많은문제에 대해서 범용적인 적용이 가능
  • 추상화후의 세계에 조작이 풍부하다

함수타입 언어로 추상화를 진행할때 명확하게 좋다고 느끼는 부분은 집합론(群論,group theory),권론(圏論,category theory) 라고하는 수학 쪽의 추상화

수학의 역사는 컴퓨터의 역사보다도 긴 동안에 추상화의 과정을 거쳐왔음.
때문에 수학의 수학의 성과를 그대로 적용할수 있다면 보다 높은 추상화를 얻을수있다.

함수타입언어의 최적화

언어에서 최적화는 중요한 요소, 컴파일러에게 맡길수 있는 최적화의 여지가 클수록 개발자가 편해질가능성이 높음.


//C (c99)
int totalFrom1To(int n) {
int result = 0;
for (int i = 1 ; i <= n; result += i++);
return result;
}

1부터 n까지 더하는 함수


— Haskell
totalFrom1To = sum.enumFromTo 1
(enumFromTo 1 리스트를 만들어서  sum을 계산함)

일반적으로 c언어가 더 빠르지만,
구조를 만드는 함수와, 구조의 접는(합치는) 함수를 합성할 경우 중간구조를 만들 필요가 없다는 수학의 성과가 존재.
를 이용해서 아래와 같은 C언와 같은 수준의 최적화가 가능.


— Haskell
totalFrom1To = auxTotalFrom1To 0 where
auxTotalFrom1To result 0 = result
auxTotalFrom1To result n = auxTotalFrom1To(result + n)(n-1)

[1부터 n까지 리스트를 작성]
[작성한 리스트의 값을 전부 더함]

의 코드보다는

[result에 더해지는 수i를 1부터 증가시켜 n까지 더함]

쪽이 부품화되어있음. 또 동작및 확인이 간단해짐 (보충하면 프로그램의 정확성측면에서 두개의 기능을 가진 부품 보단 ,하나의 기능을 가진 부품으로서의 결과가 더 정확하다는 말)

함수타입 언어가 수학적으로 추상화하기가 쉽기 때문에 ,다른 언어의 일반적 최적화와 더불어 수학적 추상화를 이용해 최적화 단계를 적용하는 경우가 있다.

C47A29B7-91B0-4EB3-8E5B-BAA167A4010E

명령타입 언어쪽이 cpu에 보다 가까운 추상화도가 낮은 위치에 있기때문에 , 단순히 명령타입언어와 비교할경우 실행속도로 함수 타입 언어에 불리한 점이 있는것이 사실

  • 명령타입 언어에서 최적화를 넣어도 원하는 성능이 나오지 않을경우 , 더 이상 튜닝하기가 어렵지만,
  • 함수타입언어는 추상도가 높기 때문에 연구에 의해 최적화를 더할 여지가 크다고 말할 수 있다.

함수타입언어와 병행/병렬 프로그래밍

매년 cpu의 코어는 증가 , 주파수의 증가는 더이상 기대하기가 힘들다.

병행(concurrent)/병렬(parallel)의 개념과 프로그래밍의 어려움

  • 전 프로그램안에서 동작하는 병행 실행의 코드를 작성하는 경우 , 각각의 처리가 뒤섞여서 비대해지는 경우가 많다.
  • 재현성이 낮은 버그가 나오는 경우도 많다.

병행(concurrent)프로그래밍은 복수의 task가 동시에 실행될 필요가 있을때 유용.

78167C02-A734-4343-97FE-803F5851E5B2

  • 병렬(parallel)프로그래밍은 각 코어에서 실행하더라도 같은 결과가 나올수 있는경우에 유용. (단일task의 고속화)

병행 프로그래밍의 어려움

병행프로그래밍 = multithreaded Programming

문제점 -> 복수의 thread가 동일한 resource를 같은 시간에 엑세스 할 경우

  • resource가 기대하지 않은 상태가 되어있는 것 (race condition)
  • 보통 mutex라 불리는 배타제어를 할 필요가 있음.

하지만 이 lock를 이용한 제어에는 문제가 많아서

  • lock을 해야할 장소(사양,설계,코드)에 lock을 잊어버렸다.
  • lock한뒤에 unlock을 잊어버렸다.
  • lock할 필요가 없는/또는 있었지만 필요없어진 리소스에 대해 lock을 해서 퍼포먼스가 낮음
  • lock을 취득한 범위가 쓸때없이 크기때문에 퍼포먼스가 낮음
  • 스레드 2개가 서로 lock한 리소스를 기다리고 있음(deadlock)

deadlock의 조건

  1. lock을 해야할 2개 이상의 리소스가 이미 존재
  2. 1개이상의 리소스를 lock한채로 별도의 리소스의 lock을 요구하고 기다리고 있다
  3. 이미 lock된 리소스를 취득 할 수가 없다.
  4. 리소스의 lock을 취득하는 순서가정해져있지 않다.

멀티스레드 프로그래밍에선 첫 3개의 조건을 깨는 것은 정말 어렵다

  • 일반적으로 최후의 조건을 제거하기 위해 , 리소스의 lock순서를 정해서 대처.
  • 위의 방법은 인간의 주의력에 의존하는 방법으로 , 전체 프로그램안에서 리소스와 lock의 순서,
  • 그리고 그것을 문서화하지 않으면 관리가 어려움.

병렬 프로그래밍의 도움 – 참조투명성의 보증

함수타입 언어의 특징

  • 파괴적 assign 및 조작이 불가능
  • 순수함수타입언어에선 언제 함수를 평가하더라도 같은 결과 -> 병렬화 하기가 쉽다
    • (다른 언어에서도 참조투명성을 보장한다면 병렬화해도 문제는 없다.단지 주의력의 문제)

틀리지않고 작성할수 있다면 , 그리고 틀리지않고 메인터넌스가 가능하다면
현대의 계산기 아키텍쳐에서는 명령타입 언어쪽이 빠른경우가 많다.
하지만 계속 업데이트를 해야하고 메인터넌스를 진행해야하는 프로그램의 경우 ,
병렬처리에 대한 언어적 지원이 있다고한다면 개발자에게 도움이 됨.

함수타입 언어의 미래

명령타입언어의 진화

  • 구조화프로그래밍의 도입 (1968년 dijkstra의 goto의 위험성에 대한 논문)
  • 캡슐화이 도입 (객체지향에 의해 내부의 상태를 숨김)

언어의 진화를 보면 , 강한 제약을 주는 것으로 편리하고 안전한 어프로치가 가능해졌음.

이 제약은 두가지로

  • 언어기능으로서 주어진 제약 (let , var 등의 참조투명성을 지켜야하는 언어적제약)
  • 프로그래머가 줄수있는 제약 (const , interface , protocol등의 개발자가 걸어둔 제약)

최신의 언어는 점점 언어적 제약을 늘려가는 추세

함수타입 언어를 채용하는 메리트

  • 언어적지원의메리트
    • 본질적이지 않은 사소한 것으로부터의 해방 (ex – 평균함수)
  • 제약의 충족을 체크해주는 메리트
    • 참조투명성같은 언어적제약
  • 타입검사가 있어서 생기는 메리트
  • 타입추론의 메리트
Functional Programming