8 个版本
0.1.3 | 2019 年 10 月 17 日 |
---|---|
0.1.2 | 2019 年 10 月 17 日 |
0.0.2 | 2019 年 10 月 12 日 |
在 #union 中排名 43
在 3 个 crate 中使用 (通过 union_export)
95KB
1.5K SLoC
union!
宏的实现。
union!
union!
- 一个宏统治一切。提供有用的快捷组合子,结合同步/异步链,将结果元组转换为元组结果,支持单线程和多线程(同步/异步)逐步执行分支。
使用此宏可以编写如下内容
#![recursion_limit = "256"]
use rand::prelude::*;
use std::sync::Arc;
use union::union_spawn;
fn generate_random_vec<T>(size: usize, max: T) -> Vec<T>
where
T: From<u8>
+ rand::distributions::uniform::SampleUniform
+ rand::distributions::uniform::SampleBorrow<T>
+ Copy,
{
let mut rng = rand::thread_rng();
(0..size)
.map(|_| rng.gen_range(T::from(0u8), max))
.collect()
}
fn is_even<T>(value: T) -> bool
where
T: std::ops::Rem<Output = T> + std::cmp::PartialEq + From<u8>,
{
value % 2u8.into() == 0u8.into()
}
fn get_sqrt<T>(value: T) -> T
where
T: Into<f64>,
f64: Into<T>,
{
let value_f64: f64 = value.into();
value_f64.sqrt().into()
}
fn power2<T>(value: T) -> T
where
T: std::ops::Mul<Output = T> + Copy,
{
value * value
}
// Problem: generate vecs filled by random numbers in parallel, make some operations on them in parallel,
// find max of each vec in parallel and find final max of 3 vecs
// Solution:
fn main() {
// Branches will be executed in parallel, each in its own thread
let max = union_spawn! {
let branch_0 =
generate_random_vec(1000, 10000000u64)
.into_iter()
// Multiply every element by himself
|> power2
>.filter(|value| is_even(*value)).collect::<Vec<_>>()
// Use `Arc` to share data with branch 1
-> Arc::new
~-> |v: Arc<Vec<_>>| {
// Extract raw poiner after sharing
let pointer = Arc::into_raw(v);
unsafe {(
// Find max and clone its value
(&*pointer)
.iter()
.max()
.map(Clone::clone),
// After this we call `from_raw` to prevent memory leak
Arc::from_raw(pointer)
)}.0
},
generate_random_vec(10000, 100000000000000f64)
.into_iter()
// Extract sqrt from every element
|> get_sqrt
// Add index in order to compare with the values of branch 0
>.enumerate()
~|> {
// Get data from branch 0 by cloning arc
let branch_0 = branch_0.clone();
let len = branch_0.len();
// Compare every element of branch 1 with element of branch 0
// with the same index and take min
move |(index, value)|
if index < len && value as u64 > branch_0[index] {
branch_0[index]
} else {
value as u64
}
}
>.max(),
generate_random_vec(100000, 100000u32)
.into_iter()
~>.max(),
map => |max0, max1, max2|
// Find final max
*[max0, max1, max2 as u64].into_iter().max().unwrap()
}
.unwrap();
println!("Max: {}", max);
}
以及像这样
#![recursion_limit="1024"]
use union::union_async;
use futures::stream::{iter, Stream};
use reqwest::Client;
use futures::future::{try_join_all, ok, ready};
use failure::{format_err, Error};
fn get_urls_to_calculate_link_count() -> impl Stream<Item = &'static str> {
iter(
vec![
"https://en.wikipedia.org/w/api.php?format=json&action=query&generator=random&grnnamespace=0&prop=revisions|images&rvprop=content&grnlimit=100",
"https://github.com/explore",
"https://twitter.com/search?f=tweets&vertical=news&q=%23news&src=unkn"
]
)
}
fn get_url_to_get_random_number() -> &'static str {
"https://www.random.org/integers/?num=1&min=0&max=500&col=1&base=10&format=plain&rnd=new"
}
async fn read_number_from_stdin() -> Result<u16, Error> {
use tokio::*;
use futures::stream::StreamExt;
let map_parse_error =
|value|
move |error|
format_err!("Value from stdin isn't a correct `u16`: {:?}, input: {}", error, value);
let mut result;
let mut reader = codec::FramedRead::new(io::BufReader::new(io::stdin()), codec::LinesCodec::new());
while {
println!("Please, enter number (`u16`)");
let next = reader.next();
result = union_async! {
next
|> |value| value.ok_or(format_err!("Unexpected end of input"))
=> |result| ready(result.map_err(|err| format_err!("Failed to apply codec: {:?}", err)))
=> |value|
ready(
value
.parse()
.map_err(map_parse_error(value))
)
!> |error| { eprintln!("Error: {:#?}", error); error}
}.await;
result.is_err()
} {}
result
}
#[tokio::main]
async fn main() {
println!(
"{} {}\n{}",
"Hello.\nThis's is the game where winner is player, which abs(value) is closest to",
"the max count of links (starting with `https://`) found on one of random pages.",
"You play against random generator (0-500)."
);
enum GameResult {
Won,
Lost,
Draw
}
let client = Client::new();
let game = union_async! {
// Make requests to several sites
// and calculate count of links starting from `https://`
get_urls_to_calculate_link_count()
|> {
// If pass block statement instead of fn, it will be placed before current step,
// so it will us allow to capture some variables from context
let ref client = client;
move |url|
// `union_async!` wraps its content into `async move { }`
union_async! {
client
.get(url).send()
=> |value| value.text()
=> |body| ok((url, body))
}
}
>.collect::<Vec<_>>()
|> Ok
=> try_join_all
!> |err| format_err!("Error retrieving pages to calculate links: {:#?}", err)
=> |results|
ok(
results
.into_iter()
.map(|(url, body)| (url, body.matches("https://").collect::<Vec<_>>().len()))
.max_by_key(|(_, link_count)| link_count.clone())
.unwrap()
)
// It waits for input in stdin before log max links count
~?> |result| {
result
.as_ref()
.map(
|(url, count)| {
let split = url.to_owned().split('/').collect::<Vec<_>>();
let domain_name = split.get(2).unwrap_or(&url);
println!("Max `https://` link count found on `{}`: {}", domain_name, count)
}
)
.unwrap_or(());
},
// In parallel it makes request to the site which generates random number
get_url_to_get_random_number()
-> ok
=> {
// If pass block statement instead of fn, it will be placed before current step,
// so it will allow us to capture some variables from context
let ref client = client;
let map_parse_error =
|value|
move |err|
format_err!("Failed to parse random number: {:#?}, value: {}", err, value);
move |url|
union_async! {
client
.get(url)
.send()
=> |value| value.text()
!> |err| format_err!("Error retrieving random number: {:#?}", err)
=> |value| ok(value[..value.len() - 1].to_owned()) // remove \n from `154\n`
=> |value|
ready(
value
.parse::<u16>()
.map_err(map_parse_error(value))
)
}
}
// It waits for input in stdin before log random value
~?> |random| {
random
.as_ref()
.map(|number| println!("Random: {}", number))
.unwrap_or(());
},
// In parallel it reads value from stdin
read_number_from_stdin(),
// Finally, when we will have all results, we can decide, who is winner
map => |(_url, link_count), random_number, number_from_stdin| {
let random_diff = (link_count as i32 - random_number as i32).abs();
let stdin_diff = (link_count as i32 - number_from_stdin as i32).abs();
match () {
_ if random_diff > stdin_diff => GameResult::Won,
_ if random_diff < stdin_diff => GameResult::Lost,
_ => GameResult::Draw
}
}
};
let _ = game.await.map(
|result|
println!(
"You {}",
match result {
GameResult::Won => "won!",
GameResult::Lost => "lose...",
_ => "have the same result as random generator!"
}
)
).unwrap();
}
组合子
-
映射:
|>
表达式 -value
.map(expression
) -
Then:
=>
表达式 -value
.and_then(expression
) -
Then:
->
表达式 -expression
(value
) -
点:
>.
表达式 -value
.expression
-
或:
<|
表达式 -value
.or(expression
) -
OrElse:
<=
表达式 -value
.or_else(expression
) -
MapErr:
!>
表达式 -value
.map_err(expression
) -
Inspect:
?>
表达式 - (|value
| {expression
(&value
);value
})(value
) 对于同步链,以及 (|value
|value
.inspect(expression
))(value
) 对于 futures
其中 value
是前一个值。
以~
为前缀的每个组合器都将作为延迟操作(所有操作将等待每一步的完成,然后才移动到下一步)。
处理器
可能是以下之一
-
map
=> 将作为 results.map(|(result0, result1, ..)| handler(result0, result1, ..)) 实现 -
and_then
=> 将作为 results.and_then(|(result0, result1, ..)| handler(result0, result1, ..)) 实现 -
then
=> 将作为 handler(result0, result1, ..) 实现
或者未指定 - 则返回 Result<(result0, result1, ..), Error> 或 Option<(result0, result1, ..)>
自定义未来crate路径
您可以在宏调用的开头指定自定义路径(futures_crate_path
)
use union::union_async;
use futures::future::ok;
#[tokio::main]
async fn main() {
let value = union_async! {
futures_crate_path(::futures)
ok::<_,u8>(2u16)
}.await.unwrap();
println!("{}", value);
}
单线程组合
简单结果组合
将输入转换为一系列链式结果并逐步合并。
use std::error::Error;
use union::union;
type Result<T> = std::result::Result<T, Box<dyn Error>>;
fn action_1() -> Result<u16> {
Ok(1)
}
fn action_2() -> Result<u8> {
Ok(2)
}
fn main() {
let sum = union! {
action_1(),
action_2().map(|v| v as u16),
action_2().map(|v| v as u16 + 1).and_then(|v| Ok(v * 4)),
action_1().and_then(|_| Err("5".into())).or(Ok(2)),
map => |a, b, c, d| a + b + c + d
}.expect("Failed to calculate sum");
println!("Calculated: {}", sum);
}
未来组合
每个分支将表示任务链。所有分支都将使用 ::futures::join!
宏和 union_async!
返回 unpolled
未来。
#![recursion_limit="256"]
use std::error::Error;
use union::union_async;
use futures::future::{ok, err};
type Result<T> = std::result::Result<T, Box<dyn Error>>;
async fn action_1() -> Result<u16> {
Ok(1)
}
async fn action_2() -> Result<u8> {
Ok(2)
}
#[tokio::main]
async fn main() {
let sum = union_async! {
action_1(),
action_2().and_then(|v| ok(v as u16)),
action_2().map(|v| v.map(|v| v as u16 + 1)).and_then(|v| ok(v * 4u16)),
action_1().and_then(|_| err("5".into())).or_else(|_| ok(2u16)),
and_then => |a, b, c, d| ok(a + b + c + d)
}.await.expect("Failed to calculate sum");
println!("Calculated: {}", sum);
}
多线程组合
要并行执行多个任务,可以使用 union_spawn!
(spawn!
)来同步任务,以及 union_async_spawn!
(async_spawn!
)来处理未来。由于 union_async
已经在一个线程中提供了并行未来执行,因此 union_async_spawn!
将每个分支都部署到 tokio
执行器,因此它们将在多线程执行器中进行评估。
多线程同步分支
union_spawn
为每个分支的每个步骤(分支数是当时的最大线程数)启动一个 ::std::thread
。
use std::error::Error;
use union::union_spawn;
type Result<T> = std::result::Result<T, Box<dyn Error + Send + Sync>>;
fn action_1() -> Result<usize> {
Ok(1)
}
fn action_2() -> Result<u16> {
Ok(2)
}
fn main() {
// Branches will be executed in parallel
let sum = union_spawn! {
action_1(),
action_2().map(|v| v as usize),
action_2().map(|v| v as usize + 1).and_then(|v| Ok(v * 4)),
action_1().and_then(|_| Err("5".into())).or(Ok(2)),
map => |a, b, c, d| a + b + c + d
}.expect("Failed to calculate sum");
println!("Calculated: {}", sum);
}
union_async_spawn!
使用 ::tokio::spawn
函数来启动任务,因此它应该在 tokio
运行时内完成(分支数是当时的最大 tokio
任务数)。
多线程未来
#![recursion_limit="256"]
use std::error::Error;
use union::union_async_spawn;
use futures::future::{ok, err};
type Result<T> = std::result::Result<T, Box<dyn Error + Send + Sync>>;
async fn action_1() -> Result<u16> {
Ok(1)
}
async fn action_2() -> Result<u8> {
Ok(2)
}
#[tokio::main]
async fn main() {
let sum = union_async_spawn! {
action_1(),
action_2().and_then(|v| ok(v as u16)),
action_2().map(|v| v.map(|v| v as u16 + 1)).and_then(|v| ok(v * 4u16)),
action_1().and_then(|_| err("5".into())).or_else(|_| ok(2u16)),
and_then => |a, b, c, d| ok(a + b + c + d)
}.await.expect("Failed to calculate sum");
println!("Calculated: {}", sum);
}
使用组合器,我们可以将第一个同步示例重写为
use std::error::Error;
use union::union;
type Result<T> = std::result::Result<T, Box<dyn Error>>;
fn action_1() -> Result<u16> {
Ok(1)
}
fn action_2() -> Result<u8> {
Ok(2)
}
fn main() {
let sum = union! {
action_1(),
action_2() |> |v| v as u16,
action_2() |> |v| v as u16 + 1 => |v| Ok(v * 4),
action_1() => |_| Err("5".into()) <| Ok(2),
map => |a, b, c, d| a + b + c + d
}.expect("Failed to calculate sum");
println!("Calculated: {}", sum);
}
通过在操作中分离链,您将使操作在进入下一步之前等待当前步骤中所有操作完成。
#![recursion_limit="256"]
use std::error::Error;
use union::union;
type Result<T> = std::result::Result<T, Box<dyn Error + Send + Sync>>;
fn action_1() -> Result<u16> {
Ok(1)
}
fn action_2() -> Result<u8> {
Ok(2)
}
fn main() {
let sum = union! {
action_1(),
let result_1 = action_2() ~|> |v| v as u16 + 1,
action_2() ~|> {
let result_1 = result_1.as_ref().ok().map(Clone::clone);
move |v| {
// `result_1` now is the result of `action_2()` [Ok(1u8)]
if result_1.is_some() {
v as u16 + 1
} else {
unreachable!()
}
}
} ~=> {
let result_1 = result_1.as_ref().ok().map(Clone::clone);
move |v| {
// `result_1` now is the result of `|v| v as u16 + 1` [Ok(2u16)]
if let Some(result_1) = result_1 {
Ok(v * 4 + result_1)
} else {
unreachable!()
}
}
},
action_1() ~=> |_| Err("5".into()) <| Ok(2),
map => |a, b, c, d| a + b + c + d
}.expect("Failed to calculate sum");
println!("Calculated: {}", sum);
}
依赖项
~1.5MB
~36K SLoC