let Bindings
let Bindings (함수 바인딩)
F#에서 let은 이름과 값을, 또는 이름과 함수를 하나로 묶어주는 가장 기본적인 키워드예요. 바인딩(binding)은 식별자(identifier)와 값이나 함수를 연결한다는 뜻이고, 이 let 키워드로 이름을 값이나 함수에 묶어요. 함수를 정의하는 일도 결국 이 바인딩에서 출발한다고 보면 돼요.
출처
본문
바인딩이란
바인딩은 식별자를 값이나 함수와 연결해 줘요. 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 i = 1
식별자와 표현식을 줄바꿈으로 나눠 적을 땐 표현식의 모든 줄을 들여쓰기해야 해요. 아래 코드가 그 예시예요.
let someVeryLongIdentifier =
// Note indentation below.
3 * 4 + 5 * 6
단순한 이름 대신 패턴도 쓸 수 있어요. 예를 들어 튜플 패턴을 쓸 수 있죠.
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 바인딩은 본문 표현식이 없어도 되지만, 그 외 스코프에서는 본문 표현식이 필수예요. 바인딩된 이름은 정의된 지점 이후에서만 쓸 수 있고, 그 전에는 쓸 수 없어요. 아래 코드에서 그 차이를 확인해 볼게요.
// 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 바인딩은 클래스 타입에는 쓸 수 있지만 구조체나 레코드 타입에는 쓸 수 없어요. 클래스 타입에서 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)
모듈 최상위, 타입 안, 또는 계산 표현식 안의 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 바인딩은 모듈이 접근 가능하다면 그 모듈을 사용하는 쪽에서 접근할 수 있어요. 모듈의 let 바인딩은 모듈의 public 함수로 컴파일되기 때문이죠. 반대로 클래스 안의 let 바인딩은 클래스에 private이에요.
보통 모듈 안의 함수는 클라이언트 코드에서 쓸 때 모듈 이름으로 한정(qualify)해야 해요. 예를 들어 Module1 모듈에 function1 함수가 있다면, 사용자는 Module1.function1이라고 적어 그 함수를 가리켜요.
모듈 사용자는 import 선언으로 모듈 안의 함수들을 모듈 이름 없이 쓸 수 있게 할 수도 있어요. 아까 든 예시에서 사용자는 open Module1이라는 import 선언으로 모듈을 연 뒤, 이후부터는 그냥 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 모듈이 이런 특성을 가진 대표적인 예시예요.
모듈과 접근 제어에 대해 더 알고 싶다면 Modules와 Access Control 문서를 참고해 주세요.