Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

명령형 에르고노믹스 (Imperative Ergonomics)

FunLang는 명령형 스타일 코드를 더 자연스럽게 작성할 수 있는 네 가지 문법 기능을 제공합니다. 이 장에서는 표현식 시퀀싱, 루프, 인덱싱 문법, else 없는 if 표현식을 살펴봅니다.

표현식 시퀀싱 (Expression Sequencing)

함수형 언어에서 여러 부수 효과(side effect)를 순서대로 실행하려면 전통적으로 let _ = e1 in e2 패턴을 사용했습니다. FunLang에서는 ; 연산자로 이를 간결하게 쓸 수 있습니다.

e1; e2e1을 평가한 뒤 그 결과를 버리고, e2를 평가하여 그 값을 반환합니다.

$ cat seq_basic.l3
let _ = println "hello"; println "world"

$ fn seq_basic.l3
hello
world
()

여러 단계를 체이닝할 수도 있습니다. 아래는 가변 변수와 결합하여 카운터를 증가시키는 예입니다:

$ cat seq_chain.l3
let result = let mut x = 0 in x <- 1; x <- x + 1; x <- x + 1; x

$ fn seq_chain.l3
3

함수 본체 안에서도 동일하게 사용할 수 있습니다:

$ cat seq_block.l3
let f () =
    println "a"; println "b"; println "c"
let result = f ()

$ fn seq_block.l3
a
b
c
()

;은 오른쪽 결합(right-associative)이므로 e1; e2; e3e1; (e2; e3)로 파싱됩니다. 최종 반환값은 마지막 표현식의 값입니다.

루프 (Loops)

while 루프

while cond do body 형태의 루프는 조건(cond)이 참인 동안 본체(body)를 반복 실행합니다. 루프 전체의 반환값은 unit입니다.

$ cat while_basic.l3
let mut i = 0
let _ = while i < 3 do i <- i + 1
let _ = println (to_string i)

$ fn while_basic.l3
3
()

루프 본체에서 여러 문장을 실행하려면 ;으로 연결합니다. 들여쓰기 블록 안에서 ;을 사용하면 됩니다:

$ cat while_body.l3
let mut count = 0
let mut sum = 0
let _ =
    while count < 4 do
        sum <- sum + count; count <- count + 1
let _ = println (to_string sum)

$ fn while_body.l3
6
()

루프가 끝날 때 count는 4, sum은 0+1+2+3 = 6이 됩니다.

for 루프

for i = start to end do bodyistart부터 end까지 1씩 증가시키며 반복합니다. downto를 쓰면 1씩 감소합니다.

오름차순 (to):

$ cat for_asc.l3
let mut total = 0
let _ =
    for i = 0 to 3 do
        total <- total + i
let _ = println (to_string total)

$ fn for_asc.l3
6
()

i가 0, 1, 2, 3 순서로 실행되며, total은 0+1+2+3 = 6이 됩니다.

내림차순 (downto):

$ cat for_desc.l3
let mut total = 0
let _ =
    for i = 3 downto 0 do
        total <- total + i
let _ = println (to_string total)

$ fn for_desc.l3
6
()

to는 오름차순(start ≤ end), downto는 내림차순(start ≥ end)으로 반복합니다. start > end인 경우(to) 또는 start < end인 경우(downto) 루프 본체는 한 번도 실행되지 않고 unit을 반환합니다.

루프 변수의 불변성

for 루프 변수(i)는 불변입니다. 루프 본체 안에서 대입을 시도하면 E0320 에러가 발생합니다:

$ cat for_err.l3
let _ =
    for i = 0 to 9 do
        i <- 42

$ fn for_err.l3
error[E0320]: Cannot assign to immutable variable 'i'. ...
 --> for_err.l3:3:8-15
    |
  3 |         i <- 42
    |         ^^^^^^^
   = hint: Declare the variable with 'let mut' to allow assignment

루프 카운터를 직접 수정할 필요가 있다면 별도의 let mut 변수를 사용하세요.

인덱싱 문법 (Indexing Syntax)

arr.[i] 형태의 인덱싱 문법으로 배열과 해시테이블에 더 직관적으로 접근할 수 있습니다.

배열 인덱싱

arr.[i]로 읽고, arr.[i] <- v로 씁니다. 기존의 array_get/array_set 함수보다 간결한 문법입니다.

읽기 (IndexGet):

$ cat arr_index_read.l3
let arr = array_create 3 0
let _ = array_set arr 0 10
let _ = array_set arr 1 20
let _ = array_set arr 2 30
let _ = println (to_string arr.[0])
let _ = println (to_string arr.[1])
let _ = println (to_string arr.[2])

$ fn arr_index_read.l3
10
20
30
()

쓰기 (IndexSet):

