Dart 튜토리얼 3편: 함수의 구조를 잡는 법

요약#

핵심 요지#

  • 문제 정의: JavaScript 같은 동적 언어는 함수 파라미터1 전달 방식이 불명확해서 호출 시 혼란을 일으킨다.
  • 핵심 주장: Dart는 함수 정의, 파라미터 규칙, 함수 타입2을 문법으로 명확히 제공한다.
  • 주요 근거: 파라미터 규칙, 함수 타입, 타입 별칭3, 호출 가능한 객체4를 통해 구조를 명확히 한다.
  • 실무 기준: 타입 주석과 시그니처5로 함수를 명확히 문서화한다.

문서가 설명하는 범위#

  • 함수 정의와 파라미터 규칙을 다룬다.
  • function typetypedef 사용법을 설명한다.
  • callable objectdot shorthand6 문법을 소개한다.

읽는 시간: 14분 | 난이도: 초급


참고 자료#


문제 상황#

JavaScript 같은 동적 언어에서는 함수 호출 시 어떤 파라미터가 필수인지 선택인지 구분하기 어렵습니다.
실행해봐야만 누락된 파라미터를 알 수 있어서 디버깅이 어렵습니다.

기존 방식의 한계#

// JavaScript 예시
function createUser(name, age, email) {
console.log(name, age, email);
}
createUser("Alice"); // Alice undefined undefined 출력 - 오류 없음
createUser("Bob", 25); // Bob 25 undefined 출력 - 오류 없음

문제는 다음과 같습니다.

  • 어떤 파라미터가 필수인지 함수 시그니처만 봐서는 알 수 없다.
  • 선택적 파라미터와 필수 파라미터 구분이 없다.
  • 실행해봐야만 누락된 값을 발견할 수 있다.

해결 방법#

Dart는 함수 파라미터 규칙을 문법으로 명확히 구분하고, 함수 타입을 타입 시스템에 통합했습니다.

단계 1: 함수 시그니처와 선택적 파라미터#

Why#

NOTE

함수를 호출할 때 실수가 가장 많이 나는 지점은 “값을 빼먹는 것”과 “순서를 헷갈리는 것”입니다.
그래서 호출 실수를 줄이려면, 선언부에서부터 호출 규칙이 드러나야 합니다.

void setSize(int width, int height) {}
// setSize(10); // 컴파일 오류: 필요한 인자가 부족하다.

What#

NOTE

signature는 “이 함수는 어떤 파라미터를 받는가”를 나타내는 선언부 규칙입니다.

How#

TIP

Dart는 required positional parameter7, named parameter8, optional positional parameter9를 제공합니다.
함수의 signature파라미터 형태가 드러납니다.

void enableFlags({bool bold = false, bool hidden = false}) {}
// 호출: 이름을 명시해서 전달
enableFlags(bold: true, hidden: false);

필수 named parameter

const Scrollbar({super.key, required Widget child});
// child를 빼먹으면 컴파일 오류

Watch out#

WARNING

named parameter는 기본적으로 선택입니다.
“반드시 받아야 하는 값”이라면 required로 강제해야, 누락이 컴파일 단계에서 잡힙니다.

void log({String? tag}) {
print(tag);
}
log(); // OK: tag는 선택이고, 전달하지 않으면 null이다.

결론: 파라미터 형태가 선언부에 명확히 보이고, 컴파일러가 누락을 잡아냅니다.


단계 2: 함수 타입과 typedef#

Why#

NOTE

함수를 “값처럼” 넘기기 시작하면, 어느 순간부터는 “이 함수가 어떤 입력/출력을 가지는지”를 타입으로 고정해야 합니다.
그렇지 않으면 잘못된 함수를 넘겨도 늦게 발견될 수 있습니다.

int plus(int a, int b) => a + b;
int minus(int a, int b) => a - b;
// 어떤 함수를 넘기는지 명확히 하고 싶다.

What#

NOTE

function type은 함수가 받는 인자와 반환 타입을 타입으로 표현한 것입니다.

How#

TIP

Dart는 함수도 객체라서 변수에 할당하거나 다른 함수의 인자로 전달할 수 있습니다.
function type은 함수의 입력과 반환 형태를 나타내는 타입입니다.

void greet(String name, {String greeting = 'Hello'}) =>
print('$greeting $name!');
// 함수 타입 선언
void Function(String, {String greeting}) g = greet;
g('Dash', greeting: 'Howdy');

