일기

Dart 공부하기_함수형프로그래밍, 비동기

o_b:us 2023. 1. 10. 17:36

함수형 프로그래밍

💡 Dart언어에서는 객체 지향뿐만 아니라 함수형 프로그래밍도 지원한다. 또한 java의 iterator와 비슷한 iterable이라는 개념이 있다. iterable은 연속적으로 접근할 수 있는 elements의 collection이다. Map은 순서가 없기 때문에 iterable이 아니다. 하지만 기본 구현체가 linkedHashMap으로 순서가 있으므로 iterable로 변경이 된다.

1. 일반적으로 Collection끼리 변경하기

void main() {
  List<String> members = ['a','b','c','d','e'];
  
  print(members);           //[a, b, c, d, e]
  print(members.asMap());   //{0: a, 1: b, 2: c, 3: d, 4: e}
  print(members.toSet());   //{a, b, c, d, e}
  
  Map<int,String> map = members.asMap(); 
  Map map2 = members.asMap();
  
  print(map.keys.toList());//[0, 1, 2, 3, 4]
  print(map.values.toList());//[a, b, c, d, e]
  
  Set membersSet = members.toSet();  
  Set<String> membersSet2 = members.toSet(); 
  print(membersSet.toList()); //[a, b, c, d, e]
}

2. map 사용하기(List);

void main() {
  List<String> members = ['a','b','c','d','e'];
  
  //iterable 형태로 나옴
  final funcMembers = members.map((x){
    return '멤버 $x'; 
  });
  print(funcMembers);           //(멤버 a, 멤버 b, 멤버 c, 멤버 d, 멤버 e)
  
  //arrow사용
  final funcMembers2 = members.map((x) => '멤버 $x');
  print(funcMembers2);
  
  //문자열을 리스트로 나눠서 만들기
  String number = '12345';

  final list = number.split('').map((x) => '숫자$x').toList();
  print(list);//[숫자1, 숫자2, 숫자3, 숫자4, 숫자5]
  
}

3. map 사용하기(Map, Set);

Dart에서 map의 구현체는 기본적으로 LinkedHashMap이다. Dart에서 Set의 구현체는 기본적으로 LinkedHashSet이다.

void main() {

  Map<String, String> menu = {
    'pizza' : '10000',
    'hamburger' : '5000',
    'coke' : '2000'
  };
  
  final Map result = menu.map((key, value) => MapEntry(
      'menu:$key',
      'price:$value'
   ));
  print(result);//{menu:pizza: price:10000, menu:hamburger: price:5000, menu:coke: price:2000}
  
  final keys = menu.keys.map((x) => '키 = $x'); 
  final values = menu.values.map((x) => '밸류 = $x');
  print(values.runtimeType); // EfficientLengthMappedIterable<String, String>
  print(keys); //(키 = pizza, 키 = hamburger, 키 = coke)
  print(values); //(키 = pizza, 키 = hamburger, 키 = coke)
  
  
  Set menuSet = {
    'pizza','hamburger','coke'
  };
  
  final result2 = menuSet.map((x) => 'menu=$x').toSet();
  print(result2); //{menu=pizza, menu=hamburger, menu=coke}
}

4. where 사용하기

필터링하는 함수

void main() {
   List<Map<String,int>> menu = [
     {
       'pizza' : 10000,
       'hamburger': 5000,
       'coke' : 2000
     },
     {
       'ramen': 3000,
       'rice' : 1000,
       'coke' : 2000
     },
     {'coke' : 3000}
   ];

    print(menu); //[{pizza: 10000, hamburger: 5000, coke: 2000}, {ramen: 3000, rice: 1000, coke: 2000}, {coke: 3000}]

    print(menu.where((x) => x['coke'] == 2000));  //({pizza: 10000, hamburger: 5000, coke: 2000}, {ramen: 3000, rice: 1000, coke: 2000}, {coke: 2000})

  }

5. reduce 사용하기

reduce사용시엔 변수의 타입과 return값의 타입이 일치해야한다.

