title | slug |
---|---|
Combinators |
combinators |
-
One meaning of “combinator” is a more informal sense referring to the combinator pattern, a style of organizing libraries centered around the idea of combining things. Usually there is some type T, some functions for constructing “primitive” values of type T, and some “combinators” which can combine values of type T in various ways to build up more complex values of type T. The other definition is "function with no free variables". __ wiki.haskell.org
-
A combinator is a function which builds program fragments from program fragments; in a sense the programmer using combinators constructs much of the desired program automatically, rather that writing every detail by hand. __ John Hughes—Generalizing Monads to Arrows via Functional Programming Concepts
The exact definition of "combinators" in Rust ecosystem is bit unclear.
-
or()
,and()
,or_else()
,and_then()
- Combine two values of type T and return same type T.
-
filter()
forOption
types- Filter type T by using a closure as a conditional function
- Return same type T
-
map()
,map_err()
- Convert type T by applying a closure.
- The data type of the value inside T can be changed.
ex.
Some<&str>
can be converted toSome<usize>
orErr<&str>
toErr<isize>
and etc.
-
map_or()
,map_or_else()
- Transform type T by applying a closure & return the value inside type T.
- For
None
andErr
, a default value or another closure is applied.
-
ok_or()
,ok_or_else()
forOption
types- Transform
Option
type into aResult
type.
- Transform
-
as_ref()
,as_mut()
- Transform type T into a reference or a mutable reference.
While combining two expressions, which return either Option
/ Result
or()
: If either one gotSome
orOk
, that value returns immediately.and()
: If both gotSome
orOk
, the value in the second expression returns. If either one gotNone
orErr
that value returns immediately.
fn main() {
let s1 = Some("some1");
let s2 = Some("some2");
let n: Option<&str> = None;
let o1: Result<&str, &str> = Ok("ok1");
let o2: Result<&str, &str> = Ok("ok2");
let e1: Result<&str, &str> = Err("error1");
let e2: Result<&str, &str> = Err("error2");
assert_eq!(s1.or(s2), s1); // Some1 or Some2 = Some1
assert_eq!(s1.or(n), s1); // Some or None = Some
assert_eq!(n.or(s1), s1); // None or Some = Some
assert_eq!(n.or(n), n); // None1 or None2 = None2
assert_eq!(o1.or(o2), o1); // Ok1 or Ok2 = Ok1
assert_eq!(o1.or(e1), o1); // Ok or Err = Ok
assert_eq!(e1.or(o1), o1); // Err or Ok = Ok
assert_eq!(e1.or(e2), e2); // Err1 or Err2 = Err2
assert_eq!(s1.and(s2), s2); // Some1 and Some2 = Some2
assert_eq!(s1.and(n), n); // Some and None = None
assert_eq!(n.and(s1), n); // None and Some = None
assert_eq!(n.and(n), n); // None1 and None2 = None1
assert_eq!(o1.and(o2), o2); // Ok1 and Ok2 = Ok2
assert_eq!(o1.and(e1), e1); // Ok and Err = Err
assert_eq!(e1.and(o1), e1); // Err and Ok = Err
assert_eq!(e1.and(e2), e1); // Err1 and Err2 = Err1
}
🔎 Rust nightly support
xor()
forOption
types, which returnsSome
only if one expression gotSome
, but not both.
Similar to or()
. The only difference is, the second expression should be a closure which returns same type T.
fn main() {
// or_else with Option
let s1 = Some("some1");
let s2 = Some("some2");
let fn_some = || Some("some2"); // similar to: let fn_some = || -> Option<&str> { Some("some2") };
let n: Option<&str> = None;
let fn_none = || None;
assert_eq!(s1.or_else(fn_some), s1); // Some1 or_else Some2 = Some1
assert_eq!(s1.or_else(fn_none), s1); // Some or_else None = Some
assert_eq!(n.or_else(fn_some), s2); // None or_else Some = Some
assert_eq!(n.or_else(fn_none), None); // None1 or_else None2 = None2
// or_else with Result
let o1: Result<&str, &str> = Ok("ok1");
let o2: Result<&str, &str> = Ok("ok2");
let fn_ok = |_| Ok("ok2"); // similar to: let fn_ok = |_| -> Result<&str, &str> { Ok("ok2") };
let e1: Result<&str, &str> = Err("error1");
let e2: Result<&str, &str> = Err("error2");
let fn_err = |_| Err("error2");
assert_eq!(o1.or_else(fn_ok), o1); // Ok1 or_else Ok2 = Ok1
assert_eq!(o1.or_else(fn_err), o1); // Ok or_else Err = Ok
assert_eq!(e1.or_else(fn_ok), o2); // Err or_else Ok = Ok
assert_eq!(e1.or_else(fn_err), e2); // Err1 or_else Err2 = Err2
}
Similar to and()
. The only difference is, the second expression should be a closure which returns same type T.
fn main() {
// and_then with Option
let s1 = Some("some1");
let s2 = Some("some2");
let fn_some = |_| Some("some2"); // similar to: let fn_some = |_| -> Option<&str> { Some("some2") };
let n: Option<&str> = None;
let fn_none = |_| None;
assert_eq!(s1.and_then(fn_some), s2); // Some1 and_then Some2 = Some2
assert_eq!(s1.and_then(fn_none), n); // Some and_then None = None
assert_eq!(n.and_then(fn_some), n); // None and_then Some = None
assert_eq!(n.and_then(fn_none), n); // None1 and_then None2 = None1
// and_then with Result
let o1: Result<&str, &str> = Ok("ok1");
let o2: Result<&str, &str> = Ok("ok2");
let fn_ok = |_| Ok("ok2"); // similar to: let fn_ok = |_| -> Result<&str, &str> { Ok("ok2") };
let e1: Result<&str, &str> = Err("error1");
let e2: Result<&str, &str> = Err("error2");
let fn_err = |_| Err("error2");
assert_eq!(o1.and_then(fn_ok), o2); // Ok1 and_then Ok2 = Ok2
assert_eq!(o1.and_then(fn_err), e2); // Ok and_then Err = Err
assert_eq!(e1.and_then(fn_ok), e1); // Err and_then Ok = Err
assert_eq!(e1.and_then(fn_err), e1); // Err1 and_then Err2 = Err1
}
💡 Usually in programming languages
filter
functions are used with arrays or iterators to create a new array/ iterator by filtering own elements via a function/ closure. Rust also providesfilter()
as an iterator adaptor to apply a closure on each element of an iterator to transform it into another iterator. However in here we are talking about the functionality offilter()
withOption
types.
The same Some
type is returned, only if we pass a Some
value and the given closure returned true for it. None
is returned, if None
type passed or the closure returned false. The closure uses the value inside Some
as an argument. Still Rust support filter()
only for Option
types.
fn main() {
let s1 = Some(3);
let s2 = Some(6);
let n = None;
let fn_is_even = |x: &i8| x % 2 == 0;
assert_eq!(s1.filter(fn_is_even), n); // Some(3) -> 3 is not even -> None
assert_eq!(s2.filter(fn_is_even), s2); // Some(6) -> 6 is even -> Some(6)
assert_eq!(n.filter(fn_is_even), n); // None -> no value -> None
}
💡 Usually in programming languages
map()
functions are used with arrays or iterators, to apply a closure on each element of the array or iterator. Rust also providesmap()
as an iterator adaptor to apply a closure on each element of an iterator to transform it into another iterator. However in here we are talking about the functionality ofmap()
withOption
andResult
types.
map()
: Convert type T by applying a closure. The data type ofSome
orOk
blocks can be changed according to the return type of the closure. ConvertOption<T>
toOption<U>
,Result<T, E>
toResult<U, E>
⭐ Via map()
, only Some
and Ok
values are getting changed. No affect to the values inside Err
(None
doesn’t contain any value at all).
fn main() {
let s1 = Some("abcde");
let s2 = Some(5);
let n1: Option<&str> = None;
let n2: Option<usize> = None;
let o1: Result<&str, &str> = Ok("abcde");
let o2: Result<usize, &str> = Ok(5);
let e1: Result<&str, &str> = Err("abcde");
let e2: Result<usize, &str> = Err("abcde");
let fn_character_count = |s: &str| s.chars().count();
assert_eq!(s1.map(fn_character_count), s2); // Some1 map = Some2
assert_eq!(n1.map(fn_character_count), n2); // None1 map = None2
assert_eq!(o1.map(fn_character_count), o2); // Ok1 map = Ok2
assert_eq!(e1.map(fn_character_count), e2); // Err1 map = Err2
}
map_err()
forResult
types : The data type ofErr
blocks can be changed according to the return type of the closure. ConvertResult<T, E>
toResult<T, F>
.
⭐ Via map_err()
, only Err
values are getting changed. No affect to the values inside Ok
.
fn main() {
let o1: Result<&str, &str> = Ok("abcde");
let o2: Result<&str, isize> = Ok("abcde");
let e1: Result<&str, &str> = Err("404");
let e2: Result<&str, isize> = Err(404);
let fn_character_count = |s: &str| -> isize { s.parse().unwrap() }; // convert str to isize
assert_eq!(o1.map_err(fn_character_count), o2); // Ok1 map = Ok2
assert_eq!(e1.map_err(fn_character_count), e2); // Err1 map = Err2
}
Hope you remember the functionality of unwrap_or()
and unwrap_or_else()
functions. These functions also bit similar to them. But map_or()
and map_or_else()
apply a closure on Some
and Ok
values and return the value inside type T.
map_or()
: Support only forOption
types (not supportingResult
). Apply the closure to the value insideSome
and return the output according to the closure. The given default value is returned forNone
types.
fn main() {
const V_DEFAULT: i8 = 1;
let s = Some(10);
let n: Option<i8> = None;
let fn_closure = |v: i8| v + 2;
assert_eq!(s.map_or(V_DEFAULT, fn_closure), 12);
assert_eq!(n.map_or(V_DEFAULT, fn_closure), V_DEFAULT);
}
map_or_else()
: Support for bothOption
andResult
types (Result
still nightly only). Similar tomap_or()
but should provide another closure instead a default value for the first parameter.
⭐ None
types doesn’t contain any value. So no need to pass anything to the closure as input with Option
types. But Err
types contain some value inside it. So default closure should able to read it as an input, while using this with Result
types.
#![feature(result_map_or_else)] // enable unstable library feature 'result_map_or_else' on nightly
fn main() {
let s = Some(10);
let n: Option<i8> = None;
let fn_closure = |v: i8| v + 2;
let fn_default = || 1; // None doesn't contain any value. So no need to pass anything to closure as input.
assert_eq!(s.map_or_else(fn_default, fn_closure), 12);
assert_eq!(n.map_or_else(fn_default, fn_closure), 1);
let o = Ok(10);
let e = Err(5);
let fn_default_for_result = |v: i8| v + 1; // Err contain some value inside it. So default closure should able to read it as input
assert_eq!(o.map_or_else(fn_default_for_result, fn_closure), 12);
assert_eq!(e.map_or_else(fn_default_for_result, fn_closure), 6);
}
As mentioned earlier, ok_or()
, ok_or_else()
transform Option
type into Result
type. Some
to Ok
and None
to Err
.
ok_or()
: A defaultErr
message should pass as argument.
fn main() {
const ERR_DEFAULT: &str = "error message";
let s = Some("abcde");
let n: Option<&str> = None;
let o: Result<&str, &str> = Ok("abcde");
let e: Result<&str, &str> = Err(ERR_DEFAULT);
assert_eq!(s.ok_or(ERR_DEFAULT), o); // Some(T) -> Ok(T)
assert_eq!(n.ok_or(ERR_DEFAULT), e); // None -> Err(default)
}
ok_or_else()
: Similar took_or()
. A closure should be passed as the argument.
fn main() {
let s = Some("abcde");
let n: Option<&str> = None;
let fn_err_message = || "error message";
let o: Result<&str, &str> = Ok("abcde");
let e: Result<&str, &str> = Err("error message");
assert_eq!(s.ok_or_else(fn_err_message), o); // Some(T) -> Ok(T)
assert_eq!(n.ok_or_else(fn_err_message), e); // None -> Err(default)
}
🔎 As mentioned earlier, these functions are used to borrow type T as a reference or as a mutable reference.
as_ref()
: ConvertOption<T>
toOption<&T>
andResult<T, E>
toResult<&T, &E>
as_mut()
: ConvertsOption<T>
toOption<&mut T>
andResult<T, E>
toResult<&mut T, &mut E>