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

keemeesuu·2022년 11월 10일
0

Flutter > Dart

목록 보기
3/4

Functional Programming 의 기본은 형변환이다.

List, Map, Set 같은 타입으로 진행을 한다.

사전지식

[] : 리스트 타입

() : List-"[]" 와 비슷한 타입의 형태이다. 주로 toList()를 사용해 리스트 타입으로 반환한다.

What is an Iterable?

Dart에서는 iterable이라는 개념이 있다. iterable은 연속적으로 접근할 수 있는 elements의 collection이다. 즉, 반복이 가능한 그룹을 뜻하고 list나 array 등을 의미한다.
Map은 순서가 없기 때문에 iterable이 아니다. 하지만 linked Map은 순서가 있으니 iterable이다.

Iterable Reference

List, Map, Set 기본적 형변환

void main() {
  List<String> blackPink = ['로제', '지수', '리사', '제니', '제니'];
  
  print(blackPink);
  // [로제, 지수, 리사, 제니, 제니]
  print(blackPink.asMap());  
  // {0: 로제, 1: 지수, 2: 리사, 3: 제니, 4: 제니}
  print(blackPink.toSet());
  // {로제, 지수, 리사, 제니}
  
  Map blackPinkMap = blackPink.asMap();
  
  print(blackPinkMap.keys);
  // (0, 1, 2, 3, 4)
  // "()" -> Iterable<dynamic> 형태는 List-"[]" 와 비슷한 형태이다.
  // 다른형태로 바꾸기 쉽다. 실질적으로 사용하진 않고 리스트 형태로 바꿔준다.
  print(blackPinkMap.keys.toList());
  // [0, 1, 2, 3, 4]
  print(blackPinkMap.values.toList());
  // [로제, 지수, 리사, 제니, 제니]
  
  Set blackPinkSet = Set.from(blackPink);
  
  print(blackPinkSet.toList());
  // [로제, 지수, 리사, 제니]
}

map

map 메소드는 iterable(배열그룹)를 대상으로 foreach돌린다.

void main() {
  List<String> blackPink = ['로제', '지수', '리사', '제니', '제니'];
  
  // List의 map method
  // 함수에다 함수를 넣어준다.
  // blackPink의 리스트들이 x parameter에 들어간다.
  final newBlackPink = blackPink.map((x) {
    return '블랙핑크 $x';
  });
  
  print(blackPink);
  // [로제, 지수, 리사, 제니, 제니]
  print(newBlackPink.toList());
  // [블랙핑크 로제, 블랙핑크 지수, 블랙핑크 리사, 블랙핑크 제니, 블랙핑크 제니]
  
  
  // 아래와 같이 Arrow함수로 간단하게 표현 가능
  final newBlackPink2 = blackPink.map((x) => '블랙핑크 $x');
  
  print(newBlackPink2.toList());
  // [블랙핑크 로제, 블랙핑크 지수, 블랙핑크 리사, 블랙핑크 제니, 블랙핑크 제니]
  
  print(blackPink == blackPink); // true
  print(blackPink == newBlackPink); // false
  print(newBlackPink == newBlackPink2); // false
}

List Mapping 패턴 예제

void main() {
  // [1.jpg, 3.jpg, 5.jpg, 7.jpg, 9.jpg]
  String number = '13579';
  
  final parsed = number.split('').map((x) => '$x.jpg').toList();
  
  print(parsed);
  // [1.jpg, 3.jpg, 5.jpg, 7.jpg, 9.jpg]
}

Map Mapping 패턴 예제

void main() {
  Map<String, String> harryPotter = {
    'Harry Potter': '해리 포터',
    'Ron Wesley': '론 위즐리',
    'Hermione Granger': '허마이오니 그레인저'
  };
  
  // 1. map 을 mapping 하여 새로은 map을 만들 때
  // 이해를 돕기위한 예제,
  // map을 map으로 바꾸는 일은 거의 없다.
  final result2 = harryPotter.map(
    (key, value) => MapEntry(
        '영어로 $key',
        '한글로 $value',
    ),
  );
  
  print(result2);
  // {영어로 Harry Potter: 한글로 해리 포터, 영어로 Ron Wesley: 한글로 론 위즐리, 영어로 Hermione Granger: 한글로 허마이오니 그레인저}

  
  // 2. key or value 값들을 리스트로 변경하고 싶을 때
  // 자주 사용하는 예제
  final keys = harryPotter.keys.map((x) => '한글로 $x').toList();
  final values = harryPotter.values.map((x) => '영어로 $x').toList();
  
  print(keys);
  // [한글로 Harry Potter, 한글로 Ron Wesley, 한글로 Hermione Granger]
  print(values);
  // [영어로 해리 포터, 영어로 론 위즐리, 영어로 허마이오니 그레인저]
}

