콤비네이터: map
match는 Option을 처리하는 유효한 방법입니다. 하지만 특히 입력이 있을 때만 유효한 연산들의 경우, 과도한 사용은 지루할 수 있습니다. 이런 경우, 콤비네이터(combinators)를 사용하여 모듈식으로 제어 흐름을 관리할 수 있습니다.
Option에는 Some -> Some 및 None -> None의 단순 매핑을 위한 콤비네이터인 map() 메서드가 내장되어 있습니다. 여러 개의 map() 호출을 체인으로 연결하여 훨씬 더 유연하게 사용할 수 있습니다.
다음 예제에서 process()는 간결함을 유지하면서 그 이전의 모든 함수들을 대체합니다.
#![allow(dead_code)]
#[derive(Debug)] enum Food { Apple, Carrot, Potato }
#[derive(Debug)] struct Peeled(Food);
#[derive(Debug)] struct Chopped(Food);
#[derive(Debug)] struct Cooked(Food);
// 음식을 껍질 벗깁니다. 음식이 없다면 `None`을 반환합니다.
// 그렇지 않으면 껍질을 벗긴 음식을 반환합니다.
fn peel(food: Option<Food>) -> Option<Peeled> {
match food {
Some(food) => Some(Peeled(food)),
None => None,
}
}
// 음식을 썹니다. 음식이 없다면 `None`을 반환합니다.
// 그렇지 않으면 썬 음식을 반환합니다.
fn chop(peeled: Option<Peeled>) -> Option<Chopped> {
match peeled {
Some(Peeled(food)) => Some(Chopped(food)),
None => None,
}
}
// 음식을 요리합니다. 여기서는 케이스 처리를 위해 `match` 대신 `map()`을 보여줍니다.
fn cook(chopped: Option<Chopped>) -> Option<Cooked> {
chopped.map(|Chopped(food)| Cooked(food))
}
// 음식을 순서대로 껍질 벗기고, 썰고, 요리하는 함수입니다.
// 코드를 단순화하기 위해 `map()`을 여러 번 체인으로 연결합니다.
fn process(food: Option<Food>) -> Option<Cooked> {
food.map(|f| Peeled(f))
.map(|Peeled(f)| Chopped(f))
.map(|Chopped(f)| Cooked(f))
}
// 먹기 전에 음식이 있는지 없는지 확인합니다!
fn eat(food: Option<Cooked>) {
match food {
Some(food) => println!("음~ 전 {:?}가 정말 좋아요", food),
None => println!("오 이런! 먹을 수 있는 게 아니었어요."),
}
}
fn main() {
let apple = Some(Food::Apple);
let carrot = Some(Food::Carrot);
let potato = None;
let cooked_apple = cook(chop(peel(apple)));
let cooked_carrot = cook(chop(peel(carrot)));
// 이제 더 간단해 보이는 `process()`를 시도해 봅시다.
let cooked_potato = process(potato);
eat(cooked_apple);
eat(cooked_carrot);
eat(cooked_potato);
}