Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
05-15 13:20
관리 메뉴

nomad-programmer

[Programming/Dart] 함수형 프로그래밍 (Functional Programming) 본문

Programming/Dart

[Programming/Dart] 함수형 프로그래밍 (Functional Programming)

scii 2020. 10. 6. 17:55

다트는 객체 지향 프로그래밍과 함수형 프로그래밍의 특징을 모두 제공한다. 함수형 프로그래밍은 자료 처리를 수학적 함수의 계산으로 취급하는 프로그래밍 패러다임이다 (상태와 가변 데이터를 기피한다).

특히 다트의 컬렉션은 함수형 프로그래밍을 지원하는 다양한 함수를 제공한다. 자주 사용하는 몇 가지를 알아보자.

일급 객체

다트에서는 함수를 값으로 취급할 수 있다. 그러므로 다른 변수에 함수를 대입할 수 있다.

void greeting(String text) {
  print(text);
}

void main() {
  var f = greeting;
  f('hello');
}


/* 결과

hello

*/

다른 함수의 인수로 함수 자체를 전달하거나 함수를 반환받을 수도 있다.

void something(Function(int i) f) {
  f(55);
}

void main() {
  something((value) {
    print(value);
  });
}


/* 결과

55

*/

위 코드에서 something() 함수는 인수로 Function이라는 특수한 클래스의 인스턴스를 받는다. Function은 다트에서 함수를 매개변수로 전달하고자 할 때 사용하는 타입이다. something() 함수는 매개변수로 익명 함수를 받아 내부에서 그 익명 함수에 매개 변수로 55를 전달하고 실행한다.

이렇게 함수를 매개변수로 전달하기, 수정하기, 변수에 대입하기가 가능한 객체를 "입급 객체(first-class object)" 라고 한다.

다트에서 함수를 표현할 수 있는 것들(람다식, 익명 함수, 메소드)은 모두 값으로 취급할 수 있다. 따라서 함수를 다른 함수에 전달하는 방법도 여러 가지다.

void something(Function(int i) f) {
  f(55);
}

void myPrintFunction(int i) {
  print("내가 만든 함수에서 출력한 $i");
}

void main() {
  something(myPrintFunction);
  something((i) => myPrintFunction(i));
  something((i) => print(i));
  something(print);
}


/* 결과

내가 만든 함수에서 출력한 55
내가 만든 함수에서 출력한 55
55
55

*/
C# 언어에서의 delegate와 흡사하다고 생각하면 된다.

for문과 forEach() 함수

for문은 대표적인 반복문이다. for 문은 외부 반복을 한다. for문으로 외부 반복하여 리스트 내용을 출력해보자.

final items = [1, 2, 3, 4, 5];

for(int i=0; i<items.length; i++) {
  print(items[i]);
}

반면 forEach() 함수는 내부 반복을 수행한다. 외부에서 코드로 봤을 때는 반복문 형태를 띄지 않지만 내부적으로는 반복을 하고 있다.

forEach() 함수는 (E element) { } 형태의 함수를 인수로 받는다(E는 모든 타입이 가능하다는 것을 의미).

void main() {
  List<int> items = [1, 2, 3, 4, 5];

  // 1, 2, 3, 4, 5 출력
  items.forEach((element) {
    print(element);
  });

  // 1, 2, 3, 4, 5 출력
  items.forEach(print);
  // 1, 2, 3, 4, 5 출력
  items.forEach((e) => print(e));
}

(e) => print 형태의 함수에서 e는 items의 각 요소가 내부적으로 반복하면서 하나씩 들어올 인수이다.


where

Python 언어에서의 filter 내장 함수와 같다.

조건을 필터링할 때 사용하는 함수이다. 예를 들어 짝수만 출력하고 싶을 때 for문과 if문을 사용하는 코드는 다음과 같다.

final items = [1, 2, 3, 4, 5];

for(int i=0; i<items.length; i++) {
  if(items[i] % 2 == 0) {
    print(items[i]);
  }
}

where() 함수를 활용하면 다음과 같이 작성할 수 있다.

items.where((e) => e % 2 == 0).forEach(print);

함수형 프로그래밍을 지원하는 함수들은 결과를 반복 가능한 타입으로 반환하며 메소드 체인(. 연산자를 찍고 연속적으로 사용하는 것) 으로 연결하여 사용할 수 있다.


map

Python 언어에서의 map() 내장 함수와 같다.