Set Mapping 패턴 예제

void main() {
  Set blackPinkSet = {
    '로제',
    '지수',
    '제니',
    '리사',
  };

  final newSet = blackPinkSet.map((x) => '블랙핑크 $x').toSet();

  print(newSet);
}

where

배열요소를 필터링 한다.

void main() {
  List<String> fruits = ["banana", "apple", "strawberry"];
  
  final result1 = fruits.where((x) => x == "banana");
  final result2 = fruits.where((x) => x != "banana");
  
  print(result1);
  // (banana)
  print(result2);
  // (apple, strawberry)
}
void main() {
  List<Map<String, String>> people = [
    {
      'name': '로제',
      'group': '블랙핑크'
    },
    {
      'name': '지수',
      'group': '블랙핑크'
    },
    {
      'name': 'RM',
      'group': 'BTS'
    },
    {
      'name': '뷔',
      'group': 'BTS'
    },
  ];
  
  print(people);
  // [{name: 로제, group: 블랙핑크}, {name: 지수, group: 블랙핑크}, {name: RM, group: BTS}, {name: 뷔, group: BTS}]

  
  final blackPink = people.where((x) => x['group'] == '블랙핑크').toList();
  
  print(blackPink);
  // [{name: 로제, group: 블랙핑크}, {name: 지수, group: 블랙핑크}]
}

reduce

배열을 누적시켜 반환한다.

void main() {
  List<int> numbers = [1, 3, 5, 7, 9];

  // reduce()
  // parameter prev의 초기값은 0 이고
  // 다음 prev 부터는 이전 return 값을 받는다.
  final result = numbers.reduce((prev, next) {
    print('-----------------');
    print('previous : $prev');
    print('next : $next');
    print('total : ${prev + next}');

    return prev + next;
  });
  /*
  -----------------
  previous : 1
  next : 3
  total : 4
  -----------------
  previous : 4
  next : 5
  total : 9
  -----------------
  previous : 9
  next : 7
  total : 16
  -----------------
  previous : 16
  next : 9
  total : 25
  */

  print(result);
  // 25
}
void main() {
  List<String> words = [
    '오늘은 ',
    '토요일 ',
    '입니다.'
  ];
  
  final result = words.reduce((prev, next) => prev + next);
  
  print(result);
  // 오늘은 토요일 입니다.
}

리듀스가 실행할 수 없는 경우의 수가 있다.
리듀스로 반환되는 타입이 리스트 리턴타입과 같아야 한다.

void main() {
  
  List<String> words = [
    '오늘은 ',
    '토요일 ',
    '입니다.'
  ];
  
  final sentence1 = words.reduce((prev, next) => prev + next);
  
  print(sentence1);
  // 오늘은 토요일 입니다.
  
  final sentence2 = words.reduce((prev, next) => prev.length + next.length);
  // error - String 타입으로 받아야하는데 length가 int값
  
}

fold

reduce의 단점(무조건 같은 타입을 리턴해줘야하는)을 보안(?)한 메소드.
기능적으로 fold 함수로 reduce 함수를 만들수도 있다.
즉, 아무형태나 리턴가능한게 장점이다.

void main() {
 // 기능적으로, fold함수로 reduce함수 만들기
 List<int> numbers = [1, 3, 5, 7, 9];
 
 // fold의 리턴값을 정해줘야 한다. 제너릭으로 리턴값 제공.
 // 첫번째 parameter는 시작값. 즉, prev에 들어간다. 
 final sum = numbers.fold<int>(0, (prev, next) {
   print('-------------');
   print('prev : $prev');
   print('next : $next');
   print('total : ${prev + next}');
   
   return prev + next;
 });
 /*
 -------------
 prev : 0
 next : 1
 total : 1
 -------------
 prev : 1
 next : 3
 total : 4
 -------------
 prev : 4
 next : 5
 total : 9
 -------------
 prev : 9
 next : 7
 total : 16
 -------------
 prev : 16
 next : 9
 total : 25
 */
 
 print(sum);
}

reduce와 다르게 리턴값을 아래 예제와 같이 지정할 수 있다.

