let Bindings

let Bindings (F#)

F#에서 이름에 값을 묶어두는 방법, 바로 let 바인딩이에요. 값을 만들 때도, 함수를 정의할 때도, 클래스 안에서 필드를 만들 때도 전부 let이 쓰이니까 이 키워드 하나를 제대로 이해하면 F# 문법의 반은 먹고 들어간다고 할 수 있어요. 이름(식별자)과 값을 연결한다는 '바인딩'이라는 개념부터, 문법, 타입 표기, 클래스에서의 쓰임, 접근 범위까지 차근차근 살펴볼게요.

출처

본문

*바인딩(binding)*은 식별자(이름)를 어떤 값이나 함수와 연결해주는 작업이에요. 이름을 값이나 함수에 묶으려면 let 키워드를 쓰면 됩니다.

문법(Syntax)

// Binding a value:
let identifier-or-pattern [: type] =expressionbody-expression
// Binding a function value:
let identifier parameter-list [: return-type ] =expressionbody-expression

설명(Remarks)

let 키워드는 바인딩 표현식에서 하나 이상의 이름에 값을 정의하거나 함수 값을 정의할 때 쓰여요. 가장 단순한 let 표현식은 이름 하나를 단순한 값에 묶는 형태죠.

let i = 1

만약 표현식을 식별자와 새 줄로 나누어 적는다면, 표현식의 각 줄을 반드시 들여쓰기해야 해요. 아래 코드처럼요.

let someVeryLongIdentifier =
    // Note indentation below.
    3 * 4 + 5 * 6

이름 하나만 쓰는 대신 이름을 담고 있는 패턴(pattern)을 지정할 수도 있어요. 예를 들어 튜플이 그런 경우인데, 다음 코드를 보면 바로 감이 올 거예요.

let i, j, k = (1, 2, 3)

*본문 표현식(body-expression)*은 바로 그 이름들이 실제로 사용되는 표현식이에요. 본문 표현식은 자기만의 줄에 두되, let 키워드의 첫 글자와 정확히 맞춰 들여쓰기를 해야 합니다.

let result =

    let i, j, k = (1, 2, 3)

    // Body expression:
    i + 2 * j + 3 * k

let 바인딩은 모듈 최상위 레벨, 클래스 타입 정의, 그리고 함수 정의 같은 지역 스코프 어디에나 나타날 수 있어요. 다만 모듈이나 클래스 타입의 최상위 레벨에 있는 let 바인딩은 본문 표현식이 필요 없지만, 그 외 다른 스코프 레벨에서는 본문 표현식이 반드시 필요합니다. 그리고 묶은 이름은 정의한 지점 이후부터 사용할 수 있고, let 바인딩이 등장하기 전 지점에서는 사용할 수 없어요. 아래 코드가 정확히 그걸 보여줍니다.

// Error:
printfn "%d" x
let x = 100
// OK:
printfn "%d" x

함수 바인딩(Function Bindings)

함수 바인딩도 값 바인딩과 같은 규칙을 따라가요. 차이가 있다면 함수 바인딩에는 함수 이름과 매개 변수가 포함된다는 점이에요. 아래 코드를 보면 알 수 있죠.

let function1 a = a + 1

일반적으로 매개 변수는 패턴, 예를 들면 튜플 패턴이 될 수 있어요.

let function2 (a, b) = a + b

let 바인딩 표현식은 평가 결과로 마지막 표현식의 값을 가져와요. 그래서 아래 예시 코드에서 result의 값은 100 * function3 (1, 2)로 계산되고, 이건 300으로 평가돼요.

let result =
    let function3 (a, b) = a + b
    100 * function3 (1, 2)

더 자세한 내용은 Functions 문서를 확인해 주세요.

타입 표기(Type Annotations)

매개 변수의 타입을 직접 지정하고 싶다면, 콜론(:) 다음에 타입 이름을 적고 이걸 괄호로 감싸면 돼요. 반환 값의 타입을 지정하려면 마지막 매개 변수 뒤에 콜론과 타입을 덧붙이면 되고요. function1에 매개 변수 타입으로 정수를 지정한 전체 타입 표기는 아래처럼 생겼습니다.

let function1 (a: int) : int = a + 1

명시적인 타입 매개 변수가 없으면, 타입 유추(type inference)가 함수 매개 변수의 타입을 알아서 결정해요. 이 과정에서 매개 변수의 타입이 자동으로 일반화되어 제네릭이 되는 경우도 있어요.

더 자세한 내용은 Automatic GeneralizationType Inference 문서를 참고해 주세요.

클래스에서의 let 바인딩(let Bindings in Classes)

let 바인딩은 클래스 타입에는 나타날 수 있지만 구조체나 레코드 타입에는 나타날 수 없어요. 클래스 타입에서 let 바인딩을 쓰려면 그 클래스에 기본 생성자(primary constructor)가 있어야 합니다. 생성자 매개 변수는 클래스 정의에서 타입 이름 뒤에 와야 하고요. 클래스 타입 안의 let 바인딩은 그 클래스 타입의 private 필드와 멤버를 정의하며, 타입 안의 do 바인딩과 함께 그 타입의 기본 생성자 코드를 구성해요. 아래 코드 예시는 field1field2라는 private 필드를 가진 클래스 MyClass를 보여줍니다.

type MyClass(a) =
    let field1 = a
    let field2 = "text"
    do printfn "%d %s" field1 field2
    member this.F input =
        printfn "Field1 %d Field2 %s Input %A" field1 field2 input

field1field2의 스코프는 그것들이 선언된 타입 안으로 제한돼요. 자세한 내용은 let Bindings in ClassesClasses 문서를 참고해 주세요.

let 바인딩의 타입 매개 변수(Type Parameters in let Bindings)

모듈 레벨, 타입 안, 또는 계산 표현식(computation expression) 안의 let 바인딩은 명시적인 타입 매개 변수를 가질 수 있어요. 하지만 함수 정의 안처럼 표현식 안에 있는 let 바인딩은 타입 매개 변수를 가질 수 없습니다. 더 자세한 내용은 Generics 문서를 참고해 주세요.

let 바인딩에 붙는 특성(Attributes on let Bindings)

특성(attribute)은 모듈 안의 최상위 레벨 let 바인딩에 적용할 수 있어요. 아래 코드처럼 말이죠.

[<Obsolete>]
let function1 x y = x + y

let 바인딩의 스코프와 접근성(Scope and Accessibility of Let Bindings)

let 바인딩으로 선언한 개체의 스코프는, 그것을 포함하는 스코프(함수, 모듈, 파일, 클래스 등) 중 바인딩이 등장한 이후 부분으로 제한돼요. 그래서 let 바인딩은 이름을 어떤 스코프 안에 도입한다고 말할 수 있어요. 모듈 안에서는, 모듈 내 let 바인딩들이 그 모듈의 public 함수로 컴파일되기 때문에, 그 모듈이 접근 가능하기만 하면 모듈의 클라이언트 코드도 let으로 묶인 값이나 함수에 접근할 수 있습니다. 반면 클래스 안의 let 바인딩은 그 클래스에 private으로 한정돼요.

보통 모듈 안의 함수는 클라이언트 코드에서 쓸 때 모듈 이름으로 한정(qualified)해 줘야 합니다. 예를 들어 어떤 모듈 Module1에 함수 function1이 있다면, 사용자는 그 함수를 가리키기 위해 Module1.function1이라고 적어야 해요.

모듈 사용자는 import 선언을 통해 그 모듈 안의 함수들을 모듈 이름으로 한정하지 않고도 쓸 수 있게 만들 수 있어요. 아까 예시의 경우, 모듈 사용자는 import 선언 open Module1으로 모듈을 연 뒤에 function1이라고 곧바로 참조할 수 있습니다.

module Module1 =
    let function1 x = x + 1.0

module Module2 =
    let function2 x =
        Module1.function1 x

open Module1

let function3 x =
    function1 x

일부 모듈에는 RequireQualifiedAccess 특성이 붙어 있어요. 이 특성은 그 모듈이 노출하는 함수들을 반드시 모듈 이름으로 한정해서 써야 한다는 뜻이에요. F#의 List 모듈이 바로 이 특성을 가진 대표적인 예시입니다.

모듈과 접근 제어에 대한 더 자세한 내용은 ModulesAccess Control 문서를 참고해 주세요.

더 알아보기