Let Values
Let Values (F#의 값 바인딩)
F#에서 값이란 정해진 타입을 가진 하나의 수량을 뜻해요. 정수나 부동소수점 숫자, 문자나 문자열, 리스트, 시퀀스, 배열, 튜플, 판별 공용체(DU), 레코드, 클래스 타입, 그리고 함수 값까지 — F#에서는 이 모든 것을 '값'이라고 부릅니다. 이 글에서는 let 키워드로 값에 이름을 붙이는 **바인딩(binding)**을 처음부터 천천히 살펴볼게요.
출처
본문
Binding a Value — 값 바인딩
"바인딩"은 이름과 정의를 서로 연결해 주는 것을 뜻하는 용어예요. F#에서는 let 키워드가 그 역할을 담당합니다. 아래 예시를 볼게요.
let a = 1
let b = 100u
let str = "text"
// A function value binding.
let f x = x + 1
값의 타입은 **정의에서 자동으로 추론(inference)**돼요. 정수나 부동소수점 같은 기본 타입(primitive type)이라면 리터럴의 타입이 곧 그 값의 타입이 됩니다. 그래서 위 예시에서 컴파일러는 b의 타입을 unsigned int로, a의 타입은 int로 추론해요. 함수 값의 타입은 함수 본문의 반환 값에서 정해집니다. (함수 값의 타입이 궁금하다면 Functions 문서에서, 리터럴 타입은 Literals 문서에서 더 자세히 볼 수 있어요.)
한 가지 참고할 점: 컴파일러는 기본적으로 사용되지 않는 바인딩에 대해 경고를 내지 않아요. 그런 경고를 받고 싶다면 프로젝트 파일에 경고 1182번(경고 1182)을 켜거나, 컴파일러를 호출할 때 --warnon 옵션을 지정하면 됩니다 (자세한 내용은 Compiler Options 문서를 확인해요).
Why Immutable? — 왜 불변(immutable)인가?
불변(immutable) 값이란 프로그램이 실행되는 내내 결코 바뀌지 않는 값을 말해요. C++, Visual Basic, C# 같은 언어에 익숙해져 있다면, F#이 실행 중에 값을 새로 할당할 수 있는 '변수'보다 불변 값에 더 우선순위를 둔다는 점이 다소 낯설게 느껴질 수 있습니다.
그런데 불변 데이터는 함수형 프로그래밍에서 아주 중요한 요소예요. 여러 스레드가 동시에 읽고 쓰는 환경에서, 여러 스레드가 바꿀 수 있는 공유 가변 변수는 관리하기가 굉장히 어렵습니다. 게다가 가변 변수는 다른 함수에 넘겨졌을 때 "이 값이 그 안에서 바뀔 수도 있는지" 한눈에 파악하기 어렵다는 문제도 있죠.
순수 함수형 언어에는 변수라는 개념 자체가 없어요. 대신 함수는 순수하게 수학적 함수처럼 동작합니다. 절차형 언어에서 변수 할당으로 값을 바꾸는 코드는, 함수형 언어에서 "불변 값 하나가 입력으로 들어가고, 불변 함수를 거쳐, 서로 다른 불변 값들이 출력으로 나오는" 형태로 대체됩니다.
이런 수학적 엄밀함 덕분에 프로그램의 동작을 더 촘촘하게 추론할 수 있어요. 추론이 잘 되면 컴파일러가 코드를 더 엄격하게 검사하고 더 효과적으로 최적화할 수 있고, 개발자도 올바른 코드를 이해하고 작성하기가 더 쉬워집니다. 그래서 함수형 코드는 일반적인 절차형 코드보다 디버깅하기 더 쉽다고 볼 수 있어요.
물론 F# 자체는 순수 함수형 언어가 아니에요. 하지만 함수형 프로그래밍을 충분히 지원합니다. 그래서 불변 값을 쓰는 습관은 좋은 실천인데, 바로 그것만으로도 함수형 프로그래밍의 중요한 장점 하나를 그대로 누릴 수 있기 때문이에요.
Mutable Variables — 가변 변수
값을 바꿀 수 있는 변수가 필요하다면 mutable 키워드를 사용하면 돼요. 다만 F#의 가변 변수는 일반적으로 범위(scope)를 제한해서 쓰는 편이 좋습니다. 타입의 필드나 지역(local) 값으로 한정해 두는 식이죠. 범위가 좁은 가변 변수는 통제하기 쉽고, 잘못된 방식으로 수정될 가능성도 낮아요.
가변 변수의 초깃값은 일반 값을 정의할 때처럼 let 키워드로 지정합니다. 차이점은 그 이후에 <- 연산자로 새 값을 다시 넣을 수 있다는 점이에요. 아래 예시를 볼게요.
let mutable x = 1
x <- x + 1
한 가지 알아두면 좋은 세부 동작이 있어요. mutable로 표시된 값을 클로저(closure)가 포착하면, 자동으로 'a ref로 승격(promote)될 수 있습니다. 시퀀스 빌더처럼 클로저를 만드는 형태도 여기에 해당해요. 이런 일이 실제로 일어날 때 알림을 받고 싶다면 프로젝트 파일에서 경고 3180번을 켜거나 컴파일러 호출 시 옵션을 지정하면 됩니다.
더 알아보기
- let Bindings —
let키워드로 이름과 값·함수를 연결하는 방법의 전반. - Functions — F# 함수 개요.
- Null Values — F#의 널 값.
- F# Language Reference — F# 언어 참조 전체.