欲速不達

일을 급히 하고자 서두르면 도리어 이루지 못한다.

Fantastic AI, Fantastic World

CS | Computer Science/FE | Front-end

[App] Dart 4. Functions

_껀이_ 2024. 4. 19. 10:24
728x90
반응형

Functions

Dart의 함수는 javascript와 유사한 점이 많다.

함수 명 앞에 return 값의 타입에 따라 void, String 등을 명시하는 것이나 {}를 사용하는 등 공통점이 있다.

공통점을 제외하고 Dart만의 특징도 있으니 이번 포스팅에서는 Dart의 함수에 대해 정리해보자.

 


1) Defining a Functions

Dart의 함수는 크게 두 종류로 구분할 수 있다.

return 값이 없는 함수(void)와 return 값이 있는 함수(String, Int, ...)이다.

 

또, Dart는 필수적으로 main 함수를 가진다.

art의 comiler는 .dart 파일을 실행할 때 자동적으로 main 함수를 찾아 실행하며, main 함수 밖에 작성한 다른 함수들을 실행하기 위해서는 main 함수에서 호출해야 한다.

 

1-1)

void sayHello(String name) {
  // void는 이 함수가 부가적인 효과만 있고 아무것도 return 하지 않는다는 것을 말함
  // console에 print만 하고 return 하지 않음
  print("Hello, $name nice to meet you!");
}

void main() {
  // sayhello 함수는 main 밖에 작성해도 되지만, main 안에서 호출해줘야 함
  sayHello('kuuneeee');
}

 

위 코드의 sayHello 함수는 void이다.

즉, return 값이 존재하지 않고, 부가적인 효과(print)만 있다.

이 경우, main에서 sayHello를 호출하면 sayHello 내부의 print 문이 출력된다.

 

1-2) 

String sayHello(String name) {
  // String을 return하는 함수 sayHello
  return "Hello, $name nice to meet you!";
}

void main() {
  // sayhello 함수는 main 밖에 작성해도 되지만, main 안에서 호출해줘야 함
  print(sayHello('kuuneeee'));
}

 

위의 코드에서 sayHello는 String을 반환하는 함수이다.

함수명 앞에 return 값의 타입인 String을 명시하고 return으로 String을 반환한다.

이 경우, sayHello 호출 시 값을 가지고 있을 뿐이므로 main 함수에서 호출할 때 print문으로 감싸줘야 String이 출력된다.

 

기본적인 함수는 위와 같이 만든다.

하지만, 단순히 return 값을 반환하는 식의 간단한 함수를 쓰기에 3줄이나 필요한 건 리소스 낭비일 수 있다.

 

그래서 그러한 함수를 dart는 아래와 같이 간단하게 표현할 수 있다.

 

String sayHello(String name) => "Hello, $name nice to meet you!";
// => : fat arrow syntax -> 곧바로 return하는 것

 

또 다른 예제로는

 

num plus(num a, num b) => a + b;

 

이런식으로 수식도 간단하게 표현할 수 있다.

 

main 함수를 사용해 호출해도 결과는 같다.

 


2) Named Parameters

위의 1)에서 함수를 지정할 때 인자로 String name을 1개 받아오는 것을 볼 수 있다.

그렇다면 여러개의 인자를 받는 건 어떨까

파이썬이나 다른 언어와 마찬가지로 그대로 받아오면 된다.

 

2-1) 한번에 여러가지 파라미터를 입력하는 경우 : positional parameters

String sayHello(String name, int age, String country) {
  return "Hello, $name. you are $age, and you come from $country.";
}

void main() {
  print(sayHello('kuuneeee', 32, 'korea'));
}

 

 

인자를 넣을 때 순서대로 String name, int age, String country를 넣어준다.

호출할 때도 같은 순서대로 인자를 넣어준다.

인자의 순서가 각각의 의미를 가지기 때문에 positional parameters라고 부른다.

 

이해하기 쉽다.

하지만 단점은 순서를 그대로 입력해야된다는 점해당 요소들이 어떤 의미를 갖는지 알아야 한다는 점에서 인자의 개수가 많아질수록 입력하는 게 까다로워 진다는 점이다.

 

