Null Values(널 값)
Null Values(널 값)
이 문서에서는 F#에서 null 값이 어떻게 쓰이는지를 다루고 있어요. F#에서는 보통 null을 거의 쓰지 않지만, 다른 .NET 언어와 연동할 때는 반드시 만나게 되는 개념이죠. 함께 살펴볼게요.
출처: Microsoft Learn — F# Null Values 원문 갱신일: 2025-01-29
본문
F# 9 이전의 Null Values
F#에서는 값이나 변수에 null을 평범하게 쓰지 않아요. 하지만 몇 가지 특수한 상황에서 null이 비정상적인 값으로 등장하곤 합니다.
- F#으로 정의한 타입이라면 해당 타입에
AllowNullLiteral특성이 붙어 있지 않는 한,null을 일반적인 값으로 쓸 수 없어요. - 다른 .NET 언어로 정의한 타입이라면
null이 가능한 값이고, 그런 타입과 상호 운용할 때 F# 코드에서null값을 만날 수 있어요.
F#으로 정의하고 F#에서만 사용하는 타입이라면, F# 라이브러리만으로 null 값을 만드는 방법은 사실 Unchecked.defaultof나 Array.zeroCreate를 쓰는 것뿐이에요. 다만 그 타입을 다른 .NET 언어에서 사용하거나, .NET Framework처럼 F#으로 작성되지 않은 API와 함께 쓴다면 null 값이 생길 수 있답니다.
다른 .NET 언어에서 "null이 될 수 있는 참조 변수"를 쓸 자리에, F#에서는 option 타입을 활용할 수 있어요. null 대신 객체가 없을 때는 None, 객체가 있을 때는 Some(obj)를 사용하세요. 자세한 내용은 Options을 참고해요.
참고로 Some x에서 x가 우연히 null이라면 그 null을 option에 그대로 담을 수는 있어요. 그래서 값이 정말 null일 때는 반드시 None을 쓰는 게 중요합니다.
null 키워드는 F#에서도 유효한 키워드예요. .NET Framework API나 다른 .NET 언어로 작성된 API를 다룰 때는 이 키워드를 써야 합니다. null 값이 필요해지는 상황은 크게 두 가지예요.
- .NET API를 호출하면서 인자로
null을 넘길 때 - .NET 메서드 호출에서 반환값이나 출력 매개변수로 받은 값이
null인지 해석할 때
.NET 메서드에 null을 전달할 때는 호출 코드에서 null 키워드를 그냥 쓰면 돼요. 다음 예시를 볼게요.
open System
// Pass a null value to a .NET method.
let ParseDateTime (str: string) =
let (success, res) =
DateTime.TryParse(str, null, System.Globalization.DateTimeStyles.AssumeUniversal)
if success then Some(res) else None
.NET 메서드에서 얻은 null 값을 해석할 때는 가능하면 패턴 매칭을 활용하세요. 아래 예시는 입력 스트림의 끝까지 읽었을 때 ReadLine이 돌려주는 null 값을 패턴 매칭으로 처리하는 코드예요.
// Open a file and create a stream reader.
let fileStream1 =
try
System.IO.File.OpenRead("TextFile1.txt")
with :? System.IO.FileNotFoundException ->
printfn "Error: TextFile1.txt not found."
exit (1)
let streamReader = new System.IO.StreamReader(fileStream1)
// ProcessNextLine returns false when there is no more input;
// it returns true when there is more input.
let ProcessNextLine nextLine =
match nextLine with
| null -> false
| inputString ->
match ParseDateTime inputString with
| Some(date) -> printfn "%s" (date.ToLocalTime().ToString())
| None -> printfn "Failed to parse the input."
true
// A null value returned from .NET method ReadLine when there is
// no more input.
while ProcessNextLine(streamReader.ReadLine()) do
()
F# 타입의 null 값은 다른 방법으로도 만들어질 수 있어요. 예를 들어 Array.zeroCreate는 Unchecked.defaultof를 호출하거든요. 그런 코드를 쓸 때는 null 값이 밖으로 샐 수 없도록 잘 캡슐화해 두는 게 좋아요.
F# 전용 라이브러리라면 모든 함수에서 null을 검사할 필요는 없어요. 하지만 다른 .NET 언어와 연동하기 위한 라이브러리를 만든다면, C#이나 Visual Basic에서 그렇듯 null 입력 매개변수를 검사해서 ArgumentNullException을 던져야 할 수 있어요.
임의의 값이 null인지 확인할 때는 다음과 같은 코드를 쓰면 됩니다.
match box value with
| null -> printf "The value is null."
| _ -> printf "The value is not null."
F# 9부터의 Null Values
F# 9에서는 null을 값으로 가질 수 있는 참조 타입을 다루기 위한 기능이 언어에 추가되었어요. 이 기능은 기본적으로 꺼져 있기 때문에, 켜려면 프로젝트 파일에 다음 속성을 넣어야 합니다.
<Nullable>enable</Nullable>
이 속성은 F# 컴파일러에 --checknulls+ 플래그를 전달하고, 빌드에 NULLABLE 전처리기 지시문을 설정해 줘요. 자세한 내용은 컴파일러 옵션과 Nullable 전처리기 지시문을 참고하세요.
null 허용을 명시적으로 선택하려면 타입 선언 뒤에 다음과 같은 새 문법을 붙이면 됩니다.
type | null
여기서 막대 기호 |는 논리적 OR의 의미를 가져요. 즉 밑바탕이 되는 타입과 nullable 참조라는 두 서로소 타입 집합을 합집합으로 묶는 거예요. 이 기호는 F#의 discriminated union에서 여러 케이스를 선언할 때 쓰는 것과 동일한 문법 기호입니다. type AB = A | B가 "A 또는 B"라는 뜻을 담는 것과 같은 원리예요.
| null 어노테이션은 참조 타입을 평범하게 쓸 수 있는 모든 자리에서 사용할 수 있어요.
- union 타입, 레코드 타입, 커스텀 타입의 필드
- 기존 타입의 타입 별칭
- 제네릭 타입의 타입 적용
let바인딩, 매개변수, 반환 타입에 대한 명시적 타입 어노테이션- 멤버, 프로퍼티, 필드 같은 객체 지향 프로그래밍 구성 요소의 타입 어노테이션
type AB = A | B
type AbNull = AB | null
type RecordField = { X: string | null }
type TupleField = string * string | null
type NestedGenerics = { Z : List<List<string | null> | null> | null }
다만 | 기호는 F#에서 다른 용도로도 쓰이기 때문에, 문법적으로 모호해지는 상황이 생길 수 있어요. 그럴 때는 null 어노테이션을 붙인 타입을 괄호로 감싸면 됩니다.
// Unexpected symbol '|' (directly before 'null') in member definition
type DUField = N of string | null
같은 타입을 괄호 ( )로 감싸면 문제가 해결돼요.
type DUField = N of (string | null)
패턴 매칭에서 |는 서로 다른 패턴 매칭 절을 구분하는 데 쓰여요.
match x with
| ?: string | null -> ...
사실 이 코드는 먼저 string 타입 검사를 수행한 뒤, null을 처리하는 별도의 절을 두는 것과 같은 의미예요.
match x with
| ?: string
| null -> ...
중요 — 추가된 null 관련 기능들은 상호 운용을 위해 만들어진 것이에요. F# 타입을 모델링할 때 누락된 정보를 표현하려고
| null을 쓰는 것은 F#답지 않은(비관용적) 방식이에요. 그런 목적에는 위에서 설명한 것처럼 Options을 사용하세요. null 관련 규칙을 더 읽어보려면 스타일 가이드를 확인해 주세요.
더 알아보기
- Values
- Match Expressions
- F# Style Guide — Nulls and Default Values 규칙
- F# Options
- 이 문서의 원문은 GitHub의 dotnet/docs 저장소에서 확인할 수 있어요.