void main() {
  
  List<int> numbers = [1,3,5,7,9];
  //return 값이 있고 prev에 이전 return 값을 저장한다.
  //next에는 다음 idx의 값을 의미한다.
  final result = numbers.reduce((prev, next){
    print('=========');
    print('previus= $prev');
    print('next= $next');
    print('total = ${prev+next}');
    return prev + next;
  });
  print(result);
  /*=========
    previus= 1
    next= 3
    total = 4
    =========
    previus= 4
    next= 5
    total = 9
    =========
    previus= 9
    next= 7
    total = 16
    =========
    previus= 16
    next= 9
    total = 25
    25*/
  print(numbers.reduce((prev,next)=> prev+next));//25
  
  List<String> words = ['hello' , 'dart', 'world!'];
  
  print(words.reduce((prev,next) => '$prev $next')); //hello dart world!
}

6. fold 사용하기

fold는 reduce와 달리 변수의 타입과 return값의 타입이 같지 않아도 된다. 또한 fold의 시작점은 초기값부터다.

reduce의 출력값과 비교해보면 reduce는 4번 출력

fold는 5번 출력된것을 알 수 있다.

void main() {
  List<int> numbers = [1,3,5,7,9];
  
  final result = numbers.fold(0,(prev, next){
    print('=========');
    print('previus= $prev');
    print('next= $next');
    print('total = ${prev+next}');
    return prev + next;
  });
  
  print(result);
  /*
   *  =========
      previus= 0
      next= 1
      total = 1
      =========
      previus= 1
      next= 3
      total = 4
      =========
      previus= 4
      next= 5
      total = 9
      =========
      previus= 9
      next= 7
      total = 16
      =========
      previus= 16
      next= 9
      total = 25
      25
   * */
  
  List<String> words = [
    'hello', 'Dart', 'World!'
  ];
  
  final sentence = words.fold<String> ('',(prev,next) => '$prev $next'); // hello Dart World! 앞에 공백이 먼저 들어옴
  print(sentence);
  final length = words.fold<int>(0, (prev,next) => prev+next.length);
  print(length);
}

7. cascading operator

… 를 이용하여 값을 풀어 넣을 수 있다.

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

8. 객체사용해서 활용하기

void main() {
   List<Map<String,String>> menu = [
     {
       'food' : 'pizza',
       'price': '10000',
     },
     {
        'food' : 'hamburger',
        'price' : '5000',
     },
     {  
        'food' : 'coke',
        'price' : '2000',
     }
   ];

    
    print(menu); 
//   [{food: pizza, price: 10000}, {food: hamburger, price: 5000}, {food: coke, price: 2000}]
    
    final parsedMenu = menu.map(
      (x) => Menu(
        food : x['food'],// 객체 속성에 null 할당시엔 ! 안붙여도됨
        price : x['price']!) //객체 속성에 null불가시엔 ! 붙여야함.
    ).toList();

    print(parsedMenu);
//   [Menu판:pizza 10000), Menu판:hamburger 5000), Menu판:coke 2000)]
//     for(Menu menu in parsedMenu) {
//       print(menu.food);
//       print(menu.price);
//     }
//   5000원 이상인 음식 출력하기 
    print(parsedMenu.where((x) => int.parse(x.price) >= 5000).toList());
// [Menu판:pizza 10000), Menu판:hamburger 5000)]
  }

class Menu{
  final String? food;
  final String price;
  
  Menu({required this.food, required this.price});
  
  @override
  String toString() {
    return 'Menu판:$food $price)';
  }
}

비동기 프로그래밍

다트언어는 비동기 프로그래밍을 지원한다.

1. Future

비동기를 하는동안 값을 받을 때까지 기다지 않고 다음 작업을 진행한다.

void main() {
  //Future - 미래에 받아올 값, 비동기로 받아올 값
  Future<String> name = Future.value('홍길동');
  Future<int> number = Future.value(1);
  Future<bool> flag = Future.value(true);
  
  // 첫번째 파라미터, 지연할 시간
  // 두번째 파라미터, 지연시간이 지난 후 실행할 함수
  Future.delayed(Duration(seconds: 3),(){
    print('First Delay 끝');
  });  
  addNumbers(10,20);
  addNumbers(30,40);
}

