Introduction to Programming Languages - Chapter 01 ~ 04

date
Mar 21, 2024
slug
itpl-01
status
Published
tags
PL & Compiler
summary
동아리에서 진행하고 있는 PL 스터디입니다.
type
Post

Chapter 01

프로그래밍 언어란 무엇일까? 가장 심플한 답변은 프로그래밍에 사용되는 언어이다. 그러나 이는 너무나도 당연한 답변이다.
그렇다면 프로그래밍 언어는 무엇으로 구성되어 있을까? syntax(구문론)semantics(의미론)이 있다.
언어를 인간이라고 가정했을 때 구문은 외모, 의미는 생각이라고 할 수 있다.
즉 프로그래머는 syntax 규칙에 맞게 코드를 작성하고, 의미론적으로 해석하여 프로그램을 실행하게 된다. (semantics가 없으면 모든 프로그램은 쓸모가 없다.) → 구문론과 의미론 모두 프로그래밍 언어에서 중요한 관점이다.

표준 라이브러리의 풍부한 지원도 언어의 일부라고 볼 수 있다.

책에서는 아래와 같은 비유를 하였다.
표준 라이브러리가 없는 언어는 옷을 입지 않은 사람이라고 비유하고 있다. → 몸을 따뜻하게 하고 보호하기 위해 인간에게 중요하듯이 표준 라이브러리도 프로그래밍 언어에서 중요하다.
또한 언어마다 제공하는 표준 라이브러리의 기능도 제각각인데 만약 제공하지 않는다면 개발자가 이를 작성하는 데 시간을 써야 한다.

또 다른 중요한 요소는 프로그래밍 언어의 생태계이다.

언어를 사용하는 개발자, 회사, 풍부한 외부 라이브러리 보유에 따라서도 언어의 선호도가 달라질 수 있다.
예를 들어 암호화를 해야 하는데 파이썬에는 pycryptodome이 있지만 다른 언어에서는 없는 경우 개발자가 직접 개발해야 한다. → 타사 라이브러리도 중요하다. 표준 라이브러리는 일반적인 기능만 제공하기 때문이다.
프로그래밍 언어는 정말 많지만 겉모습만 다를 뿐 속은 똑같다. - TIOBE, stackoverflow survey 즉 의미론적인 부분을 이해하고 있다면 새로운 언어를 쉽게 이해하고 배울 수 있다. → 구문과 의미론에 대해 알지 못하는 사람은 구문이 바뀌면 언어를 처음부터 다시 배워야 한다고 생각한다. 반면에 구문과 의미를 구별할 수 있는 사람들은 구문이 달라도 의미가 동일하게 유지된다는 것을 알고 있다. → 새로운 구문에 익숙해지기만 하면 언어를 유창하게 사용할 수 있다.

Chapter 02

함수형 프로그래밍이란 무엇인가? 위키피디아: 프로그램의 실행 상태를 업데이트하는 것이 아니라 값을 다른 값에 매핑하는 표현식의 트리 형태이다. 스칼라로 배우는 함수형 프로그래밍: 순수 함수, 부작용이 없는 함수 만을 사용하여 프로그램을 구성하는 방법
두 번째 코드는 첫 번째의 x 변수처럼 상태가 변하는 변수가 없다. x와 y의 값은 결정되었으며 그 결과를 활용하여 조건문을 수행한다.
함수형 프로그래밍에서는 불변하기 때문에 상태가 없으며, 함수는 항상 동일한 작업을 수행하고 동일한 값을 반환한다. 이러한 함수를 순수 함수(pure function)라고 한다.
함수형 프로그래밍에서 불변성(immutability)은 매우 중요한 개념이다. → 불변성으로 인해 신뢰할 수 있는 값이라고 확신할 수 있으며, 병렬 처리에 용이하다.
병렬 쓰레드 처리를 한다고 가정했을 때 여러 쓰레드가 같은 공간에 접근하게 되는데 이때 문제가 발생한다.
→ 동기화 문제를 해결해야 한다. - example
→ 불변하지 않으면 병렬 처리에서 값을 가져왔을 때 변경되었는지 혹은 아닌지 신뢰할 수 없다.
그러나 대규모 프로젝트의 경우 immutable한 코드만 작성하는 것은 종종 비효율적이다. Scala에서는 var, OCaml에서는 ref 키워드를 사용하여 mutable한 코드를 작성할 수 있다.
하지만 저자는 immutable한 코드를 작성하라고 한다. 대부분 어려움 없이 작성할 수 있다고 한다.

Variables

Functions

C++, Java 등 많은 프로그래밍 언어에서는 함수의 반환 값을 지정하기 위해 return 키워드를 사용한다.
그러나 Scala의 함수는 수학의 함수처럼 = 오른쪽에 있는 식의 결과가 반환되는 것이다. 이때 반환 값의 형식과 괄호 뒤의 형식은 같아야 한다.

Conditionals

Lists

후자는 추후에 재귀 함수에서 사용된다.

Tuples