void main() {
  List<String> words = [
    '오늘은 ',
    '토요일 ',
    '입니다.'
  ];
  
  final result = words.fold<String>('', (prev, next){
    return prev+next;
  });
  
  print(result);
  // 오늘은 토요일 입니다.
  
  final count = words.fold<int>(0, (prev, next){
    return prev + next.length;
  });
  
  print(count);
  // 12
}

cascading operator

여러개의 리스트를 합칠때 많이 사용한다.

void main() {
  List<int> even = [2, 4, 6, 8];
  List<int> odd = [1, 3, 5, 7];
  
  // cascading operator
  // ...
  
  print([even, odd]);
  // [[2, 4, 6, 8], [1, 3, 5, 7]]
  
  print([...even, ...odd]);
  // [2, 4, 6, 8, 1, 3, 5, 7]
  
  print(even == [...even]);
  // false
}

실제 어떤식으로 Functional Programming을 다루는가?

void main() {
  
  // json 형태를 클래스로 변환작업 하는 이유
  //  1.map은 구조화가 안되어있고 자유도가 너무 높아서
  //  구조화를 하기 위해
  //  list map의 데이터 형태를 클래스로 변환하는 작업.
  //  2. json 형태를 클래스로 변환. 즉, 구조화를 하여 신뢰할 수 있는 상태로 만든다.

  // 1. json 형태의 자료
  final List<Map<String, String>> people = [
    {'name': '지수', 'group': '블랙핑크'},
    {'name': '로제', 'group': '블랙핑크'},
    {'name': 'RM', 'group': 'BTS'},
    {'name': '뷔', 'group': 'BTS'},
  ];

  print(people);
  // [{name: 지수, group: 블랙핑크}, {name: 로제, group: 블랙핑크}, {name: RM, group: BTS}, {name: 뷔, group: BTS}]

  // 3. json형태 -> 구조화 클래스 mapping 작업.
  // 이렇게 구조화를 해놓으면 다양하게 사용 할 수 있다.
  // Person 파싱.
  final parsedPeople = people.map((x) {
    return Person(
      name: x['name']!,
      group: x['group']!,
    );
  }).toList();
  
  print(parsedPeople);
  // [Person(name:지수, group:블랙핑크), Person(name:로제, group:블랙핑크), Person(name:RM, group:BTS), Person(name:뷔, group:BTS)]

  
  // 4-1. 유용한 예제
  // "print." 하면 어떤 값들이 있는지 
  // suggestion(추천단어)가 떠서 직관적으로 볼 수 있다.
  // 에러를 줄일 수 있다.
  for(Person person in parsedPeople){
    print(person.name);
    print(person.group);
  }
  
  // 4-2. 유용한 예제
  // 특정값 출력
  final bts = parsedPeople.where(
    (x) => x.group == 'BTS',
  );
  print(bts);
  // (Person(name:RM, group:BTS), Person(name:뷔, group:BTS))
  
  // 4-2를 아래와 같이 표현 가능.
  // 이렇게 이어붙이기 가능(계속 가능).
  final result = people.map(
    (x) => Person(
      name: x['name']!,  
      group: x['group']!,  
    ),
  ).where((x) => x.group == 'BTS');
  print(result);
  // (Person(name:RM, group:BTS), Person(name:뷔, group:BTS))
  
}

// 2. 구조화 클래스 제작
class Person {
  final String name;
  final String group;

  Person({required this.name, required this.group});
  
  // toString - 부모클래스 Object에 제공되는 기본함수
  
  String toString(){
    return 'Person(name:$name, group:$group)';
  }
  
}

정리

functional Programming 의 기본은

  1. 실행하는 그 대상(List, Set, Map 등등)과 다른 새로운 값을 생성해준다.
  2. Method Chaining 을 할 수 있으며 코드가 간결해진다.
    주의 : 너무 길어지면 유지보수에 힘들 수 있으니 코멘트를 달아 준다거나 잘라서 사용한다.

OOP와 Functional Programming 둘 중 뭐가 좋다가 아닌 두개의 장단점을 활용하여 사용하자.




[Reference]

Dart #3 Functional Programming 함수형 프로그래밍
[Flutter/Drat]다트 유용한 메소드 Method 함수 Function 정리~
[Dart/Flutter] 유용한 메서드, 함수 정리

[Dart] iterable 의미와 Collection 의 종류

profile
개발관련 정리하고 기록하는 블로그 🦦

0개의 댓글