Dart 기본 문법 : Function

윤뿔소·2023년 5월 4일
0

Dart / Flutter

목록 보기
4/18

오랜만이다.

이번 시간엔 다트의 함수 문법에 대해 알아보긋다.

Dart Function

가장 흔하게 보이면서 필수인 main도 다트 함수다. 앞에 void, String 등의 함수 리턴 타입와 소괄호, 중괄호가 있으면 함수다.

void sayHello(String name) {
  print("Hello $name nice to meet you!");
}

void main() {
  sayHello('뿔소');
}

이렇게 말이다. 당연히 실행해주려면 main함수에 넣어서 파라미터를 넣어준 후 실행해주자.

함수 반환 타입

아까 본 void를 칭한다. 예시를 보며 설명하겠다.

void sayHello(String name) {
  print("Hello $name nice to meet you!");
}
// 에러
void sayHelloError(String name) {
  return "Hello $name nice to meet you!";
}
String sayHelloString(String name) {
  return "Hello $name nice to meet you!";
}

void main() {
  ...
}

차이점을 알겠는가? 2번째 sayHello는 에러가 난다.

그 외 sayHello들은 에러가 안났다. 위 사례를 보며 설명하겠다.

  1. void : 콘솔에 출력만 하는 부가적인 효과만 나왔다. 즉, 값을 반환하지 않는 함수를 의미한다. 주로 콘솔에 출력하거나 상태를 업데이트하는 등의 작업을 수행하는 함수로 사용된다.
  2. String 등 타입 지정 : 그 타입의 값을 반환해야하는 함수다. return이 꼭 필요하다는 뜻.

다트 함수의 파라미터

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

문자열을 리턴해야하는 함수 sayHello를 예시로 들었다. 그와 더불어 파라미터는 문자열 name, countryinteger를 받는 age(numdouble도 받기에 int로 쓴 모습)로 구성돼있다.

그러면 당연히 함수 호출을 할 때, 지정 null값이 아닌 이상 파라미터를 전부 만들어줘야한다.

sayHello('뿔소', 25, "Korea");

Positional Parameter

파라미터 기본 형태이다. 함수 파라미터에 그냥 소괄호()만 있고 작성해준 것이다. 이때는 호출부의 Argument의 순서가 중요해진다.

String sayHello(String name, int age, String country) {
  return "Hello $name, you are $age, and you come from $country";
}
sayHello('뿔소', 25, "Korea");

위처럼 말이다. 근데 문제가 순서를 알아야하고, 직관적이지 않다는 문제가 있다.

그래서 주관적으로 파라미터가 2개 이하면 Positional을 써도 되지만 3개가 넘어가면 Named Parameter를 쓴다. 위 문제들을 해결할 수 있는 파라미터 작성법이다.

Named Parameter

sayHello 함수 호출부를 봤을 때 어떤 역할인지 잘 모르지 않는가? 직관적이지 않고, 호출할 때 복잡한 함수면 직접 찾아가서 뭐가 들어가는지 등등을 봐야하니까.

그럴 때 Named Parameter를 쓰자. 간단하다. 선언부 파라미터에 중괄호, 호출부 파라미터에 이름을 붙여주면 된다.

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

void main() {
  print(sayHello(
    age: 25, 
    name: '뿔소', 
    country: "Korea"
  ));
}

이렇게 말이다! 호출부에 순서를 선언부의 파라미터와 다르게 써도 괜찮다. 훨씬 직관적이고 편하다.

심지어 Hover 했을 때 저렇게 간단한 설명도 볼 수 있으니 어찌 안쓰랴!! 플러터에서 많이 애용하는 문법이다.

Null safety의 습격

근데 만약 위 함수를 그대로 쓰면 에러가 날 것이다.

이렇게 말이다! 사용자가 null이나 함수 호출부에서 Argument를 입력하지 않는 불상사를 막기 위해서 저런 에러가 나는 것이다. 다양한 해결법이 있다.

파라미터에 default value 할당해주기