튜플과 리스트의 중요한 차이점이 있는데 튜플은 다른 타입을 가질 수 있지만 리스트는 다른 불가능하다는 것이다. List[Int] 타입의 리스트라면 정수만 가질 수 있다.

Maps

파이썬의 딕셔너리 자료형을 생각하면 된다.

Classes and Objects

case 키워드를 사용하면 Java에서 getter, setter를 만들었던 작업을 생략할 수 있다. 이름은 클래스의 이름, 괄호 안의 이름은 클래스 필드의 이름, 필드의 타입은 지정해야 한다.

Chapter 03

3장은 불변성이 중요하고 가치 있는 지에 대해 설명하고 있다.

Advantages

불변 객체는 시간이 지나도 변하는 상태가 없기 때문에 코드의 결과를 추론하기가 더 수월하다.
복잡한 논리와 정확성이 필요한 대규모 프로그램을 개발한다면 함수형 패러다임을 채택해야 한다.
하지만 함수형 패러다임이 모든 것을 해결해준다고 생각하면 안된다. 예를 들어 함수형 스타일로 알고리즘을 구현하는 것은 일반적으로 비효율적이다. 알고리즘을 구현하기 위해 배열, mutable한 변수, loop 등을 사용하는데 이러한 방법이 함수형 패러다임보다 훨씬 더 효율적이고 빠르다.
프로그램의 목적에 맞는 적절한 프로그래밍 패러다임을 선택하는 것이 좋은 코드를 작성하는 열쇠이다.

Recursion

같은 계산을 여러 번 반복하는 코드는 개발할 때 자주 사용된다. 그러나 불변인 경우 상태가 변경되지 않기 때문에 loop가 종료되지 않게 된다. - 무한루프
하지만 함수형 프로그래밍에서 loop은 쓸모가 없다. loop 대신 재귀 함수를 사용하여 계산하면 된다.
재귀 함수는 수학적 정의를 명확, 직관적으로 설명할 수 있다.
notion image
Scala에서는 첫 번째 요소인 head(머리)와 첫 번째를 제외한 나머지 리스트를 tail(꼬리)로 구분할 수 있다. 위 코드는 lhead에 해당하는 값 + 1을 저장하고, 자기 자신을 호출하는데 tail을 넘겨준다. 즉 tail은 호출할 때마다 하나씩 줄어들기 때문에 결국 빈 리스트는 Nil에 의해 재귀가 끝나게 된다.
square 함수도 inc1과 같은 메커니즘으로 동작하는 코드이다.
위 코드는 특정 조건에 맞을 경우 값을 추가하는 코이다.
마지막으로 리스트에 있는 elemenets의 합과 곱을 계산하는 코드이다.
재귀에는 몇 가지 단점이 있다. Stack Overflow가 발생할 수 있는데, 재귀는 자기 자신을 호출할 때마다 스택에 점점 쌓이기 때문이다. 이러한 문제를 해결하기 위한 방법으로 꼬리 재귀 최적화(Tail Call Optimization)가 있다.
notion image
notion image
notion image

Tail Call Optimization(TCO)

notion image
일반적인 재귀는 계산 스택에 계속 넣으면서 계산을 미루게 되는데, Tail Call은 중간 계산 결과(intermediate)를 매개변수(inter)에 저장하여 스택 문제를 해결할 수 있다.
factorial 계산 결과가 커지게 되면 문제가 발생할 수 있는데 BigInt 타입을 사용하면 된다.
Scala 컴파일러의 최적화는 스택 오버플로우 방지 외에 함수 호출의 오버헤드도 제거한다. 그런데 tail call을 잘못 사용한다면 최적화의 범위를 넘어갈 수 있다.
이때 Scala에서 제공하는 @tailrec 어노테이션을 추가하여 tail call한 코드인지 확인해준다.
어노테이션 사용 여부에 상관 없이 컴파일러는 항상 TCO를 수행하지만 실수를 방지하기 위해서 사용하는게 좋다.

Exercise

TODO

Chapter 04

First-Class Functions

함수형 프로그래밍에서 함수는 1급 함수이다. 이는 argument에 함수 호출을 넣을 수 있다는 것이다. 쉽게 말하면 모든 것을 값으로 취급하여 사용한다는 의미이다.
위 코드를 보면 1급 함수가 무슨 의미인지 감을 잡을 수 있다. g 함수는 argument 타입이 Int, return 타입도 Int인 함수를 요구하고 있다. 이때 g(f)를 호출하면 f 함수가 g함수의 argument로 들어가게 된다. 그런데 g함수에서 h(0) 의 결과를 반환하기 때문에 그대로 f함수의 결과인 0을 받고, g(f) == 0true가 된다.
f 함수는 g 함수를 반환하고 있다. 그래서 f(0)을 했을 때 g 함수에 넣을 argument를 받지 못해서 lambda 표시가 뜬 것이다.
lambda 함수는 익명 함수를 의미한다.

Anonymous Functions

TODO

Closures

TODO

First-Class Functions and Lists

TODO

Exercise

TODO

참고자료

 

© hyuunnn 2024