점 축약 표기
점 축약 표기 (Dot shorthands)
Status.running보다 Status.running을 길게 쓰는 게 피곤하게 느껴질 때가 있어요. 점 축약 표기(dot shorthand) 문법 .foo는 컴파일러가 문맥에서 타입을 추론할 수 있을 때 타입을 생략해서 더 간결한 Dart 코드를 쓰게 해줘요.
출처: Dart 공식 문서
점 축약 표기는 언어 버전이 최소 3.10이어야 해요.
본문
개요 (Overview)
점 축약 표기 문법 .foo는 컴파일러가 문맥에서 타입을 추론할 수 있을 때 타입을 생략해서 더 간결한 Dart 코드를 쓰게 해줘요. enum 값, 정적 멤버, 생성자에 접근할 때 전체 ContextType.foo를 쓰는 것에 대한 깔끔한 대안을 제공하죠.
본질적으로 점 축약 표기는 표현식이 다음 중 하나로 시작한 뒤 다른 연산을 선택적으로 이어 붙이도록 해줘요.
- 식별자
.myValue - 생성자
.new() - 상수 생성
const .myValue()
enum 할당을 어떻게 단순화하는지 빠르게 볼게요.
// Use dot shorthand syntax on enums:
enum Status { none, running, stopped, paused }
Status currentStatus = .running; // Instead of Status.running
// Use dot shorthand syntax on a static method:
int port = .parse('8080'); // Instead of int.parse('8080')
// Uses dot shorthand syntax on a constructor:
class Point {
final int x, y;
Point(this.x, this.y);
Point.origin() : x = 0, y = 0;
}
Point origin = .origin(); // Instead of Point.origin()
문맥 타입의 역할 (The role of context type)
점 축약 표기는 문맥 타입(context type)을 사용해서 컴파일러가 해석할 멤버를 결정해요. 문맥 타입은 주변 코드가 표현식에서 기대하는 타입이에요. 예를 들어 Status currentStatus = .running에서 컴파일러는 Status가 기대된다는 걸 알기 때문에 .running이 Status.running을 의미한다고 추론해요.
어휘 구조와 문법 (Lexical structure and syntax)
정적 멤버 축약(static member shorthand)은 앞에 오는 점(.)으로 시작하는 표현식이에요. 주변 문맥에서 타입을 알 수 있을 때, 이 문법은 정적 멤버, 생성자, enum 값에 접근하는 간결한 방법을 제공해요.
enum
점 축약 표기의 주된, 그리고 매우 권장되는 사용 사례는 enum이에요. 특히 enum 타입이 아주 명확한 할당과 switch 문에서 유용하죠.
enum LogLevel { debug, info, warning, error }
/// Returns the color code to use for the specified log [level].
String colorCode(LogLevel level) {
// Use dot shorthand syntax for enum values in switch cases:
return switch (level) {
.debug => 'gray', // Instead of LogLevel.debug
.info => 'blue', // Instead of LogLevel.info
.warning => 'orange', // Instead of LogLevel.warning
.error => 'red', // Instead of LogLevel.error
};
}
// Example usage:
String warnColor = colorCode(.warning); // Returns 'orange'
이름 있는 생성자 (Named constructors)
점 축약 표기는 이름 있는 생성자나 팩토리 생성자를 호출하는 데 유용해요. 이 문법은 제네릭 클래스의 생성자에 타입 인자를 제공할 때도 동작해요.
class Point {
final double x, y;
const Point(this.x, this.y);
const Point.origin() : x = 0, y = 0; // Named constructor
// Factory constructor
factory Point.fromList(List<double> list) {
return Point(list[0], list[1]);
}
}
// Use dot shorthand syntax on a named constructor:
Point origin = .origin(); // Instead of Point.origin()
// Use dot shorthand syntax on a factory constructor:
Point p1 = .fromList([1.0, 2.0]); // Instead of Point.fromList([1.0, 2.0])
// Use dot shorthand syntax on a generic class constructor:
List<int> intList = .filled(5, 0); // Instead of List.filled(5, 0)
이름 없는 생성자 (Unnamed constructors)
.new 점 축약 표기는 클래스의 이름 없는 생성자를 호출하는 간결한 방법을 제공해요. 타입이 이미 명시적으로 선언된 필드나 변수를 할당할 때 유용하죠.
이 문법은 반복적인 클래스 필드 초기화를 정리하는 데 특히 효과적이에요. 아래 "after" 예시처럼 인자가 있는 생성자와 없는 생성자 모두에 쓸 수 있어요. 제네릭 타입 인자도 문맥에서 추론해요.
점 축약 표기가 없을 때:
class _PageState extends State<Page> {
late final AnimationController _animationController = AnimationController(
vsync: this,
);
final ScrollController _scrollController = ScrollController();
final GlobalKey<ScaffoldMessengerState> scaffoldKey =
GlobalKey<ScaffoldMessengerState>();
Map<String, Map<String, bool>> properties = <String, Map<String, bool>>{};
// ...
}
점 축약 표기를 쓸 때:
// Use dot shorthand syntax for calling unnamed constructors:
class _PageState extends State<Page> {
late final AnimationController _animationController = .new(vsync: this);
final ScrollController _scrollController = .new();
final GlobalKey<ScaffoldMessengerState> scaffoldKey = .new();
Map<String, Map<String, bool>> properties = .new();
// ...
}
정적 멤버 (Static members)
점 축약 문법으로 정적 메서드를 호출하거나 정적 필드/getter에 접근할 수 있어요. 컴파일러는 표현식의 문맥 타입에서 대상 클래스를 추론해요.
// Use dot shorthand syntax to invoke a static method:
int httpPort = .parse('80'); // Instead of int.parse('80')
// Use dot shorthand syntax to access a static field or getter:
BigInt bigIntZero = .zero; // Instead of BigInt.zero
상수 표현식 (Constant expressions)
접근하는 멤버가 컴파일 타임 상수라면 상수 문맥 안에서도 점 축약 표기를 쓸 수 있어요. enum 값과 const 생성자 호출에서 흔히 쓰죠.
enum Status { none, running, stopped, paused }
class Point {
final double x, y;
const Point(this.x, this.y);
const Point.origin() : x = 0.0, y = 0.0;
}
// Use dot shorthand syntax for enum value:
const Status defaultStatus = .running; // Instead of Status.running
// Use dot shorthand syntax to invoke a const named constructor:
const Point myOrigin = .origin(); // Instead of Point.origin()
// Use dot shorthand syntax in a const collection literal:
const List<Point> keyPoints = [.origin(), .new(1.0, 1.0)];
// Instead of [Point.origin(), Point(1.0, 1.0)]
규칙과 제약 (Rules and limitations)
점 축약 표기는 명확한 문맥 타입에 의존하므로, 알아 두면 좋은 몇 가지 규칙과 제약이 있어요.
체인에서 명확한 문맥 타입 필요 (Clear context type required in chains)
메서드 호출이나 프로퍼티 접근 같은 연산을 점 축약 표기 뒤에 이어 붙일 수는 있지만, 전체 표현식은 문맥 타입에 대해 검증돼요.
컴파일러는 먼저 문맥을 사용해서 점 축약 표기가 무엇으로 해석되는지 결정해요. 체인의 후속 연산은 그와 같은 초기 문맥 타입과 일치하는 값을 돌려줘야 하죠.
// .fromCharCode(72) resolves to the String "H",
// then the instance method .toLowerCase() is called on that String.
String lowerH = .fromCharCode(72).toLowerCase();
// Instead of String.fromCharCode(72).toLowerCase()
print(lowerH); // Output: h
비대칭 동등성 검사 (Asymmetric equality checks)
==와 != 연산자에는 점 축약 표기에 대한 특별한 규칙이 있어요. 동등성 검사의 오른쪽에서 점 축약 문법을 직접 쓰면, Dart는 왼쪽의 정적 타입을 사용해서 축약의 클래스나 enum을 결정해요.
예를 들어 myColor == .green 같은 표현식에서는 변수 myColor의 타입이 문맥으로 쓰여요. 이 말은 컴파일러가 .green을 Color.green으로 해석한다는 뜻이에요.
enum Color { red, green, blue }
// Use dot shorthand syntax for equality expressions:
void allowedExamples() {
Color myColor = Color.red;
bool condition = true;
// OK: `myColor` is a `Color`, so `.green` is inferred as `Color.green`.
if (myColor == .green) {
print('The color is green.');
}
// OK: Works with `!=` as well.
if (myColor != .blue) {
print('The color is not blue.');
}
// OK: The context for the ternary is the variable `inferredColor`
// being assigned to, which has a type of `Color`.
Color inferredColor = condition ? .green : .blue;
print('Inferred color is $inferredColor');
}
점 축약 표기는 ==나 != 연산자의 오른쪽에 있어야 해요. 조건 표현식 같은 더 복잡한 표현식과 비교하는 것도 허용되지 않아요.
enum Color { red, green, blue }
void notAllowedExamples() {
Color myColor = Color.red;
bool condition = true;
// ERROR: The shorthand must be on the right side of `==`.
// Dart's `==` operator is not symmetric for this feature.
if (.red == myColor) {
print('This will not compile.');
}
// ERROR: The right-hand side is a complex expression (a conditional expression),
// which is not a valid target for shorthand in a comparison.
if (myColor == (condition ? .green : .blue)) {
print('This will not compile.');
}
// ERROR: The type context is lost by casting `myColor` to `Object`.
// The compiler no longer knows that `.green` should refer to `Color.green`.
if ((myColor as Object) == .green) {
print('This will not compile.');
}
}
표현식 문은 .로 시작할 수 없다 (Expression statements can't start with .)
미래의 잠재적인 파싱 모호성을 피하기 위해, 표현식 문은 . 토큰으로 시작할 수 없어요.
class Logger {
static void log(String message) {
print(message);
}
}
void main() {
// ERROR: An expression statement can't begin with `.`.
// The compiler has no type context (like a variable assignment)
// to infer that `.log` should refer to `Logger.log`.
.log('Hello');
}
유니언 타입의 제한적 처리 (Limited handling of union types)
nullable 타입(T?)과 FutureOr<T>에는 특별 처리가 있지만, 지원은 제한적이에요.
- nullable 타입(
T?)에 대해T의 정적 멤버에는 접근할 수 있지만,Null의 멤버에는 접근할 수 없어요. FutureOr<T>에 대해T의 정적 멤버에는 접근할 수 있지만(주로async함수 반환을 지원하기 위해),Future클래스 자체의 정적 멤버에는 접근할 수 없어요.