그래서 복잡할수록 named parameters를 사용하는 게 바람직하다.

 

2-2) named parameters : default setting

String sayHello({
  String name = 'anon',
  int age = 99,
  String country = 'wakanda',
}) {
  // main에서 named argument 사용하는 경우, 함수 입력 부분에 {} 해야함
  // 단순히 {}만 하면 안됨 -> null safety 고려해야함 -> default value 지정
  return "Hello, $name. you are $age, and you come from $country.";
}

void main() {
  print(sayHello(
    // named argument
    age: 32,
    country: 'korea',
    name: 'kuuneeee',
  ));
}

 

호출 시 named parameter는 key와 value 형태로 입력한다.

age:32, country:'korea',name:'kuuneeee' 이런식으로 말이다.

이렇게 함으로써 더 이상 순서를 고려하지 않아도 어떤 key에 어떤 value가 들어가는지를 알기 때문에 입력만 하면 된다.

 

함수 정의 부분에서는 받아오는 인자를 {}로 감싸준다.

{String name, int age, String country} 이렇게 말이다.

 

이렇게 하면 named parameter를 사용한 함수가 된다.

 

 

 

여기서 문제는

dart는 null safety를 항상 고려한다는 점이다.

즉, 입력 시에 null이 들어오는 것까지 고려해줘야 한다.

 

타입명에 ?를 사용해도 좋지만, 위의 코드처럼 default 값을 지정해줘도 된다.

?만 사용하면 null일때 처리하는 코드를 작성해야 한다.

 

2-3) named parameters : required

String sayHello({
  required String name,
  required int age,
  required String country,
}) {
  // required modifier
  // required는 입력값이 없으면 에러가 발생함
  return "Hello, $name. you are $age, and you come from $country.";
}

void main() {
  print(sayHello(
    age: 12,
    country: 'korea',
    name: 'kuuneeee',
  )); // required로 지정된 변수들이 입력되지 않으면 에러가 발생함
}

 

null 값을 입력하는 게 문제이므로, 아예 required라고 지정하는게 안전하다.

requied 변수는 입력 시에 null이면 에러가 발생하기 때문에 반드시 입력하게끔 해준다. 

 


3) Optional Positional Parameters

Optional Positional Parameters는 말 그대로 positional parameter에 옵션을 부여하는 것이다.

 

String sayHello(String name, int age, [String? country = 'korea']) =>
    'Hello, $name. you are $age, and you come from $country.';
// country null일 수도 있고 아닐 수도 있고(-> ?), null이면 default value인 korea를 사용함

void main() {
  var result = sayHello('kuuneeee', 32);
  print(result);
}

 

입력 인자 중에서 옵션을 주고자 하는 인자를 []로 감싸고 null 값이 들어올 때를 감안하여 default 값을 지정해준다.

위의 코드에서 [String? country = 'korea']는 country가 null이면 korea를 입력한다는 의미이다.

 


4) QQ Operator

QQ Operator는 Question Question Operator로 ?? 연산자를 말한다.

바로 예시를 보자.

 

String capitalizeName(String name) => name.toUpperCase();

 

입력으로 받아로는 name을 대문자로 변환하는 짧은 코드이다.

 

이때 null 값도 입력할 수 있게 코드를 작성하면 다음과 같다.

 

String capitalizeName(String? name) {
  // 길게 쓰면
  if (name != null) {
    return name.toUpperCase();
  }
  return 'ANON';
}

 

String? name으로 작성해서 null 값을 받아올 수 있게 했고, null이 아니면 name.toUpperCase() 메서드를 실행한다. null이면 'ANON'을 출력한다.

 

길다. 줄여보자.

 

String capitalizeName(String? name) =>
    name != null ? name.toUpperCase() : 'ANON';
    
// name이 null이 아니면 ? .toUpperCase(), null이면 'ANON'
// 파이썬 if else 문이랑 유사

 

if 문 대신 ? 연산자를 사용한다.

? 연산자는 앞의 수식이 true면 뒤를 실행하는 것이다.