가장 직관적인 방법이다.

String sayHello({
  String name = "철수", 
  int age = 0, 
  String country = "Korea"
}) {
  return "Hello $name, you are $age, and you come from $country";
}

void main() {
  print(sayHello(name: '뿔소', age: 25, country: "Korea"));
}

저렇게 파라미터에 기본 값을 할당해주는 것이다. 즉, 아무것도 입력하지 않아도 null이 되지 않아 에러가 안나는 것이다.

하지만 sayHello()로 끝내도 아무런 문제가 없기도 하고, 값이 맨날 할당되기에 버그가 발생하기 좋은 환경이다. 다음 방법을 보자.

Required 지정하기

파라미터 앞에 required를 지정해서 호출할 때 이 파라미터가 필수적으로 들어가야한다고 표시하는 기능이다.

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

이렇게 말이다. 즉, null이 아니란 표시고, '호출부에 작성하지 않으면 에러나게 해주세요~'라는 표시다.

위 사진처럼 호출부에 에러를 줘 컴파일 되지 않게 막는 것이다. 되게 편한 기능!

Nullable하다고 표시하기

3번째 방법은 전편에서 봤다싶이 변수 타입 지정자에 ?를 붙이는 것이다. 아예 nullable하다고 표시하면 들어가든 말든 상관 없으니 말이다. required과 반대되는 개념이다.

어떤 느낌인지 딱 오지않는가?! ?은 필요 없는 파라미터만 사용하자.

여기서 또 named냐, positional이냐가 나뉘는데 아래의 예시를 보자.

String sayHelloPositional(String? name, int? age, [String? country = "Korea"]) {
  return "Hello $name, you are $age, and you come from $country";
}

String sayHelloNamed({String? name, int? age, String? country = "Korea"}) {
  return "Hello $name, you are $age, and you come from $country";
}

void main() {
  print(sayHelloPositional('뿔소', 25));
  print(sayHelloNamed(name: '뿔소', age: 25));
}

이런 느낌이다. 근데 주관적으로 봤을 때 Named가 더 직관적이고 편하다.. 난 걍 Named 쓸래..

화살표 함수 사용 가능

TS, JS 쟁이들은 화살표 함수를 어떻게 쓰는지 안다!

String sayHelloString(String name) => "Hello $name nice to meet you!";

void main() {
  print(sayHelloString('뿔소'));
}

중괄호{}와 return을 생략하고 =>를 써주면 된다! 대신 1줄 함수만 가능하다. 1줄로 쭉 쓰니까..

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

이런 식으로 쓰면 된다.

⭐️이상한 다트 나라의 operator, null-aware 연산자

제목 무슨일인가 싶지만 나도 어떻게 명칭을 써야할지 모르겠어서 이렇게 적었다.

바로 ???= 연산자(Operator)를 배워보자. 다트 안 연산자가 앞 2개만 있는 건 아닌데 다트에서 특이한 연산자라 배우는 것이다. 나머지 기본 연산자는 다 있다. 부등호나 뭐 비교 연산자 등등

??

만약 아래와 같이 대문자로 바꿔주는 함수, capitalizeName이 있다.

String capitalizeName(String? name) {
  if (name != null) {
    return name.toUpperCase();
  }
  return "NULL";
}
String capitalizeNamee(String? name) => name != null ? name.toUpperCase() : "NULL";

void main() {
  print(capitalizeName("rhino"));
  print(capitalizeName(null));
}
// RHINO
// NULL

null 처리를 해주려고 조건문을 써서 4줄의 코드로 함수를 완성했다. 삼항연산자로 하니 1줄로 끝낸다. 근데 여기서 ?? 연산자를 써주면 더 짧게 할 수 있다.

String capitalizeName(String? name) => name?.toUpperCase() ?? "NULL";

이렇게 말이다.

즉, ??연산자는

left ?? right

가 있을 때, leftnull이라면 right를 리턴하는 아주 간단한 조건문이다.

??=