void addNumbers(int number1, int number2) {
  print('Second 계산 전 시작');
  //case : 서버에 요청하는 상황
  Future.delayed(Duration(seconds: 1), (){
    print('Third 계산 중: $number1 + $number2 = ${number1+number2}');
  });

  print('Fourth 계산 완료');
}

// Second 계산 전 시작
// Fourth 계산 완료
// Second 계산 전 시작
// Fourth 계산 완료
// Third 계산 중: 10 + 20 = 30
// Third 계산 중: 30 + 40 = 70
// First Delay 끝

2. async , await

비동기를 하는동안 값을 받을 때까지 기다릴 수가 있다. 그러나 CPU가 놀고 있는게 아니라 다른 작업을 진행하며 기다린다.

void main() {
  //Future - 미래에 받아올 값, 비동기로 받아올 값
  Future<String> name = Future.value('홍길동');
  Future<int> number = Future.value(1);
  Future<bool> flag = Future.value(true);
  
  // 첫번째 파라미터, 지연할 시간
  // 두번째 파라미터, 지연시간이 지난 후 실행할 함수
  Future.delayed(Duration(seconds: 3),(){
    print('First Delay 끝');
  });  
  addNumbers(10,20);
  addNumbers(30,40);
}

void addNumbers(int number1, int number2) async{
  print('Second 계산 전 시작');
  //case : 서버에 요청하는 상황
  await Future.delayed(Duration(seconds: 1), (){
    print('Third 계산 중: $number1 + $number2 = ${number1+number2}');
  });

  print('Fourth 계산 완료');
}

// Second 계산 전 시작
// Second 계산 전 시작
// Third 계산 중: 10 + 20 = 30
// Fourth 계산 완료
// Third 계산 중: 30 + 40 = 70
// Fourth 계산 완료
// First Delay 끝

3. void async,await

Future를 리턴해주는 함수만 await가 가능하다.

함수가 완료될 때까지 기다렸다가 다음 함수를 진행한다.

await가 없을 시엔 완료여부와 상관없이 다음 함수를 진행한다.

void main() async{
  //Future - 미래에 받아올 값, 비동기로 받아올 값
  Future<String> name = Future.value('홍길동');
  Future<int> number = Future.value(1);
  Future<bool> flag = Future.value(true);
  
  // 첫번째 파라미터, 지연할 시간
  // 두번째 파라미터, 지연시간이 지난 후 실행할 함수
  Future.delayed(Duration(seconds: 3),(){
    print('First Delay 끝');
  });  
  await addNumbers(10,20);
  await addNumbers(30,40);
}

//void => Future<void>
Future<void> addNumbers(int number1, int number2) async{
  print('Second 계산 전 시작');
  //case : 서버에 요청하는 상황
  await Future.delayed(Duration(seconds: 1), (){
    print('Third 계산 중: $number1 + $number2 = ${number1+number2}');
  });

  print('Fourth 계산 완료');
}

// Second 계산 전 시작
// Third 계산 중: 10 + 20 = 30
// Fourth 계산 완료
// Second 계산 전 시작
// Third 계산 중: 30 + 40 = 70
// Fourth 계산 완료
// First Delay 끝

4. return 값이 있을 때

void main() async{
  //Future - 미래에 받아올 값, 비동기로 받아올 값
  Future<String> name = Future.value('홍길동');
  Future<int> number = Future.value(1);
  Future<bool> flag = Future.value(true);
  
  // 첫번째 파라미터, 지연할 시간
  // 두번째 파라미터, 지연시간이 지난 후 실행할 함수
  final result1 = await addNumbers(10,20); 
  final result2 = await addNumbers(20,40);
  print('main첫번쨰 $result1'); 
  //addNumebers(10,20);
  //await를 안붙이고 함수 실행시엔 Instance of '_Future<int>'로 값을 받기전에 출력해버림
  print('main두번째 $result2');
  
}

