[Dart/Document] A tour of the Dart language - 6. Functions

작성 날짜:

최근 업데이트 날짜:

목차로 돌아가기

Functions

Dart는 객체지향(object-oriented) 언어이기 때문에 함수(function)들도 오브젝트(object)이며 타입을 가진다. 이것은 함수가 변수(variable)에 할당되거나 다른 함수에 argument로 보내질 수 있다는 것을 의미한다. Dart 클래스의 인스턴스를 함수처럼 부를 수도 있다.

아래는 함수를 구현한 예시이다.

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

Effective Dart는 public API에 대해서 type annotation을 권장하지만, 함수에 타입을 적지 않아도 문제 없다.

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

하나의 expression만 포함하고 있는 함수는 짧은 구문(shorthand syntax)을 통해 간편히 쓸 수 있다.

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

=> expr 구문은 { return expr; } 를 줄인 것이다. => 표기(notation)은 화살표 구문이라고도 말한다.

Note: 화살표(=>)와 세미콜론(semicolon) 사이에는 expression만 들어갈 수 있다. statement는 넣을 수 없다. 예를 들어 if statement는 넣을 수 없지만 conditional expression는 사용할 수 있다.

Parameters

함수는 required positional parameter를 몇 개던 가질 수 있다. 그 뒤에는 named parameter나 optional positional parameter를 넣을 수 있다. 하지만 named parameter와 optional positional parameter 둘 다를 넣을 수는 없다.

Note: 일부 API(특히 Flutter 위젯 constructor)는 필수 parameter에 대해서도 named parameter만 사용한다. 자세한 내용은 다음 섹션에서 다룬다.

함수로 argument를 보낼 때나 함수 parameter를 정의할 때 trailing comma(마지막 아이템 뒤의 쉼표)를 사용할 수 있다.

Named parameters

Named parameter는 필수라고 명시되지 않는 이상 기본적으로는 선택사항이다.

함수를 부를 때, paramName: value를 사용해서 named parameter를 지정할 수 있다. 아래는 예시이다.

enableFlags(bold: true, hidden: false);

함수를 정의할 때, {param1, param2, …}를 사용해서 named parameter를 지정한다.

/// [bold]와 [hidden]를 설정.
void enableFlags({bool bold, bool hidden}) {...}

비록 named parameter가 optional parameter의 종류이지만, @required로 표기해서 parameter가 필수라고 표시할 수 있다. 유저는 필수 parameter에 대해서 무조건 값을 제공해야한다. 아래는 예시이다.

const Scrollbar({Key key, @required Widget child})

만약 child argument를 지정하지 않고 Scrollbar를 만들려고 한다면 analyzer가 문제가 생겼다고 말해줄 것이다.

@required 표기를 사용하기 위해서는 meta package를 사용하면 된다.

Optional positional parameters

[] 괄호로 감싼 함수의 parameter는 optional positional parameter가 된다.

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

아래는 이 함수를 optional parameter 없이 부르는 예시이다.

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

아래는 3번째 parameter와 함께 이 함수를 부르는 예시이다.

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

Default parameter values

named parameter와 positional 변수에 디폴트(default) 값을 정의하기 위해 =를 사용할 수 있다. 디폴트 값은 컴파일 타임 constant여야만 한다. 만약 디폴트 값이 따로 제공되지 않는다면 디폴트 값은 자동으로 null이 된다.

아래는 named parameter의 디폴트 값을 설정하는 예시이다.

/// [bold]와 [hidden]를 설정.
void enableFlags({bool bold = false, bool hidden = false}) {...}

// bold는 true, hidden은 false가 될 것이다.
enableFlags(bold: true);

Deprecation note: 옛날 코드는 named parameter에 디폴트 값을 설정하기 위해 = 대신에 :를 사용했을 수 있다. 그 이유는 원래 named parameter를 위해 :만 지원되었기 때문이다. 이는 더 이상 지원되지 않을 수 있으므로 =를 사용해서 디폴트 값을 지정하는 것을 추천한다.

아래의 예시는 positional parameter에 디폴트 값을 세팅하는 방법을 보여준다.

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');

디폴트 값으로 list나 map을 보낼 수도 있다. 아래의 예시에서 doStuff()를 정의하는데, 이 함수는 list parameter에 디폴트 list, gifts parameter에 디폴트 map을 지정한다.

void doStuff(
    {List<int> list = const [1, 2, 3],
    Map<String, String> gifts = const {
      'first': 'paper',
      'second': 'cotton',
      'third': 'leather'
    }}) {
  print('list:  $list');
  print('gifts: $gifts');
}

The main() function

모든 앱은 최상위 단계의 main() 함수를 가지고 있다. main() 함수는 앱의 시작점 역할을 한다. 또한 main() 함수는 void를 return하고 argument에 대한 optional List<String> parameter를 가진다.