얘는 위 ??랑 비슷한 매커니즘이다. 변수가 null일 경우에만 값을 할당하여 리턴하는 역할을 한다.

void main() {
  String name;
  // name 변수가 null이므로 'Guest' 문자열을 할당.
  name ??= 'Guest'; 
  print(name); // 출력: Guest
  // name 변수가 이미 'Guest' 문자열을 가지고 있으므로 'John' 문자열을 할당하지 않음.
  name ??= 'John'; 
  print(name); // 출력: Guest
}

이렇게 말이다! 할당 후 리턴이 되므로 John은 절대 나오지 않는 모습이다. John이 나오게 하려면

void main() {
  String name;
  name ??= 'Guest'; 
  print(name); // 출력: Guest
  name = null;
  name ??= 'John'; 
  print(name); // 출력: John
}

이렇게 하면 John을 볼 수 있을 것이다!
즉, 변수를 null-check 하고, 변수가 null일 경우에만 새로운 값으로 변수를 초기화할 때 유용하게 사용할 수 있다.

플러터에서 자주자주 쓰이니 꼭 기억해놓자. 약간 API 통신할 때 많이 쓸듯? 기본값들이 서버 통신하기 전 null이니 말이다.

Typedef

일단 설명하기 전 예시부터 보자. 숫자로 된 List를 반대로 뒤집어서 return하는 function을 작성해보겠다.

List<int> reverseListOfNumbers(List<int> list) {
  var reversed = list.reversed;
  return reversed.toList();
}

일단 먼저 얘기할 것은 list를 뒤집은 reversed toList를 왜 해줬냐면 reverse가 적용되면 iterable이 돼서 toList를 써줘 다시 파라미터 list와 같은 자료인 리스트로 만들기 위해서다. 짚고 넘어가자.

여기서 이제 Typedef를 설명하자면

함수 타입의 별칭을 만들어주는 역할
즉, 자료형(함수)에 alias를 붙여준다.

그러니까 타입을 커스텀으로 정의 후 붙일 수 있다는 것이다.

typedef IntList = List<int>;

IntList reverseListOfNumbers(IntList list) {
  var reversed = list.reversed;
  return reversed.toList();
}

TS의 interface로 정의 후 객체데이터에 넣는 느낌으로 보면 된다. IntList로 넣었고, 디테일을 보니 List<int>가 뜨는 모습을 볼 수 있다. 다른 타입들도 가능하다.

또한 함수로도 사용할 수 있고 사용법이 무궁무진하다.

typedef myFunctionType = int Function(int, int);

int add(int x, int y) {
  return x + y;
}

int multiply(int x, int y) {
  return x * y;
}

void main() {
  myFunctionType operation;

  operation = add; // myFunctionType 타입 변수에 add 함수를 할당
  print(operation(3, 4)); // 출력: 7

  operation = multiply; // myFunctionType 타입 변수에 multiply 함수를 할당
  print(operation(3, 4)); // 출력: 12
}

이런 식으로 모양을 잡아놓고도 할 수 있다. 쨌든 typedef는 타입 별칭을 우리 맘대로 만들어서 붙일 수 있다는 거시다.

profile
코뿔소처럼 저돌적으로

7개의 댓글

comment-user-thumbnail
2023년 5월 4일

재밌네요 플러터도 공부하시는군요 근데 보니까 c++이랑 비슷한 느낌이네요 화이팅입니다

1개의 답글
comment-user-thumbnail
2023년 5월 6일

플러터도 공고 많던데.... 고민입니다

답글 달기
comment-user-thumbnail
2023년 5월 7일

고생하셨습니당 !!

답글 달기
comment-user-thumbnail
2023년 5월 7일

잊고있던 플러터 포스팅이네요ㅎㅎ 화이팅입니다💪

1개의 답글
comment-user-thumbnail
2023년 5월 7일

윤뿔소님 글에선 정말 코뿔소같은 저돌적인 느낌이 드는군요! 실력이 뒷받침 되는 자신감! 부럽습니다

답글 달기