let 바인딩
let 바인딩 (let Bindings)
바인딩(binding)은 이름(식별자)에 값이나 함수를 연결해 두는 일이에요. F#에서는 let이라는 키워드가 바로 그 역할을 해요. 값을 이름에 묶어 두면 그 이름으로 언제든 꺼내 쓸 수 있죠. 이 문서는 F#의 let 바인딩을 어떤 형태로 쓰는지, 어디에 쓸 수 있는지 하나씩 짚어 볼게요.
출처: Microsoft Learn — let Bindings (F#)
본문
문법 (Syntax)
let 바인딩은 크게 값을 바인딩할 때와 함수 값을 바인딩할 때 두 가지 형태로 나뉘어요. 값 바인딩은 이름(또는 패턴)에 표현식을 연결하고, 함수 바인딩은 이름과 매개변수 목록에 함수 본문을 연결하죠.
// Binding a value:
let identifier-or-pattern [: type] =expressionbody-expression
// Binding a function value:
let identifier parameter-list [: return-type ] =expressionbody-expression
Remarks (설명)
let 키워드는 바인딩 표현식(binding expression)에서 하나 이상의 이름에 값이나 함수 값을 정의할 때 써요. 가장 간단한 형태는 이름 하나에 단순한 값을 연결하는 경우예요.
let i = 1
표현식을 식별자와 줄바꿈으로 구분한다면, 그 표현식의 각 줄을 들여쓰기해야 해요. 아래 코드처럼요.
let someVeryLongIdentifier =
// Note indentation below.
3 * 4 + 5 * 6
이름 하나 대신 이름을 담은 패턴도 지정할 수 있어요. 예를 들어 튜플(tuple) 패턴을 쓰면 여러 값을 한 번에 바인딩할 수 있죠.
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 바인딩은 모듈 최상위, 클래스 타입 정의, 그리고 함수 정의 안 같은 지역 스코프(local scope)에 쓸 수 있어요. 모듈 최상위나 클래스 타입의 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 Generalization과 Type Inference 문서를 보세요.
클래스에서의 let 바인딩 (let Bindings in Classes)
let 바인딩은 클래스 타입에는 쓸 수 있지만 구조체(struct)나 레코드(record) 타입에서는 쓸 수 없어요. 클래스에서 let 바인딩을 쓰려면 그 클래스에 기본 생성자(primary constructor)가 있어야 하고, 생성자 매개변수는 클래스 정의에서 타입 이름 뒤에 와야 해요.
클래스 타입의 let 바인딩은 그 클래스의 private 필드와 멤버를 정의해요. 그리고 do 바인딩과 함께 그 타입의 기본 생성자 코드를 구성하죠. 아래 코드는 private 필드 field1과 field2를 가진 클래스 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
field1과 field2의 스코프는 선언된 타입 안으로 제한돼요. 자세한 내용은 let Bindings in Classes와 Classes 문서를 참고하세요.
let 바인딩의 타입 매개변수 (Type Parameters in let Bindings)
모듈 최상위, 타입, 계산 표현식(computation expression) 안의 let 바인딩은 명시적인 타입 매개변수를 가질 수 있어요. 하지만 함수 정의 안 같은 표현식 안의 let 바인딩은 타입 매개변수를 가질 수 없죠. 자세한 내용은 Generics 문서를 보세요.
let 바인딩의 특성 (Attributes on let Bindings)
모듈의 최상위 let 바인딩에는 특성(attribute)을 적용할 수 있어요.
[<Obsolete>]
let function1 x y = x + y
let 바인딩의 스코프와 접근성 (Scope and Accessibility of Let Bindings)
let 바인딩으로 선언한 개체의 스코프는 그것을 포함하는 스코프(함수, 모듈, 파일, 클래스 등)에서 바인딩이 등장한 지점 이후 부분으로 제한돼요. 그래서 let 바인딩은 이름을 어떤 스코프 안에 도입한다고 말할 수 있어요.
모듈 안의 let 바인딩된 값이나 함수는 모듈이 접근 가능한 한 모듈의 클라이언트도 접근할 수 있어요. 이유는 모듈의 let 바인딩이 모듈의 public 함수로 컴파일되기 때문이에요. 반대로 클래스 안의 let 바인딩은 그 클래스에 private이에요.
보통 모듈의 함수는 클라이언트 코드에서 모듈 이름으로 한정해 주어야 해요. 예를 들어 모듈 Module1에 함수 function1이 있다면 사용자는 Module1.function1 형태로 호출하죠.
사용자는 import 선언(open)으로 그 모듈 안의 함수를 모듈 이름 없이 쓸 수도 있어요. 앞의 예시에서 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 모듈이 대표적인 예예요. RequireQualifiedAccess 설명도 함께 보면 좋아요.
모듈과 접근 제어에 대한 자세한 내용은 Modules와 Access Control 문서를 참고하세요.