//void => Future<void>
Future<int> addNumbers(int number1, int number2) async{
  print('Second 계산 전 시작');
  //case : 서버에 요청하는 상황
  await Future.delayed(Duration(seconds: 2), (){
    print('Third 계산 중: $number1 + $number2 = ${number1+number2}');
  });

  print('Fourth 계산 완료');
  
  return number1+number2;
}

// Second 계산 전 시작
// Third 계산 중: 10 + 20 = 30
// Fourth 계산 완료
// Second 계산 전 시작
// Third 계산 중: 30 + 40 = 70
// Fourth 계산 완료
// First Delay 끝

5. Stream

💡 Future represents a one-time value: the app performs an operation and comes back with some data. A Stream represents a sequence of data. 출처: https://www.kodeco.com/32851541-dart-futures-and-streams
그래도 잘 이해가 안되서 스택오버플로우를 보니 Future는 현재 사용할 수 없는 값이지만 나중에 사용할 수 있고, 변경되지 않는 값에 사용된다. stream은 시간이 지남에 따라 일부 값이 변경 될 때 사용한다. 즉 Future는 한번만 리턴하고, stream은 리턴이 계속해서 바뀔 수 있다.

 

import 'dart:async';

void main() {
  final controller = StreamController();
//   final stream = controller.stream;
  //한번만 리스닝할 수 있음(리스닝 하나만)
  final stream = controller.stream.asBroadcastStream();
  final streamListener1 = stream.listen((val){
    print('listener : $val');
  });
  
  //값들을 listening
  controller.sink.add(1);
  controller.sink.add(2);
  controller.sink.add(3);
  controller.sink.add(4);
  controller.sink.add(5);
  
  final streamListener2 = stream.listen((val){
    print('Listener 2 : $val');
  });
}

/* 
 * 
 * listener : 1
Listener 2 : 1
listener : 2
Listener 2 : 2
listener : 3
Listener 2 : 3
listener : 4
Listener 2 : 4
listener : 5
Listener 2 : 5
 * */

6. stream과 함수형 프로그래밍

import 'dart:async';

void main() {
  final controller = StreamController();
 
  //case : 짝수 값만 리스닝하라
  //where 사용하기
  final stream = controller.stream.asBroadcastStream();
  final streamListener1 = stream.where((val)=> val%2 == 0).listen((val){
    print('짝수만 : $val');
  });
 
  controller.sink.add(1);
  controller.sink.add(2);
  controller.sink.add(3);
  controller.sink.add(4);
  controller.sink.add(5);
  
  final streamListener2 = stream.where((val) => val%2 ==1).listen((val){
    print('홀수만  : $val');
  });
}

 /*
  * 홀수만  : 1
    짝수만 : 2
    홀수만  : 3
    짝수만 : 4
    홀수만  : 5
*/

7. 함수로 stream 사용하기

import 'dart:async';

void main() {
  calculate(2).listen((val){
    print('calculate(1) : $val');
  });
}
//1초마다 loop를 돌면서 값을 받는다.
Stream<int> calculate(int number) async* {
  for(int i = 0; i < 5; i++) {
    yield number * i;
    
    await Future.delayed(Duration(seconds: 1));
  }
}
 /*
  calculate(1) : 0
  calculate(1) : 2
  calculate(1) : 4
  calculate(1) : 6
  calculate(1) : 8
*/

8. yield*

import 'dart:async';

void main() {
  playAllStream().listen((val){
    print(val);
  });
}
//1초마다 loop를 돌면서 값을 받는다.
Stream<int> calculate(int number) async* {
  for(int i = 0; i < 5; i++) {
    yield number * i;
    
    await Future.delayed(Duration(seconds: 1));
  }
}

//yield* 은 Future의 await처럼 
//함수의 모든 값을 다받고 나서 실행을 한다.
Stream<int> playAllStream() async* {
  yield* calculate(1);
  yield* calculate(10);
}
 /*
  0
  1
  2
  3
  4
  0
  10
  20
  30
  40
*/

출처 : https://velog.io/@keemeesuu/Flutter-Dart.-Functional-Programming함수형-프로그래밍 ,
인프런_[코드팩토리][입문]Dart 언어 4시간만에 완전정복