Expand description
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 Results.
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)