How to become a real programmer/Front-End

Flutter - Dart 기초(3)[Functional Programming]

MinDDokDDok 2022. 12. 29. 23:41

함수형 프로그래밍 Functional Programming?

일련의 코딩 접근 방식으로서, 자료처리를 수학적 함수의 계산으로 취급하고 상태와 가변데이터를 멀리하는 프로그래밍 패러다임을 뜻한다

나도 솔직히 뭔 소린진 와닿진 않는다, 좀 객체지향 프로래밍과 대조적인 특징인데 다트는 객체지향 프로그래밍과 함수형 프로그래밍의 특징 모두 다 제공한다. 

즉, 여러 함수들을 통해서 프로그래밍 할 수 있다는 것

 

1. List, Map, Set 자료형 변환

void main(){
  List<String> newJeans = ['해린', '민지', '하니', '다니엘', "혜인", '해린'];
  print(newJeans);
  print(newJeans.asMap()); // 리스트를 맵으로 변환
  print(newJeans.toSet()); // 리스트를 세트로 변환 1
  // print(Set.from(newJeans)); // 리스트를 세트로 변환 2
  
  print("----------------------------------");
  Map newJeansMap = newJeans.asMap(); // 리스트를 맵으로
  print(newJeansMap);
  print(newJeansMap.keys);
  print(newJeansMap.values);
  print(newJeansMap.keys.toList()); // 맵의 키값들을 리스트로 변환
  print(newJeansMap.values.toList()); // 맵의 밸류값들을 리스트로 변환
  
  print("----------------------------------");
  Set newJeansSet = Set.from(newJeans); // 리스트를 세트로
  print(newJeansSet);
  print(newJeansSet.toList()); // 세트를 리스트로 변환 
  
}

위와 같이 다트에서 제공되는 여러 함수들을 통해서 자료형 변환이 자유롭게 활용된다(자유로워 보이는 자료형변환이 너무너무 좋다,,)

위 코드의 결과

2. Map() 함수

map 함수는 어떠한 형태에서 다른 형태로 변환할 때 유용한데, map함수는 사용 하기 위해서 우선 함수 내 함수가 필요하다

void main(){
  List<String> newJeans = ['해린', '민지', '하니', '다니엘', "혜인"];
  
  final newNewJeans = newJeans.map((x){
    return '뉴진스 $x';
  }); 
  // map 함수는 함수 내 함수가 필요, map()은 어떠한 형태에서 다른 형태로 변환할때 유용하다
  // 뉴진스를 멤버명 앞에 붙여주기
  
    print(newJeans);
  print(newNewJeans);
  
}

리스트의 경우,

newNewJeans라는 변수를 만들고, newJeans.map해준 결과를 넣어서 출력해보면 멤버명 앞에 '뉴진스'가 붙는 것을 알 수 있다

즉, map에서 x는 위 리스트에서 값들을 하나하나 불러와서 새로운 리스트값으로 리턴해주는 것임을 알 수 있다.

Iterable 형태로 리턴되어 옴을 알 수 있다 - 즉 iterable 하다는 것은 다른 자료형으로 변경가능하다는 것, toList 해주면 리스트형으로 되겠다

final newNewJeans = newJeans.map((x){
    return '뉴진스 $x';
  }); 
  
// 2. final newNewJeans2 = newJeans.map((x) => '뉴진스 $x');

두 번째 방식으로 arrow 함수 방식으로 써도 된다

  print(newJeans == newJeans);
  print(newNewJeans == newJeans);
  print(newNewJeans == newNewJeans2);

만약 타입을 검사해보면,

map 해서 만든 newNewJeans와 newNewJeans2가 각각 다르다는 것을 알 수 있다, 즉 map은 아예 새로운 리스트를 계속 만들어 낸다는 것을 알아야!

다트에서 제공하는 함수형 프로그래밍이 아주 편한 이유는 아래와 같다

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

간단하게 함수 두개를 이용한 한 줄 만으로 새로운 리스트를 뚝딱 만들 수 있다.

 

Map 함수를 활용해 키와 밸류값을 가지는 맵 자료형도 변환가능하다

void main(){
  
  Map<String, String> HarryPorter = { 'Harry Porter': '해리 포터', 
                                    'Ron Weasley': '론 위즐리',
                                    'Hermione Granger': '엠마 왓슨'};
  
  final Map<String, String> HarryPorter2 = HarryPorter.map(
    (key, value) => MapEntry(
    'Harry Porter Character $key',
    '해리포터 캐릭터 $value'
    )
  );
  
  print(HarryPorter2);
  
}

