binding

바인딩

나루 위키
둘러보기로 가기 검색하러 가기

바인딩지시와 함께 유닛의 구성 요소로서, 표현식(흔히 선언)을 슬롯(보통 이름)에 연관짓는 역할을 한다. 각 영역은 바인딩의 목록을 포함하고 있다.

language_name := "naru"
the_answer := 42
hello := fn { "Hello?" println() }

바인딩의 좌항은 패턴 표현식이며, 여러 개의 슬롯을 동시에 선언하는 것이 가능하다. 바인딩에서 사용하는 패턴은 완전해야 한다.

(a, b, x: c) := (1, 2, x: 3)
(a, b, c) repr println() --> (1, 2, 3)

(1, b, x: c) := (1, 2, x: 3) -- 오류: 패턴 `(1, _, x: _)`는 완전하지 않음

f := fn { "voiddd" println() }
() := f() -- 슬롯을 선언하지 않는 바인딩도 적법하다.

var 예약어가 쓰인 슬롯은 변경 가능하다. 이러한 슬롯은 대입 문장으로 바꿀 수 있다. 바꿔 말하면, var가 쓰이지 않은 슬롯은 변경할 수 없다. 변경 가능성은 슬롯의 속성이지 바인딩의 속성이 아님을 주의할 것.

(var x, y) := (54, 54)
x = 42 -- OK
y = 42 -- 오류: y는 변경 불가능함

참조 타입도 변경 불가능한 슬롯에 들어 있으면 변경할 수 없다. 그러나 참조 타입은 여러 참조자를 허용하므로 변경 가능한 슬롯에 넣은 뒤 바꿀 수는 있으며, 이렇게 바뀐 참조를 변경 불가능한 슬롯을 통해 관측할 수 있다.

x := new "hello"
x@ = "goodbye" -- 오류: 변경 불가능한 x를 통해 참조를 변경하려 함

var y := x
y@ = "goodbye"
x@ println() --> goodbye

바인딩은 타입의 필드에도 적용될 수 있는데, 이 경우에는 해당 타입의 메서드 또는 프로퍼티 등을 정의하는 역할을 할 수 있다.

Position := new type (x: rat, y: rat)

Position distance := fn (self, other: Position) {
    dx := self x - other x
    dy := self y - other y
    (dx ** 2 + dy ** 2) sqrt
}

zelda := Position (x: 3, y: 2)
bokoblin := Position (x: -24, y: 15)
zelda distance(bokoblin) println()

필드에 대한 바인딩 또한 이름에 대한 바인딩과 같은 영역 규칙이 적용된다. 다시 말해, 함수 안에서 어떤 타입에 대한 필드 바인딩을 추가해도 함수 밖에서 그 필드를 쓸 수 있게 되지 않는다.

선언이 바인딩의 우항에 들어 있고 아직 이름이 붙지 않았을 경우, 좌항에 있는 이름으로 그 선언의 이름이 부여된다.

최상위 바인딩

패키지 유닛 안의 최상위 바인딩은 지시와 함께 패키지를 구성하는 기본 단위가 되며, 다른 프로그램에서 use 지시를 사용하여 가져올 수 있게 된다.

최상위 영역에 선언된 바인딩은 그 초기화 순서가 보장되지 않는다. 따라서 바인딩이 선언된 순서에 무관하게 사용할 수 있으며, 재귀 호출도 간단하게 가능하다. 실제 초기화 순서는 바인딩 사이의 상호 의존성으로부터 추론(topological sorting)된다.

k := f(42) -- 아직 선언되지 않은 함수 f를 참조한다.
f := fn(x) { x + 1 }

-- 그러나 아래와 같은 재귀는 허용되지 않는다.
x := y + 3
y := x - 3

이름이 같은 최상위 바인딩은 일반적으로 오류이다. 그러나 약한 바인딩으로 들여온 바인딩은 가릴 수 있다.

바인딩 문장

함수 선언 안의 바인딩은 순서대로 적용된다. 이름이 같을 경우에도 마찬가지로 적용되기 때문에, 바인딩이 다른 바인딩을 가릴(shadow) 수 있다. 바인딩을 가릴 때는 새로운 영역이 그 직후에 생기는 것으로 간주된다.

x := 42
f := fn {
    x println() -- 이 x는 위의 x를 가리킨다.
}
x := "foo" -- 위의 x 바인딩을 가린다.
x println() --> foo
f() -- 하지만 이 호출은 여전히 42를 출력한다.

바인딩되는 선언은 바인딩 직후의 영역을 볼 수 있으므로, 자기 자신을 참조하는 재귀가 가능하다.

fib := fn(n) {
    -- 여기서는 fib는 볼 수 있지만 fac은 볼 수 없다.
    if n < 2 { n } else { fib(n-1) + fib(n-2) }
}
fac := fn(n) {
    if n < 1 { 1 } else { n * fac(n-1) }
}

? 최상위 바인딩은 상호 재귀하는 선언을 허용한다. 하지만 바인딩 문장에서도 이게 가능해야 하지 않을까? 바인딩 문장이 연속으로 나오면 해당 블록 안에서는 재귀 선언이 되는 식으로?

바인딩이 문장 단위에서 일어났을 경우, 그 바깥에서 해당 바인딩이 사용되면 영역 확장이 일어날 수 있다.

의미론

논리적으로, 바인딩된 은 원본과는 독립적인 값이 되며, 이후의 변경이 원본에 영향을 끼치지 않는다. 다시 말해, 바인딩은 참조가 아니라 복사의 의미를 가진다. 이는 루아파이썬 등의 언어에서 변수가 참조의 의미로 동작하는 것과 다르다. 여러 바인딩에서 값을 공유해야 할 경우에는 명시적으로 참조를 사용해야 한다.

나루 구현체는 효율을 위해 일부 불변 타입의 바인딩을 참조처럼 구현할 수 있다.