프로그래밍언어개론 - 1. Introduction to OCaml (3)

ensalada.de.pollo·2024년 3월 27일
0

ProgrammingLanguages

목록 보기
4/4

Statement vs. Expression

프로그래밍 언어에서 statement와 expression은 서로 다른 용도로 사용됩니다.
이 둘의 차이를 구분할 수 있는 지식이 조금 필요하겠습니다.

Statement

: 실행 시 프로그램의 상태전이를 수행하는 언어의 구성요소를 의미합니다.

  • 상태전이란?: 메모리 상태를 변경하는 행위를 의미합니다.
  • Statement는 값을 반환하지 않습니다. (값으로 계산되지 않음)
#include <iostream>

int main() {
  int a = 3; // 변수 a에 3이라는 값을 넣는 작업입니다.
  // 근데 이 작업은 딱히 값을 반환하지는 않습니다.
}

Expression

: 실행 시 값으로 계산되는 언어의 구성요소를 의미합니다.

  • expression의 실행 결과는 항상 "값"을 도출합니다.
  • 상태전이를 수행하지 않습니다.

정리를 하자면 아래와 같습니다.

StatementExpression
상태 전이OX
값의 반환XO

언어에 따라서 statement와 expression이 명확하게 분리되지 않는 경우도 존재하기도 합니다.

  1. Statement와 Expression이 서로 변환이 되기도 하고
  2. Expression이 상태전이를 일으키기도 합니다. (e.g. function call)

Pure Functional Language

순수 함수형 언어(Pure Functional Language)는 오직 expression으로만 구성이 됩니다.

  • 상태전이를 수행하는 statement가 존재하지 않습니다.
    • 상태전이가 일어나지 않기 때문에 Expression 실행 전과 실행 후의 메모리 상태가 동일합니다 (side-effect가 없다고 할 수 있습니다 (immutability))
    • 모든 언어의 구성요소가 "값"으로 계산이 됩니다.
  • 이와 별개로 module 선언 및 value, type, exception에 이름을 붙일 수 있는 definition이 존재합니다.

Q. 여기서 side-effect란 무엇인가요?
A. 프로그래밍 언어에서 side-effect라는 것은,
변수의 값을 바꾸는 것, 저장 장치에 쓰기(write)를 하는 것
과 같은 요소들을 의미합니다.

How about OCaml?

Ocaml은 pure functional language는 아닙니다!!

  • 다소 제한된 형태로 메모리 상태를 변경할 수 있기 때문입니다.
    • 때때로 이러한 side-effect가 유용하기도 하지만,
    • 지금 단계에서는 pure functinoal language처럼 사용할 것입니다!

따라서 statement와 expression을 구분하는 것이 중요합니다.

Comment

OCaml은 오직 multiline comment만을 지원합니다.

  • (* ... *)와 같은 형태로 ...의 내용을 주석화합니다.
  • C/C++또는 Java의 multiline comment(/* ... */)와 동일하게 볼 수 있습니다.

Standard I/O

OCaml에는 다양한 입출력 방식이 존재하지만, 간단한 형태의 입출력은 다음을 활용합니다.

  • Standard Input: OCaml의 built-in 함수 read_line
    • 사용자 입력을 문자열로 반환합니다. (linefeed 제외)
  • Standard Output: OCaml의 built-in module Format
    • Pretty-printing과 관련된 다양한 형태의 함수를 지원합니다.
    • printf: formatted string을 출력합니다 (C, C++에서의 printf와 같은 것이죠.)

Q1. 위 코드에서 print_flush를 사용하는 이유는 무엇인가요?
A1. 입력 코드를 받기 전에 출력문이 표시되지 않을 수도 있기 때문에, 이를 방지하기 위해서 flush 함수를 호출해 출력 버퍼를 먼저 비웁니다!

Q2. 위 코드에서 print_flush와 read_line 뒤에 왜 "()"가 붙나요?
A2. OCaml에서는 함수를 "함수명 인자1 인자2 ..."와 같은 형태로 호출합니다.
이 때, printf 함수는 인자(사용자가 출력하고자하는 내용)를 필요로 하여 "Input: "이라는 인자를 적어주었습니다.
그에 반해 print_flush와 read_line은 인자를 필요로 하지 않습니다. 함수를 호출하려면 인자를 적어야하지만, 인자를 필요로 하지 않기 때문에 앞서 배웠던 "아무 것도 없음"을 의미하는 unit type의 "()"를 인자로 적어주는 것입니다!!

Variable

OCaml에서 변수라는 것은, 특정 값에 묶인 식별자를 의미합니다.
무슨 말인지 감이 잘 오지 않을 수 있습니다.

C, C++, Java에서 변수는 값을 넣는 공간을 의미합니다.
위 언어들에서 int a = 3; 이라는 구문이 있다고 해봅시다. 이 구문은 a라는 변수가 가리키는 메모리 공간에 3이라는 값을 집어넣음을 의미합니다.