리스트를 변환하는것과 동일하지만 키와 밸류값으로 되어 있기에 MapEntry 함수를 통해서 변경해주어야 하는데, 위와 같이 작성한 후 실행해보면 결과값으로 

잘 생성된다

물론 키값만 따로 변환하려 한다면 아래와 같다

final keys = HarryPorter.keys.map((x) => 'Harry Potter Character $x');

즉, 맵 자체를 다른 맵으로 바꾸는 방법과, 다른 리스트로 만들어버리는 방식 있다는 것

 

세트 자료형 변환은 아래와 같은데,

void main(){
  
  Set chelsea = {
    '메이슨 마운트',
    '리스 제임스',
    '세자르 아스필리쿠에타',
    '티아고 실바'
  };
  
  final newChelsea = chelsea.map((x) => '첼시팬의 사랑 $x').toSet();
  print(newChelsea);
  
  
}

리스트와 동일하다, 위 결과는 아래와 같은 세트를 보여준다

 

2. Where 함수로 값 유지, 삭제

where은 자료형을 루프를 돌면서 조건과 다르면 false, 일치하면 true를 리턴하며 false일 시 값 삭제, true일 시 값 유지를 할 수 있게 해주는 함수이다. 아래와 같이 사용가능하다

void main(){
  
  List<Map<String, String>> people = [{
    'name' : '로제',
    'group' : '로제파스타'
  },
                                      {
    'name' : '지수',
    'group' : '로제파스타'
  },
                                      {
    'name' : 'RM',
    'group' : 'Relationship Management'
  },
                                      {
    'name' : '정국',
    'group' : 'Relationship Management'
  }];
  
  print(people);
  
  // where은 리스트 멤버들을 루프를 돌면서 true, false 값을 돌려준다 
  // true일 시 값을 유지, false일 시 값을 제거
  final blackpink = people.where((x) => x['group'] == '로제파스타').toList();
  final bts = people.where((x) => x['group'] == 'Relationship Management').toList();
  
  print(blackpink);
  print(bts);
  
}

즉 리스트 안에 맵 형식으로 값을 가지는 리스트를 선언을 한 후에, group 값이 로제파스타인 경우와, 하나는 다른 경우로 블랙핑크와 bts 리스트를 만들어 보았고 결과를 본다면

위와 같이 그룹 명에 따라 값을 유지, 제거한 결과가 잘 나타난다(너무 좋은데?)

필터링 역할을 하기에 자주, 많이 사용된다하니 잘 기억해두자.

 

 

3. Reduce 함수로 합 구하기

int값으로 구성된 리스트를 하나 정의하고,

리스트에 reduce 함수를 사용해본다. map은 인자로 하나가 필요한 반면 reduce는 두개가 필요하다.

void main(){

  List<int> numbers = [
    1,
    3,
    5,
    7,
    9
  ];
  
  final result = numbers.reduce((prev, next){
    print('--------------');
    print('prev : $prev');
    print('next : $next');
    print('total : ${prev + next}');
    
    return prev + next;
  });
  // final result = numbers.reduce((prev, next) => prev + next); 와 동일 역할
  
  print(result);
}

결과를 보면, 

이렇게 나오는데, 즉 prev 값으로는 리스트 첫 번째 값이 먼저 들어가고 next값은 그 다음번째 리스트 값, 그리고 이후 prev값은 return된 값을 받는다, 그리고 next는 next에서 한 단계 더 뒤의 값을 가리킨다.

즉 이렇게 sum을 구할 수 있다. sum은 다른 방식으로 더 쉽게 만들수도 잇으니 다르게 한번 사용해보자,

이렇게 문자열을 결합해 만들수도 있겠다

다만 reduce은 실행 조건이 있다.

return되는 값이 태초가 되는 값의 자료형과 동일해야한다는 것이다. 즉, String으로 된 List를 돌면서 문자열 길이를 다 더해서 int로 리턴하거나 할 수는 없다는 것!

 

4. Fold함수

같은 타입을 무조건 반환해야 한다는 reduce의 조건을 보완한 함수이다.

List<int> numbers = [1, 3, 5, 7, 9];
  
  final sum = numbers.fold<int>(0, (prev, next) => prev + next);
  print(sum);