아래는 웹 앱을 위한 main() 함수의 예시이다.

void main() {
  querySelector('#sample_text_id')
    ..text = 'Click me!'
    ..onClick.listen(reverseText);
}

Note: 위의 코드에서 .. 구문은 cascade로 불린다. cascade를 통해 한 오브젝트의 맴버에 대해 여러 operation을 실행할 수 있다.

아래는 argument를 사용하는 command-line 앱의 main() 함수 예시이다.

// 앱을 이렇게 실행한다:  dart args.dart 1 test
void main(List<String> arguments) {
  print(arguments);

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

command-line argument를 정의하고 parse하기 위해 args library를 사용할 수 있다.

Functions as first-class objects

아래와 같이 함수를 다른 함수의 parameter로 넘길 수 있다.

void printElement(int element) {
  print(element);
}

var list = [1, 2, 3];

// printElement를 parameter로 넘긴다.
list.forEach(printElement);

또한 아래와 같이 함수를 변수로 넘길 수 있다.

var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');

위의 예시는 anonymous 함수를 사용한다. 이에 대한 내용은 바로 다음 섹션에서 자세히 나와있다.

Anonymous functions

대부분의 함수들은 main()이나 printElement()처럼 이름이 있다. 하지만 이름이 없는 anonymous 함수도 만들 수 있다. anonymous 함수는 lambda나 closure라고 불리기도 한다. anonymous 함수도 변수에 할당할 수 있다. 예를 들어 anonymous 함수를 collection에 더하거나 뺄 수도 있다.

anonymous 함수은 괄호 안에서 쉼표와 optional 타입 표기로 구분되는 0개 이상의 parameter를 가진다는 것에서 named 함수와 비슷하다.

아래의 코드 블록은 함수의 body를 포함한다.

([[Type] param1[, …]]) {
  codeBlock;
};

아래의 예시는 타입이 적혀있지 않은 parameter인 item을 가지는 anonymous 함수를 정의한다. 해당 함수는 목록의 각 item 마다 불리며 그때마다 인덱스(index)에 해당하는 값을 포함하는 문자열(string)을 출력한다.

var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
  print('${list.indexOf(item)}: $item');
});

위의 코드를 실행하게 되면 아래와 같은 결과가 나온다.

0: apples
1: bananas
2: oranges

만약 함수가 단 하나의 statement만 포함하고 있다면 화살표 표기를 사용해서 줄일 수 있다.

var list = ['apples', 'bananas', 'oranges'];
list.forEach(
    (item) => print('${list.indexOf(item)}: $item'));

Lexical scope

Dart는 lexically scoped 언어로서, 변수의 범위가 코드의 레이아웃에 의해 단순하고 정적으로 결정된다. {} 괄호를 따라 보면 변수의 범위를 쉽게 알 수 있다.

아래는 각 {} 괄호 범위마다 변수가 있는 nested 함수의 예시이다.

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);
    }
  }
}

nestedFunction()가 각 레벨에서부터 최상위 레벨에서까지 변수를 어떻게 사용할 수 있는지를 주의하자.

Lexical closures

closure는 함수가 본래의 범위 밖에서 사용되더라도 함수의 lexical 범위 안에 있는 변수에 접근할 수 있는 함수 오브젝트이다.

함수는 아래의 예에서 makeAdder()addBy 변수를 가지고 있다. 리턴된 함수는 어디에 있던 addBy를 기억한다.

/// 함수의 argument에 [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);
}

Testing functions for equality

아래는 equality에 대한 최상위 레벨 함수, 정적 메소드, 인스턴스 메소드의 예시이다.

void foo() {} // 최상위 레벨 함수.

class A {
  static void bar() {} // 정적 메소드.
  void baz() {} // 인스턴스 메소드.
}

void main() {
  var x;

  // 최상위 레벨 함수를 비교한다.
  x = foo;
  assert(foo == x);

  // 정적 메소드를 비교한다.
  x = A.bar;
  assert(A.bar == x);

  // 인스턴스 메소드를 비교한다.
  var v = A(); // A의 인스턴스 #1
  var w = A(); // A의 인스턴스 #2
  var y = w;
  x = w.baz;

  // 이 closure들은 같은 인스턴스(#2)를 참조하기 때문에 같다.
  assert(y.baz == x);

  // 이 closuer들은 다른 인스턴스를 탐조하기 때문에 다르다.
  assert(v.baz != w.baz);
}

Return values

모든 함수는 값을 리턴한다. 만약 리턴 값이 적혀있지 않다면 return null ; statement가 자동적으로 들어간다고 보면 된다.

foo() {}

assert(foo() == null);

태그:

카테고리:

최근 업데이트 날짜:

댓글남기기