Crate comp [−] [src]
Pure-macro Do notation and List-comprehension for Option, Result and Iterator.
It provides syntax extensions to easily combind wrapper type (Option
, Result
and Iterator
),
which seems like for-comprehension
in scala or Do notation
in haskell.
Usage
First, add the following to your Cargo.toml
:
[dependencies]
comp = "0.1"
Next, add this to your crate root:
#[macro_use] extern crate comp;
Example
comp-rs
delivers three macros : option!
, result!
and iter!
,
transforming the arrow(<-)
statements into FP binding( flat_map()
).
Iterator
#[macro_use] extern crate comp; let iter = iter! { let x <- 0..2u8; let y <- vec!['a', 'b']; (x, y) }; for x in iter { println!("{:?}", x); } // Print (0, 'a') (0, 'b') (1, 'a') (1, 'b')
Option
#[macro_use] extern crate comp; let option = option! { let a <- Some(1); let b <- Some(2); a + b }; assert_eq!(option, Some(3));
Result
Unlike Iterator
and Option
, rust provides Question Mark syntax to combine Result
s.
Let's see how comp-rs
makes it more explicit and expressive.
Native way
use std::fs::File; use std::io; use std::io::prelude::*; // try!() macro must be wrap into a function fn content() -> io::Result<String> { let mut f = try!(File::open("foo.txt")); let mut s = String::new(); try!(f.read_to_string(&mut s)); Ok(s) }
Question mark
use std::fs::File; use std::io; use std::io::prelude::*; // '?' mark must be wrap into a function fn content() -> io::Result<String> { let mut f = File::open("foo.txt")?; let mut s = String::new(); f.read_to_string(&mut s)?; Ok(s) }
result!
way
use std::fs::File; use std::io; use std::io::prelude::*; let content: io::Result<String> = result! { let mut f <- File::open("foo.txt"); let mut s = String::new(); let _ <- f.read_to_string(&mut s); s };
Syntax
All three macros return wrapped type(Option<T>
, Result<T>
and
Iterator<Item=T>
), and yield the last expression.
Syntax: (sentence)* ; expression
sentence can be:
* let pattern <- expression;
: bind expression to pattern.
if filter_expression;
: filter by condition, and jump over when not satisfied.statement;
: let assignment, value assignment, etc.{...}
: block and unsafe block.
Syntax Detail
1. Basic arrow(<-) syntax
Rules
Macro Expand | option! {} | Some(()) | option! { x; } | { x; Some(()) } | option! { x } | Some(x) | Macro ------------------------------------ Expand option! { let x <- Some(1); } ------------------------------------ Some(1).and_then(move |x| option!{}) option! { let x <- Some(1); x } ------------------------------------ Some(1).and_then(move |x| option!{ x }) option! { let mut x <- Some(1); x } ------------------------------------ Some(1).and_then(move |mut x| option!{ x })
Example
let option = option! { let a <- Some(1); let b <- Some(2); a + b }; // code above is expanded roughly into this let option = { Some(1).and_then(move |a| { Some(2).and_then(move |b| { Some(a + b) }) }) };
let iter = iter! { let x <- 0..2; let y <- vec!['a', 'b']; (x, y) }; // code above is expanded roughly into this let iter = { (0..2).into_iter().flat_map(move |x| { (vec!['a', 'b']).into_iter().flat_map(move |y| { ::std::iter::once((x, y)) }) }) };
2. Yield
The last expression of the block will be yielded, similar to functions in rust.
let iter = iter! { let x <- 0..2; let y <- vec!['a', 'b']; (x, y) // <------- Yield };
The block yields ()
while the last line is arrow statement or statement
with semicolon.
let option: Option<()> = option! { let a <- Some(1); let b <- Some(2); }; let option: Option<()> = option! { let a <- Some(1); let b <- Some(2); a + b; };
3. Pattern
In comp-rs
, pattern is supported as it should be.
Tuple
let option = option! { let (x, y) <- Some((1, 2)); (y, x) }; assert_eq!(option, Some((2, 1)));
Struct
struct Struct { x: usize }; let option = option! { let Struct { x } <- Some(Struct { x: 1 }); x }; assert_eq!(option, Some(1));
Ignore
let option = option! { let _ <- Some(1); };
4. If-Guard
If-Guard is specific for iter!
which translates condition into filter()
.
It wraps the following code into a block and call filter()
on it.
let iter = iter! { let x <- 0..4; let y <- 2..6; if x == y; // won't reach here if condition isn't satisfied (x, y) }; let expected = vec![(2, 2), (3, 3)]; assert_eq!(expected, iter.collect::<Vec<_>>());
5. Statement & Block
Statements and blocks are also supported.
// statement let iter = iter! { let start = 5; let end; end = start * 3; // 5, 6, ..., 13, 14 let x <- start..end; x }; let expected = 5..15; assert!(iter.eq(expected.into_iter()));
let iter = iter! { let mut a <- 0..5; // block { fn double(x: u8) -> u8 { x * 2} let tmp = double(a); a = tmp; }; // unsafe block let count = unsafe { static mut CALL_COUNT: u8 = 0; CALL_COUNT += 1; CALL_COUNT }; (a, count) }; let expected = vec![(0, 1), (2, 2), (4, 3), (6, 4), (8, 5)]; assert!(iter.eq(expected.into_iter()));
Array
Array
in rust behaves differently from other collections. It only iterates its
content by reference.
So iter!
always binds references in arrow(<-)
syntax, then you need to
deref the bound value.
And since one can't move any value out of an array
, array should be placed
outside the macro to satisfy lifetime.
let array = [0, 1, 2, 3]; let iter = iter! { let x <- array; let y <- *x..4; (*x, y) }; let expected = vec![(0, 0), (0, 1), (0, 2), (0, 3), (1, 1), (1, 2), (1, 3), (2, 2), (2, 3), (3, 3)]; assert_eq!(expected, iter.collect::<Vec<_>>());
Contribution
All kinds of contribution are welcome.
- Issue. Feel free to open an issue when you find typos, bugs, or have any question.
- Pull requests. Better implementation, more tests, more documents and typo fixes are all welcome.
License
Licensed under MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
Macros
iter |
syntax extension specific for Iterator |
option |
syntax extension specific for Option |
result |
syntax extension specific for Result |