reduce와 거의 동일하게 형태를 가지고 있지만 return하는 자료형을 fold뒤에 명시해주고, prev, next 앞 인자는 처음 시작하는 prev값을 가리킨다. 즉 첫 번째 수행에서는 prev : 0, next : 1, 두 번째 수행에서는 prev : 1, next : 3, 세 번째 수행에서는 prev : 4, next : 5 이런 식인 것이다.

fold를 통해 아래처럼 아까전에 reduce로 불가했던 다른 자료형의 리턴값을 나타낼 수 있다.

5. Cascading Operator

Cascading Operator는 리스트 내 값들을 하나하나 풀어서 전달한다. 리스트 앞 ...을 붙여줌으로써 사용가능하고 아래와 같이 결과를 받을 수 있다.

print(even == [...even]); 으로, cascade 해 만든 리스트와 기존 리스트를 비교하면 false가 출력된다, 즉 아예 다른 리스트를 새로 하나 만드는 것이다

 

이러한 함수형 프로그래밍으로 실제로 데이터를 변환하는 예시?

만약, 아래와 같은 맵을 가지는 리스트 형식으로 데이터를 가져온다고 가정하자, 하지만 맵은 그 내부에 값들의 자유도가 높기에(형식이 정해지지 않았기에) 각 리스트 내 맵 값을 신뢰할 수 있는 구조인 클래스로 만들고 싶어하는 상황이다.

final List<Map<String, String>> people = [
    {
      'name' : '지수',
      'group' : '블랙핑크'
    },
    {
      'name' : '제니',
      'group' : '블랙핑크'
    },
    {
      'name' : 'RM',
      'group' : 'BTS'
    },
    {
      'name' : '뷔',
      'group' : 'BTS'
    },
  ];

함수형 프로그래밍으로 변환

void main(){

  final List<Map<String, String>> people = [
    {
      'name' : '지수',
      'group' : '블랙핑크'
    },
    {
      'name' : '제니',
      'group' : '블랙핑크'
    },
    {
      'name' : 'RM',
      'group' : 'BTS'
    },
    {
      'name' : '뷔',
      'group' : 'BTS'
    },
  ];
  
  print(people);
  
  final parsedPeople = people.map(
    (x) => Person(
    name: x['name']!,
    group: x['group']!
    ),
  ).toList();
  
  print(parsedPeople);
 
  for(Person person in parsedPeople){
    print(person.name);
    print(person.group);
    print('----------------\n');
  }
  
  final bts = parsedPeople.where(
    (x) => x.group == 'BTS');
  
  print('--------bts--------');
  print(bts);
  
  
}

class Person{
  final String name;
  final String group;
  
  Person({required this.name,
         required this.group});
 
  
@override
String toString(){
  return 'Person(name: $name, group: $group)';
} // 클래스가 사용하는 toString을 instance의 합 방식이 아닌 개별 값으로 바꾸는 메서드
  
}

근데 이렇게 따로따로 과정을 거쳐가고, 출력하는 과정 말고 함수형 프로그래밍은 그 과정을 아래처럼 단축할 수 있다.

void main(){

  final List<Map<String, String>> people = [
    {
      'name' : '지수',
      'group' : '블랙핑크'
    },
    {
      'name' : '제니',
      'group' : '블랙핑크'
    },
    {
      'name' : 'RM',
      'group' : 'BTS'
    },
    {
      'name' : '뷔',
      'group' : 'BTS'
    },
  ];
  
  final result = people.map(
  (x) => Person(
  name : x['name']!,
  group: x['group']!)
  ).where((x) => x.group == 'BTS');
    print(result);
    
  // !해주는 이유는 하지 않으면 name, group을 key값으로 가지고 있다는 것을 명확히 알 수 없기에
  // 오류를 리턴 할 수 있는데, !로 강제해주는 것(우리는 알 수 있기에)
  
  
}

class Person{
  final String name;
  final String group;
  
  Person({required this.name,
         required this.group});
 
  
@override
String toString(){
  return 'Person(name: $name, group: $group)';
} // 클래스가 사용하는 toString을 instance의 합 방식이 아닌 개별 값으로 바꾸는 메서드 
}

위 코드의 결과로

Person 클래스로 정렬된 BTS 그룹의 값을 출력할 수 있다.

함수형 프로그래밍은 이렇게 무한하게 계속 리턴되는 값을 리턴하고, 리턴하고 함수를 계속 붙이면서 코드를 짤 수 있다.

다만 너무 짧게 코드를 짜는 천재적인 능력을 자랑하지말자 너무 복잡해질 수 있다.