map() 함수는 반복되는 값을 다른 형태로 변환하는 방법을 제공하는 함수이다. 다음은 짝수를 찾아 '숫자'라는 글자를 붙여 문자열 형태로 출력하는 예제이다.

void main() {
  const List<int> items = [1, 2, 3, 4, 5];

  // 결과 2, 4
  for(int i=0; i<items.length; i++){
    if(items[i] % 2 == 0){
      print('숫자 ${items[i]}');
    }
  }

  // 결과 2, 4
  items.where((e) => e % 2 == 0).map((e) => '숫자 $e').forEach(print);
}

toList

다트에서 함수형 프로그래밍을 지원하는 함수 대부분은 Iterable<T>라는 인터페이스 타입 인스턴스를 반환한다. 하지만 실제 사용할 때는 대부분 리스트 형태로 변환해야 하는 경우가 많다. 결과를 리스트로 저장하는 예제 코드이다.

void main() {
  final items = [1, 2, 3, 4, 5];

  List<String> result = items.where(
          (e) => e % 2 == 0).map((e) => '숫자 $e').toList();

  print(result);
}


/* 

[숫자 2, 숫자 4]

*/

toList() 함수는 where(), map()과 같이 Iterable 인터페이스를 반환하는 메소드에서 사용할 수 있다.


toSet

리스트에 중복된 데이터가 있을 경우 중복을 제거한 리스트를 얻고 싶을 수 있다. for 문을 사용해 짝수 리스트를 얻는 코드를 다음과 같이 구현할 수 있다.

void main() {
  final items = [1, 2, 2, 3, 3, 4, 5];

  var result = <int>[];
  // Set 자료 구조
  var temp = <int>{};
  for(int i=0; i<items.length; i++){
    if(items[i] % 2 == 0){
      temp.add(items[i]);
    }
  }
  result = temp.toList();
  print(result);
}


/* 결과

[2, 4]

*/

where() 함수와 toSet() 함수를 함께 사용하면 다음과 같이 간단히 중복 데이터를 없앨 수 있다.

void main() {
  final items = [1, 2, 2, 3, 3, 4, 5];

  List<int> result = items.where((e) => e % 2 == 0).toSet().toList();
  
  print(result);
}


/* 결과

[2, 4]

*/

any

Python 언어에서의 any() 내장 함수와 같다.

any() 함수는 리스트에 특정 조건을 충족하는 요소가 있는지 없는지 검사할 때 사용한다. 다음은 리스트에 짝수가 하나라도 있는지 검사하여 결과를 출력하는 코드이다.

void main() {
  final items = [1, 2, 2, 3, 3, 4, 5];

  bool flag = false;
  for(int i=0; i<items.length; i++){
    if(items[i] % 2 == 0){
      flag = true;
      break;
    }
  }
  print(flag);

  bool flag2 = items.any((e) => e % 2 == 0);
  print(flag2);
}


/* 결과

true
true

*/

every

Python 언어에서의 all() 내장 함수와 같다.

every() 함수는 리스트의 모든 요소가 전달한 콜백 함수 조건에 부합하는지 테스트할 때 사용한다. 만약 단 하나라도 거짓(false)이라면 false를 반환하고, 모든 요소가 조건에 부합하다면 true를 반환하는 함수이다.

void main() {
  final items = [1, 2, 2, 3, 3, 4, 5];

  bool flag = true;
  for(int i=0; i<items.length; i++){
    if(items[i] > 7){
      flag = false;
      break;
    }
  }
  print(flag);

  bool flag2 = items.every((e) => e < 7);
  print(flag2);
}


/* 결과

true
true

*/

reduce

Python 언어에서의 functools.reduce() 집계 함수와 같다.

reduce() 함수는 반복 요소를 줄여가면서 결과를 만들 때 사용하는 함수이다.

다음 예제는 리스트에서 최댓값을 구할 때 순차적으로 비교하는 로직이다. dart:math 패키지는 max(), min() 등 다양한 수학 함수를 제공한다. 이 함수들을 사용하려면 dart:math 패키지를 임포트해야 한다.

import 'dart:math';

void main() {
  final List<int> items = [1, 2, 3, 4, 5];

  int maxResult = items[0];
  for(int i=1; i<items.length; i++){
    maxResult = max(items[i], maxResult);
  }
  // 결과: 5
  print(maxResult);

  int maxResult2 = items.reduce((value, element) => max(value, element));
  // 결과: 5
  print(maxResult2);
  // 결과: 5
  print(items.reduce(max));

  int sumResult = items.reduce((value, element) => value + element);
  // 결과: 15
  print(sumResult);
}
Comments