typedef를 사용하면 긴 타입을 짧은 이름으로 표현할 수 있습니다.

typedef IntList = List<int>;
IntList il = [1, 2, 3];
typedef Compare<T> = int Function(T a, T b);
int sort(int a, int b) => a - b;
assert(sort is Compare<int>); // true

Watch out#

WARNING

function type은 “모양(시그니처)”이 정확히 맞아야 합니다.
즉, 인자 개수/순서/이름이 다르면 같은 함수처럼 보이더라도 타입이 맞지 않아 할당할 수 없습니다.

void greet(String name, {String greeting = 'Hello'}) {}
// void Function(String) g = greet; // 컴파일 오류: named parameter가 포함된 시그니처다.

결론: 긴 타입을 짧은 이름으로 표현하고, 함수 타입을 재사용할 수 있습니다.


단계 3: callable object#

Why#

NOTE

함수 하나로는 부족하고 “상태를 가진 동작”이 필요할 때가 있습니다.
이때 객체를 함수처럼 호출할 수 있으면, API가 더 단순해집니다.

class Counter {
int value = 0;
}

What#

NOTE

call() 메서드를 구현하면, 클래스 인스턴스를 함수 호출 문법으로 부를 수 있습니다.

How#

TIP

클래스 인스턴스를 함수처럼 호출하려면 call() 메서드를 구현합니다.
call() 메서드는 일반 함수와 동일하게 파라미터와 반환 타입을 가질 수 있습니다.

class WannabeFunction {
String call(String a, String b, String c) => '$a $b $c!';
}
var wf = WannabeFunction();
var out = wf('Hi', 'there,', 'gang');
print(out); // Hi there, gang!

설계 의도는 다음과 같습니다.

  • 클래스 인스턴스에 함수 호출 형태를 부여한다.
  • 파라미터와 반환 타입은 일반 함수와 동일하다.
  • 함수처럼 동작하지만 상태를 가질 수 있다.

결론: 함수처럼 호출 가능하면서도 클래스의 상태와 메서드를 활용할 수 있습니다.

Watch out#

WARNING

call()은 “함수”가 아니라 “메서드 호출 문법을 제공하는 객체”입니다.
즉, 인스턴스 자체의 타입은 클래스이며, 필요한 경우에는 call()의 시그니처를 기준으로 타입을 맞춰야 합니다.

var wf = WannabeFunction();
print(wf is WannabeFunction); // true: wf는 클래스 인스턴스다.
print(wf('Hi', 'there,', 'gang')); // call()이 실행된다.

한계#

dot shorthand는 다음 제약을 가집니다.

  • Dart 버전 3.10 이상이 필요하다.
  • context type10이 분명해야 한다.
  • 표현식 문장은 .로 시작할 수 없다.
// 가능: 타입 문맥이 명확함
int port = .parse('8080'); // int.parse('8080')
// 불가능: 표현식 문장은 .으로 시작 불가
class Logger {
static void log(String message) => print(message);
}
void main() {
// .log('Hello'); // 컴파일 오류
Logger.log('Hello'); // 명시적으로 작성
}

Footnotes#

  1. parameter(파라미터): 함수가 받는 입력값을 정의하는 변수다.

  2. function type(함수 타입): 함수의 입력과 반환 형태를 나타내는 타입이다.

  3. typedef(타입 별칭): 긴 타입 이름을 짧게 부르는 별칭이다.

  4. callable object(호출 가능한 객체): call() 메서드를 구현해 함수처럼 호출할 수 있는 객체다.

  5. signature(시그니처): 함수의 이름, 파라미터, 반환 타입으로 구성된 선언부다.

  6. dot shorthand(점 표기 축약): 점으로 시작하는 간결한 호출 표현식이다.

  7. required positional parameter(필수 위치 파라미터): 호출 시 순서대로 반드시 전달해야 하는 파라미터다.

  8. named parameter(이름 있는 파라미터): 중괄호로 감싸 이름을 붙여 전달하는 파라미터다.

  9. optional positional parameter(선택적 위치 파라미터): 대괄호로 감싸 선택적으로 전달하는 파라미터다.

  10. context type(문맥 타입): 표현식이 놓인 위치에서 기대되는 타입을 뜻한다.

공유

이 글이 도움이 되었다면 다른 사람과 공유해주세요!

Dart 튜토리얼 3편: 함수의 구조를 잡는 법
https://moodturnpost.net/posts/dart/dart-functions-call-model/
작성자
Moodturn
게시일
2026-01-04
Moodturn

목차