OCaml에서 변수는 조금 다릅니다.
let a = 3 이라는 구문이 있다면, 여기에서 a는 메모리 공간을 가리키는 것이 아닙니다. 3이라는 값에 묶여있는 형태지요. 값으로 치환이 된다고 생각을 하면 될 것 같습니다.
그래서 한 번 묶인 변수의 값은 변경이 되지 않습니다.

이렇게 보니 위에서 언급한 expression과 statement의 차이가 가슴에 와닿네요!

Q. 한 번 묶인 변수의 값은 변경이 되지 않는다고 했는데, 그럼 다른 값이 필요할 때 그 변수는 버려야 되나요?
A. 그건 아닙니다. 조금 밑에서 알아보도록 하겠습니다.

let-in expression

Ocaml에서 변수에 값을 묶는 구문입니다.

  • let: 변수를 선언하고 값을 묶는 역할입니다.
  • in: 변수의 사용 범위를 정의합니다.

예를 들어서,

int main() {
  int x = 3;
  printf("x: %d\n", x);
  return 0;
}

이런 C 코드가 있을 때, 변수 x의 사용 범위는 선언부터 main함수 return까지가 될 것입니다.

let x = 3 in
Format.printf "x: %d\n" x

Ocaml에서는 in으로 이어진 구문이 변수의 사용 범위가 될 것입니다.

immutable

변수에 대한 설명을 할 때 OCaml에서 모든 변수는 값의 변경이 불가능하다고 했습니다. 치환의 개념이기 때문에, 한 번 묶인 변수에 다른 값을 묶을 수 없죠.

C, C++, Java에서 '=' 연산자는 변수에 값을 할당하는 연산자입니다.
OCaml에서는 'IsEqualsTo'라는 비교 연산자의 역할을 합니다.

let-in에서는 변수를 선언하고 '=' 연산자를 활용하면 바인딩을 수행합니다.

그럼 같은 이름의 변수를 여러 번 선언하고 값을 묶어버리면 오류가 발생하는 걸까요?
오류는 발생하지 않습니다.

let x = 3 in
let x = x + x in
x

여기서, 처음 선언했던 x와 다음에 선언했던 x는 같은 x가 아닙니다. 같은 이름의 변수가 하나 더 생긴다고 이해하면 될 것 같습니다.

그럼 변수의 접근에 문제가 생길 수 있겠죠? 하지만, 이를 방지하기 위해 같은 이름으로 여러 번 선언한 변수 중 가장 최근에 선언한 변수에만 접근할 수 있도록 만들어 놓았습니다.

변수 음영(variable shadowing): 동일 scope내에 가장 최근(inner most)에 선언된 변수가 같은 이름의 다른 변수를 가리는 것

Global let binding

Global let Binding이란 "값"에 이름을 붙이는 definition입니다.

기본형: let [variable] = [expression]

  • Right-hand side expr를 계산하고
  • Expr 계산 결과 값을 left-hand side var에 바인딩 합니다.
  • 이후 var이 사용되면 해당 var을 바인딩 된 값으로 치환하여 계산합니다.
  • 이 친구는 expression이 아니라 값으로 계산되지 않습니다.

이름있는 함수를 정의하는 등 다른 형태의 활용도도 존재합니다.

Wildcard

OCaml에도 wildcard가 존재합니다. wildcard는 보통 단순히 expression만 실행하고 싶을 때 사용합니다.
wildecard: "anything"이라는 의미로, left-hand side variable에 위치하면 계산 결과를 바인딩하지 않습니다.

기본형: let _ = [expression]

  • Right-hand side expression을 계산합니다.
  • 일반적인 let binding과 다르게 계산 결과를 바인딩하지 않도 버립니다.

Q. 그냥 변수 하나 놓으면 되지 왜 굳이 와일드 카드를 사용하는 건가요?
A. OCaml에서는 사용하지 않는 값을 변수에 바인딩하는 것을 금지합니다. 이러한 이유로 단순한 expression을 실행할 때에는 wildcard를 사용하는 것입니다.
(e.g. let _ = Format.printf "Hello World")

Local let-in binding

위에서 다룬 let-in expression의 추가적인 내용을 다룰 예정입니다.
Local let-in binding은 "값"에 이름을 붙인 후 세부 expression을 수행하기 위한 expression입니다.

기본형: let [variable] = [expression1] in [expression2]

  • Expression1을 계산합니다.
  • Expression1의 계산 결과 값을 left-hand side variable에 바인딩 합니다.
  • Expression2를 계산합니다.
  • Expression2의 계산 결과 값을 반환합니다.
  • Expression2를 계산할 때 variable이 사용되면 해당 variable을 expression1의 결과로 바인딩 된 값으로 치환하여 계산합니다.
  • 그 자체로 하나의 expression이므로 값으로 계산이 됩니다.

모듈 개방 등의 다른 형태의 활용도도 존재합니다.

0개의 댓글