티스토리 뷰

반응형

함수

다트는 진정한 객체지향 언어이기 때문에 함수도 객체이고, 함수는 Function이라는 타입을 갖는다. 함수는 객체이므로 변수에 할당하거나 다른 함수에 인수로 전달할 수도 있다.

bool isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

 

타입을 생략해도 타입 추론으로 함수는 여전히 작동하지만 추천하지 않는다.

isNoble(atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

 

 

 

화살표 구문

함수 본문에 표현식이 하나라면 화살표 구문(arrow syntax)를 사용할 수 있다.

bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

 

 

 

매개변수

다트에서 지원하는 매개변수의 종류는 3가지가 있다.

  • 위치 매개변수 (positional arguments)
  • 명명된 매개변수 (named arguments)
  • 선택적 위치 매개변수 (optional positional arguments)

 

매개변수는 다음과 같은 조합으로 사용할 수 있다.

  • 위치 매개변수
  • 위치 매개변수 + 명명된 매개변수
  • 위치 매개변수 + 옵셔널 위치 매개변수
  • 명명된 매개변수
  • 옵셔널 위치 매개변수

 

 

 

명명된 매개변수

명명된 매개변수는 required로 표시하지 않으면 선택사항이 된다. 매개변수를 {}로 감싸면 명명된 매개변수가 된다. 기본값이 없으면 널-가능 타입이어야 한다.

/// bold과 hidden은 선택사항이 된다.
void enableFlags({bool? bold, bool? hidden}) {...}


함수를 호출할 때 "매개변수이름: 값"형식으로 명명된 매개변수를 지정할 수 있다.

enableFlags();
enableFlags(bold: true);
enableFlags(bold: true, hidden: false);
enableFlags(hidden: false, bold: true);
enableFlags(hidden: false);

 

=을 사용하여 기본값을 지정할 수 있다. 기본값은 컴파일-타임 상수여야 한다.

void enableFlags({bool bold = false, bool hidden = false}) {...}
// bold는 true가 될 것이고, hidden은 false가 될 것입니다.
enableFlags(bold: true);

 

명명된 매개변수를 필수로 사용하게 하려면 required를 사용한다.

const Scrollbar({required Widget child});


required를 사용해도 널-가능 타입을 사용해서 null을 입력받을 수 있다.

const Scrollbar({required Widget? child});

 

위치 매개변수와 명명된 매개변수가 같이 사용될 때, 명명된 매개변수를 먼저 인수로 전달할 수 있다.

void repeat(void Function() operation, {int? times}) { ... }
repeat(times: 2, () {
  ...
});

 

 

 

선택적 위치 매개변수

매개변수를 []로 감싸면 선택적 위치 매개변수가 된다. 기본값을 제공하지 않으면 널-가능 타입이어야 한다.

String say(String from, String msg, [String? device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}

 

선택적 위치 매개변수를 사용하지 않고 say 함수를 호출하는 예제이다.

assert(say('Bob', 'Howdy') == 'Bob says Howdy');

 

다음은 위치 매개변수를 사용하여 호출한 예제이다.

assert(say('Bob', 'Howdy', 'smoke signal') ==
    'Bob says Howdy with a smoke signal');

 

=를 사용하여 기본값을 지정할 수 있다. 기본값은 컴파일-타임 상수여야 한다.

String say(String from, String msg, [String device = 'carrier pigeon']) {
  var result = '$from says $msg with a $device';
  return result;
}
assert(say('Bob', 'Howdy') == 'Bob says Howdy with a carrier pigeon');

 

 

 

main() 함수

모든 앱은 최상위 main() 함수를 가져야 한다. 이 함수가 앱의 진입점(entry point) 역할을 한다. main() 함수는 void를 반환하며, 인수 전달이 필요하면 List<String> 매개변수를 사용한다.

void main() {
  print('Hello, World!');
}

 

 

다음은 인수를 사용하는 main() 함수이다.

void main(List<String> arguments) {
  print(arguments);

  assert(arguments.length == 2);
  assert(int.parse(arguments[0]) == 1);
  assert(arguments[1] == 'test');
}

 

위 코드가 args.dart 파일일 경우, 커맨드 라인에서 다음과 같이 사용된다.

dart args.dart 1 test

 

 

 

함수를 일급 객체로 사용하기

함수는 일급 객체이므로 다른 함수의 매개변수로 전달할 수 있다.

void printElement(int element) {
  print(element);
}
var list = [1, 2, 3];
list.forEach(printElement);

 

함수를 변수에 할당할 수도 있다.

var func = printElement;

 

Function으로 명시적으로 타입을 지정할 수도 있다.

void Function(int) func = printElement;

 

 

 

익명 함수

대부분의 함수는 main()처럼 이름이 지정된 함수이다. 익명 함수(또는 람다, 클로저)라고 불리는 이름이 없는 함수를 생성할 수도 있다.

 

익명 함수는 일반 함수와 유사한 구조를 갖는다.

list.forEach((int element) {
  print(element);
});

 

다트는 타입 추론이 되므로 익명 함수의 매개변수 타입을 사용하지 않아도 된다.

list.forEach((element) {
  print(element);
});

 

위의 코드는 화살표 표기법으로 더 간결하게 만들 수 있다.

list.forEach((element) => print(element));

 

 

 

렉시컬 스코프 (Lexical scope)

다트는 렉시컬 스코프 언어로, 변수의 범위가 코드의 레이아웃을 기반으로 정해진다. 중괄호를 따라 바깥으로 나가면서 변수가 범위 내에 있는지 확인할 수 있다.

 

다음 코드에서 nestedFunction()이 모든 레벨에서 상위 레벨까지의 변수를 사용할 수 있다는 것에 주목하자.

bool topLevel = true;

void main() {
  var insideMain = true;

  void myFunction() {
    var insideFunction = true;

    void nestedFunction() {
      var insideNestedFunction = true;

      assert(topLevel);
      assert(insideMain);
      assert(insideFunction);
      assert(insideNestedFunction);
    }
  }
}

 

 

 

렉시컬 클로저 (Lexical closures)

클로저는 원래 스코프를 벗어나 사용되더라도 랙시컬 스코프에서 변수에 접근할 수 있는 함수 객체이다. 함수는 주변 스코프에서 정의된 변수를 클로저로 닫을 수 있다.

 

다음 예에서 makeAdder()는 변수 addBy를 캡처한다. 그래서 반환된 함수가 어디로 가든 캡처된 addBy를 기억할 수 있다.

/// 함수의 인수에 [addBy]를 더하는 함수를 반환합니다.
Function makeAdder(int addBy) {
  return (int i) => addBy + i;
}

void main() {
  // 2를 더하는 함수를 생성합니다.
  var add2 = makeAdder(2);

  // 4를 더하는 함수를 생성합니다.
  var add4 = makeAdder(4);

  assert(add2(3) == 5);
  assert(add4(3) == 7);
}

 

 

 

함수 동등성 테스트

다음은 최상위 함수, 정적 메서드, 인스턴스 메서드의 동등성을 확인하는 예제이다.

void topLevelFunc() {}  // 최상위 함수

class MyClass {
  static void staticFunc() {} // 정적 메서드
  void instanceFunc() {} // 인스턴스 메서드
}

void main() {
  // 최상위 함수를 비교한다.
  var _topLevelFunc = topLevelFunc;
  assert(_topLevelFunc == topLevelFunc);

  // 정적 메서드를 비교한다.
  var _myClassStaticFunc = MyClass.staticFunc;
  assert(_myClassStaticFunc == MyClass.staticFunc);

  var myClass1 = MyClass();
  var myClass2 = MyClass();
  var myClass1InstanceFunc = myClass1.instanceFunc;
  var myClass2InstanceFunc = myClass2.instanceFunc;

  // 같은 인스턴스의 메서드를 비교하므로 동등하다.
  assert(myClass1.instanceFunc == myClass1InstanceFunc);

  // 서로 다른 인스턴스의 메서드를 비교하므로 동등하지 않다.
  assert(myClass1InstanceFunc != myClass2InstanceFunc);
}

 

 

 

Tear-off

괄호 없이 함수, 메서드, 또는 명명된 생성자를 참조하면 Dart는 tear-off를 생성한다. tear-off는 동일한 매개변수를 취하고 호출하는 클로저이다.

 

클로저가 동일한 매개변수를 받아 명명된 함수를 호출해야 하는 경우, 익명 함수를 만들지 말고 tear-off를 사용하자.

var charCodes = [68, 97, 114, 116];
var buffer = StringBuffer();

 

좋음 : tear-offs를 사용하는 경우

// Function tear-off
charCodes.forEach(print);

// Method tear-off
charCodes.forEach(buffer.write);


나쁨 : tear-offs를 사용하지 않고, 익명 함수로 사용하는 경우

// Function lambda
charCodes.forEach((code) {
  print(code);
});

// Method lambda
charCodes.forEach((code) {
  buffer.write(code);
});

 

 

 

반환 값

모든 함수는 값을 반환한다. 반환 값이 지정되지 않는 경우, return null;이 암시적으로 함수 본문 끝에 추가된다.

foo() {}

assert(foo() == null);


함수에서 여러 값을 반환하려면 레코드를 사용하면 된다.

(String, int) foo() {
  return ('something', 42);
}

 

 

 

제너레이터

값의 연속을 지연하여 생성해야 할 때, 제너레이터를 사용하는 것이 좋다.

 

다트는 두 가지 종류의 제너레이터 함수를 내장하고 있다.

  • 동기 제너레이터 : Iterable 객체를 반환한다.
  • 비동기 제너레이터 : Stream 객체를 반환한다.

 

동기 제너레이터 함수를 구현하려면 함수 본문을 sync*로 표시하고, yield 문을 사용하여 값을 전달한다.

Iterable<int> naturalsTo(int n) sync* {
  int k = 0;
  while (k < n) yield k++;
}

 

비동기 제너레이터 함수를 구현하려면 함수 본문을 async*로 표시하고, yield 문을 사용하여 값을 전달한다.

Stream<int> asynchronousNaturalsTo(int n) async* {
  int k = 0;
  while (k < n) yield k++;
}

 

재귀적인 제너레이터인 경우, yield*를 사용하여 성능을 향상시킬 수 있다.

Iterable<int> naturalsDownFrom(int n) sync* {
  if (n > 0) {
    yield n;
    yield* naturalsDownFrom(n - 1);
  }
}

 

 

 

외부 함수

외부 함수는 선언과 별도로 구현되는 함수이다.

함수 선언 앞에 external 키워드를 포함하여 다음과 같이 선언한다.

external void someFunc(int i);

 

외부 함수의 구현은 다른 다트 라이브러리에서 오거나, 더 일반적으로 다른 언어에서 올 수 있다.

외부 함수는 최상위 함수, 인스턴스 메서드, getter 또는 setter, 또는 비-리디렉팅 생성자가 될 수 있다.

인스턴스 변수도 외부일 수 있는데, 이는 외부 getter와 변수가 final이 아닌 외부 setter와 동일하다.

반응형
댓글
공지사항