$ cat arr_index_write.l3
let arr = array_create 3 0
let _ = arr.[0] <- 42
let _ = arr.[1] <- 99
let _ = println (to_string arr.[0])
let _ = println (to_string arr.[1])

$ fn arr_index_write.l3
42
99
()

해시테이블 인덱싱

ht.[key]로 읽고, ht.[key] <- v로 씁니다.

읽기:

$ cat ht_index_read.l3
let ht = hashtable_create ()
let _ = hashtable_set ht "x" 100
let _ = hashtable_set ht "y" 200
let _ = println (to_string ht.["x"])
let _ = println (to_string ht.["y"])

$ fn ht_index_read.l3
100
200
()

쓰기:

$ cat ht_index_write.l3
let ht = hashtable_create ()
let _ = ht.["name"] <- "Alice"
let _ = ht.["score"] <- 95
let _ = println ht.["name"]
let _ = println (to_string ht.["score"])

$ fn ht_index_write.l3
Alice
95
()

체이닝

.[는 왼쪽 결합(left-associative)이므로 matrix.[r].[c]처럼 중첩 인덱싱이 가능합니다. 아래는 2D 배열(행렬) 예시입니다:

$ cat matrix.l3
let row0 = array_create 2 0
let row1 = array_create 2 0
let _ = row0.[0] <- 1
let _ = row0.[1] <- 2
let _ = row1.[0] <- 3
let _ = row1.[1] <- 4
let matrix = array_create 2 row0
let _ = matrix.[1] <- row1
let _ = println (to_string matrix.[0].[0])
let _ = println (to_string matrix.[1].[1])

$ fn matrix.l3
1
4
()

matrix.[0]row0 배열을 반환하고, matrix.[0].[0]은 그 첫 번째 원소(1)를 반환합니다.

else 없는 if 표현식

if cond then expr 형태로 else 없이 조건문을 쓸 수 있습니다. 이 경우 컴파일러가 암묵적으로 else ()를 추가합니다. 즉, if cond then expr else ()와 동일합니다.

부수 효과만 실행하고 결과를 버리는 경우에 유용합니다:

$ cat if_then.l3
let x = 5
let _ = if x > 0 then println "positive"

$ fn if_then.l3
positive
()

가변 변수와 함께 사용하는 실용적인 예시입니다:

$ cat if_then_mut.l3
let mut x = 0
let _ = if true then x <- 42
let result = x

$ fn if_then_mut.l3
42

암묵적 else ()가 붙으므로, then 브랜치가 unit이 아닌 값을 반환하면 타입 불일치 에러가 발생합니다:

$ cat if_then_err.l3
let _ = if true then 42

$ fn if_then_err.l3
error[E0301]: Type mismatch: expected int but got unit
 --> if_then_err.l3:1:6-23
    |
  1 | let _ = if true then 42
    |       ^^^^^^^^^^^^^^^^^

if cond then expr는 then 브랜치가 unit인 경우에만 사용하세요. 값을 반환하는 조건문이라면 반드시 if cond then expr1 else expr2 형태로 작성하세요.

루프와 시퀀싱 조합 — 실용 예제

네 가지 기능을 모두 조합한 실용적인 예시입니다. 배열에서 짝수의 개수를 세는 프로그램입니다:

$ cat imperative_example.l3
let arr = array_create 5 0
let _ = arr.[0] <- 2
let _ = arr.[1] <- 3
let _ = arr.[2] <- 4
let _ = arr.[3] <- 7
let _ = arr.[4] <- 8
let mut even_count = 0
let _ =
    for i = 0 to 4 do
        if arr.[i] % 2 = 0 then even_count <- even_count + 1
let result = even_count

$ fn imperative_example.l3
3
  • arr.[i]로 배열 원소를 읽고 (인덱싱 문법)
  • % 2 = 0으로 짝수 여부를 확인하고 (기본 연산)
  • if ... then ...으로 조건부 실행하고 (else 없는 if)
  • for i = 0 to 4 do로 루프를 돌리며 (for 루프)
  • 결과를 even_count에 누적합니다 (가변 변수 + 시퀀싱)

배열 원소 중 짝수(2, 4, 8)가 3개이므로 결과는 3입니다.

구문 요약

구문설명
e1; e2순서대로 평가, e2의 값 반환
e1; e2; e3오른쪽 결합 체이닝
while cond do body조건이 거짓이 될 때까지 반복
for i = s to e do bodyi를 s부터 e까지 1씩 증가
for i = s downto e do bodyi를 s부터 e까지 1씩 감소
arr.[i]배열 원소 읽기 (IndexGet)
arr.[i] <- v배열 원소 쓰기 (IndexSet)
ht.[key]해시테이블 값 읽기
ht.[key] <- v해시테이블 값 쓰기
if cond then exprelse 없는 if (then 브랜치는 unit이어야 함)