즉, A ? B는 A가 true면 B 실행이다.

 

위의 코드는 "name이 null이 아니면(true) name.toUpperCase() 실행하고, null이면 'ANON'  반환"을 의미한다.

파이썬의 if else 문 one-line 과 같은 개념이다.

 

그런데 더 줄여볼 수 있을까?

 

String capitalizeName(String? name) => name?.toUpperCase() ?? 'ANON';
// name이 null이 아니면 .toUperase가 null이 아니니깐 그대로
// name이 null이면 .toUperase가 null이니깐 ANON을 반환

 

=> 를 사용하여 한줄로 작성하면서 ? 연산자와 ?? 연산자로 조건을 달았다.

?? 연산자는 A ?? B일때, "A가 null이면 B를 반환한다"를 의미한다.

 

위의 코드에서는 "name?.toUpperCase()가 null이면 'ANON'을 반환한다"를 의미한다.

물론 null이 아니면 그대로 name.toUpperCase()를 실행한다는 의미이다.

 

 

4-1) QQ Equal Operator

 

추가로, QQ Equal Operator( Question Question Eauals Operatior) 라는 것도 있다.

 

QQ Assignment Operatior(Question Question Assignment Operatior)라고도 하며, ??= 연산자를 의미한다.

 

void main() {
  String? name;
  name ??= 'kuneeee'; // name이 null이면 name = 'kuneeee' 할당
  // name = null;
  name ??= 'another'; // 여기서는 에러 -> name에 'kuuneeee'가 할당되어 있으므로, null이 되지 않으니까
  print(name);
}

 

A ??= B는 "A가 null 이면 B를 할당"하는 것이며, 주로 변수 선언시에 사용한다.

 

위의 코드를 보면 String? name 으로 선언하여 null 상태인 변수 name이 있다.

그 후에 name ??= 'kuuneeee'로 name이 null이면 값을 할당 해준다.

그 다음에 name ??= 'another'를 실행하려면 에러가 발생한다. 이미 name에 할당된 값이 있기 때문이다.

이를 실행하려면 사이에 name = null을 추가해주면 된다. 


5) Typedef

Typedef는 자료형이 길거나 복잡해서 작성하기 어렵거나 헷갈릴 때, 별칭(alias)를 지정해주는 방법이다.

 

// 1)
List<int> reverseListOfNumbers(List<int> list) {
  var reversed =
      list.reversed; // .reversed하면 iterable이 되서 .toList()를 해줘야 list로 반환함
  return reversed.toList();
}

typedef ListOfInts = List<int>; 
// 타입에 대해 따로 별명을 지어주고

// 2) 
ListOfInts reverseListOfNumbers(ListOfInts list) {
  // 이렇게 바꿀 수 있음
  var reversed =
      list.reversed; // .reversed하면 iterable이 되서 .toList()를 해줘야 list로 반환함
  return reversed.toList();
}

void main() {
  print(reverseListOfNumbers([1, 2, 3]));
}

 

위 코드의 1)과 같이 리스트의 값을 뒤집어서 출력하는 함수가 있다고 하자.

이때 return 값은 List<int>이다. 

이를 typedef를 사용해서 ListOfInts라고 alias를 만들고 2)처럼 데이터 타입명을 대체할 수 있다.

 

Map도 같은 방식으로 alias를 만들 수 있다.

 

typedef UserInfo = Map<String, String>;

String sayHi(UserInfo userInfo) {
  return "Hi, ${userInfo['name']}";
}

void main() {
  print(sayHi({"name": 'kuuneeee'}));
}

 

여기서 중요한 점은 typedef는 간단한 데이터의 alias를 만들때 사용한다는 거다.

조금 더 복잡한 데이터의 구조화를 위해서는 class를 사용하는 것이 여러모로 바람직하다.

728x90
반응형

'CS | Computer Science > FE | Front-end' 카테고리의 다른 글

[App] Dart 5. Classes  (1) 2024.04.19
[App] Dart 3. Data Types  (0) 2024.04.18
[App] Dart 2. Variables  (0) 2024.04.18
[App] Dart 1. say "Hello World!"  (0) 2024.04.18