Import 선언: open 키워드
Import 선언: open 키워드
import 선언은 특정 모듈이나 네임스페이스의 요소들을 정규화된(fully qualified) 이름을 붙이지 않고 바로 참조할 수 있게 해 줘요. F# 코드를 쓸 때 반복해서 전체 경로를 쓰는 일을 피하고 싶다면, 이 open 키워드가 아주 자주 찾게 되는 도구예요. C#의 using, Visual C++의 using namespace, Visual Basic의 Imports와 비슷한 역할을 한다고 보면 돼요.
출처: Microsoft Learn - Import declarations: The open keyword (2021-11-05 기준)
본문
사용법과 이유
매번 전체 네임스페이스나 모듈 경로를 붙여서 코드를 참조하다 보면, 쓰기도 어렵고 읽기도 어렵고 유지보수도 힘들어져요. 대신 자주 쓰는 모듈과 네임스페이스에는 open 키워드를 써서, 그 모듈이나 네임스페이스의 멤버를 참조할 때 짧은 형태의 이름만 사용하면 돼요.
open으로 지정하는 모듈이나 네임스페이스는 같은 프로젝트 안에 있거나, 참조된 프로젝트나 어셈블리 안에 있어야 해요. 그렇지 않다면 프로젝트에 참조를 추가하거나, -reference 명령줄 옵션(줄여서 -r)을 쓰면 돼요. 자세한 내용은 Compiler Options를 참고해요.
import 선언이 지정하는 이름들은 그 선언 뒤에 따라오는 코드부터, 그 코드를 감싸고 있는 네임스페이스·모듈·파일의 끝까지 사용할 수 있어요.
import 선언을 여러 개 쓸 때는 각각 서로 다른 줄에 배치해야 해요.
다음 코드는 open 키워드가 어떻게 코드를 간결하게 만드는지 보여 줘요.
// Without the import declaration, you must include the full
// path to .NET Framework namespaces such as System.IO.
let writeToFile1 filename (text: string) =
let stream1 = new System.IO.FileStream(filename, System.IO.FileMode.Create)
let writer = new System.IO.StreamWriter(stream1)
writer.WriteLine(text)
// Open a .NET Framework namespace.
open System.IO
// Now you do not have to include the full paths.
let writeToFile2 filename (text: string) =
let stream1 = new FileStream(filename, FileMode.Create)
let writer = new StreamWriter(stream1)
writer.WriteLine(text)
writeToFile2 "file1.txt" "Testing..."
보시다시피 첫 번째 함수에서는 System.IO.FileStream, System.IO.FileMode처럼 전체 경로를 모두 붙였는데, open System.IO 뒤에 정의한 두 번째 함수에서는 FileStream, FileMode처럼 짧은 이름만 쓰면 돼요.
이름 충돌(모호성) 주의
F# 컴파일러는 둘 이상의 open 모듈이나 네임스페이스에 같은 이름이 있을 때 오류나 경고를 뱉지 않아요. 대신 모호함이 생기면 더 나중에 open된 모듈이나 네임스페이스를 우선해요. 예를 들어 다음 코드에서 empty는 List와 Seq 모듈 둘 다에 있지만, 결과적으로 Seq.empty를 의미해요.
open List
open Seq
printfn %"{empty}"
그래서 List나 Seq처럼 동일한 이름의 멤버를 가진 모듈이나 네임스페이스를 open할 때는 조심해야 해요. 그런 경우 정규화된 이름을 쓰는 걸 고려하고, 코드가 import 선언의 순서에 의존하게 되는 상황은 피하는 게 좋아요.
Open type 선언
F#은 타입에도 open을 쓸 수 있어요. 예를 들어 이렇게요.
open type System.Math
PI
이렇게 하면 해당 타입의 접근 가능한 모든 static 필드와 멤버가 노출돼요.
또한 F#으로 정의한 레코드와 판별 공용체(discriminated union) 타입을 열어서 static 멤버를 노출시킬 수도 있어요. 판별 공용체의 경우에는 union case도 노출할 수 있어요. 모듈 안에 선언된 타입의 union case에 접근하고 싶은데 그 모듈 전체는 열고 싶지 않을 때 특히 유용해요. 예를 들어 이렇게요.
module M =
type DU = A | B | C
let someOtherFunction x = x + 1
// Open only the type inside the module
open type M.DU
printfn "%A" A
루트 경로에서만, global 지정자로 열기
중첩된 모듈, 예를 들어
module A =
module B =
...
는 이렇게 열 수 있어요.
open A // opens A
open B // opens A.B
완전히 정규화된(fully qualified) 모듈이나 네임스페이스만 열고 싶다면 global 지정자를 붙이면 돼요.
open global.A // works
open global.B // this now fails
open global.A.B // works
기본으로 열려 있는 네임스페이스
일부 네임스페이스는 F# 코드에서 너무 자주 쓰여서, 명시적인 import 선언 없이도 암시적으로 열려 있어요. 다음 표는 기본으로 열려 있는 네임스페이스들이에요.
| 네임스페이스 | 설명 |
|---|---|
| FSharp.Core | int, float 같은 기본 제공 타입에 대한 F# 타입 정의를 담고 있어요. |
| FSharp.Core.Operators | +, * 같은 기본 산술 연산을 담고 있어요. |
| FSharp.Collections | List, Array 같은 불변 컬렉션 클래스를 담고 있어요. |
| FSharp.Control | 지연 평가(lazy evaluation)와 async 표현식 같은 제어 구문용 타입을 담고 있어요. |
| FSharp.Text | printf 함수처럼 서식을 갖춘 입출력을 위한 함수를 담고 있어요. |
AutoOpen 특성
어셈블리에 AutoOpen 특성을 적용하면, 그 어셈블리가 참조될 때 네임스페이스나 모듈을 자동으로 열 수 있어요. 모듈에 AutoOpen 특성을 적용하면, 부모 모듈이나 네임스페이스가 열릴 때 그 모듈도 자동으로 열려요. 자세한 내용은 AutoOpenAttribute를 참고해요.
RequireQualifiedAccess 특성
일부 모듈, 레코드, 공용체 타입은 RequireQualifiedAccess 특성을 지정할 수 있어요. 그런 모듈·레코드·공용체의 요소를 참조할 때는 import 선언을 포함하더라도 반드시 정규화된 이름을 써야 해요. 흔히 쓰이는 이름을 정의하는 타입에 이 특성을 전략적으로 사용하면 이름 충돌(name collision)을 피할 수 있고, 그래서 라이브러리가 변해도 코드가 더 튼튼해져요. 자세한 내용은 RequireQualifiedAccessAttribute를 참고해요.