#monads #macro #transformer #vec-deque #linked-list #reader #reader-writer

monadic

用于定义 Haskell 风格的单子动作块,适用于 IntoIterators、Reader、Writer,以及 Vec、LinkedList 和 VecDeque 上 ReaderT 和 WriterT 的宏

51 个版本 (4 个破坏性更新)

0.5.5 2019 年 12 月 29 日
0.5.4 2019 年 12 月 20 日
0.4.10 2019 年 12 月 11 日
0.3.14 2019 年 12 月 3 日
0.1.16 2019 年 11 月 22 日

#594Rust 模式

Download history 1/week @ 2024-03-10 171/week @ 2024-03-31 3/week @ 2024-05-19

每月 98 次下载

MIT 许可证

53KB
623

rust-monadic

mdo! 宏

用于编写 Haskell 风格单子代码的宏

适用于 IntoIterator (可迭代) 作为单子

每个单子表达式步骤都是一个 flat_mapped 表达式,与剩余部分合并成一个惰性 FlatMap 表达式,该表达式实现 IntoIterator,使用捕获环境和参数的 move 闭包 作为 Fn(A) -> U: IntoIterator。lambda 体将递归解析为单子,其类型也应该是 IntoIterator 的实例。

除了实现 IntoIterator 的类型外,所有迭代器都按照 文档 中所述进行。

monad 模块中定义了 BindMonad 特性,作为 IntoIterator 的超特性。

以下是宏 mdo 的模式表,其中 monadic_expression 是类型之一,必须是 IntoIterator 的实例

* 绑定单子结果 `identifier "<-" monadic_expression ";"`
* 提升值并绑定 `identifier "<-" "pure" expression ";"`
* 解引用共享可迭代项的项引用 `"&" identifier "<-" &iterable ";"`
* 组合单子结果 `"let" identifier "=" expression ";"`
* 过滤结果 `"guard" boolean_expression ";"`
* 忽略单子结果 “_” <-- monadic_expression ";"
返回表达式值 “pure” return_expression
以单子表达式结束 monadic_expression

注意:在宏内部,let 只引入一个绑定。

示例1:类似于 Haskell 的单子推导(文件:examples/comprehension.rs)

use monadic::{mdo, monad::{Bind, Monad}};
use num::Integer;

fn main() {
    let xs = mdo!{ 
    
        x <- 1..7;
        y <- 1..x;
        guard (&y).is_odd() ;
        let z = match x.is_even() { 
                    true => &y + 1,
                    _ => &y - 1,
                };
        pure (x, z)
        
    }.collect::<Vec<_>>();
    
    println!("result: {:?}", xs); 
}

执行

$ cargo run --example comprehension

result: [(2, 2), (3, 0), (4, 2), (4, 4), (5, 0), (5, 2), (6, 2), (6, 4), (6, 6)]

示例2:引用容器和 lambda 参数位置的变体(文件:examples/comprehension2.rs)

use monadic::{mdo, monad::{Bind, Monad}};
use num::Integer;

fn main() {
    let xs = mdo!{ 
    
        &x <- &vec![1,2,3,4];   // with item ref pattern (&x) in the lambda argument position
        guard x.is_odd() ;
        let z = x + 1 ;
        pure (x, z)
        
    }.collect::<Vec<_>>();
    
    println!("result: {:?}", xs); 
}

执行

$ cargo run --example comprehension2

result: [(1, 2), (3, 4)]

示例:控制台 I/O。如果您想返回 String 变量,可以通过克隆来实现

// example console io

use monadic::{mdo, monad::{Bind, Monad}, 
                   mio::{read_line, print_str, stdout_flush}};

fn main() {
    let res =mdo!{
    
                x <- pure 1;
                let y = x + 1;
                
                _ <- print_str("enter integer i32>");
                _ <- stdout_flush();
                
                li1 <- read_line();
                z <- li1.trim().parse::<i32>() ;
                
                pure (y, z, li1.clone())
                
              }.collect::<Vec<_>>();

    println!("result: {:?}", res);              
}
$ cargo run --example console_io

enter integer i32>10
result: [(2, 10, "10")]

Reader 单子宏 rdrdo!

一个 Reader 单子 适配宏示例

//! examples/reader1
//!
//! You must specify in a type restriction the type of the environment of the Reader bloc
//!
//! `local` can be used as a function or as a method

use monadic::{rdrdo, reader::{Reader, ask, local}};
use partial_application::partial;
use std::collections::HashMap;

type Env = HashMap<String, i32>;

fn immutable_insert( k_slice: &str, v: i32, dict: Env) -> Env {
   let mut dict1 = dict.clone();
   dict1.insert( String::from(k_slice), v);
   dict1
}

fn my_initial_env() -> Env {
   immutable_insert( "a", 1, HashMap::new())
}   


fn main() {

  let modify_env = partial!(immutable_insert => "b", 2, _);
  
  let bloc1: Reader<'_, Env, _>  = rdrdo!{
  
       env1 <- ask();
       
       // run a subbloc with a modified environment
       pair <- local( modify_env, rdrdo!{ 
       
               x <- pure 9;
               y <- ask();
               pure (x, y)
             }) ;
             
       pure (env1.clone(), pair.0, pair.1)      
    };


  let res = bloc1.initial_env( my_initial_env() );

  println!("result: {:?}", res);  
}

执行

$ cargo run --example reader1

result: ({"a": 1}, 9, {"b": 2, "a": 1})

ReaderT 单子变换宏 rdrt_mdo!

这个单子变换器是严格的,并且只适用于实现了 Monad + FromIterator + Clone 的内部单子,仅限于 Vec、LinkedList 和 VecDeque。您可以使用 lift 与任意单子混合指令,因为绑定是通过迭代 IntoIteratorinto_iter().flat_map().collect() 来发生的。

这个宏需要更多的类型注解,因为内部单子和 lambda 参数可能是不确定的。

为了减少类型注解,它们通过宏插入 ask(),使用 Env 作为环境类型别名,它必须被定义。

pure return_expression 通过宏转换为 ReaderT::lift( Vec::pure( return_expression))

示例

// examples/reader_trans1

#[allow(unused_imports)]
use monadic::{rdrt_mdo, monad::{Monad}, 
              reader_trans::{ReaderT, ask, local}};
use num::Integer;
use partial_application::partial;
use std::collections::HashMap;

/// mandatory type alias Env as it is used in the macro
/// to save you type annotations
type Env = HashMap<String, i32>; 

fn immutable_insert( k_slice: &str, v: i32, dict: Env) -> Env {
   let mut dict1 = dict.clone();
   dict1.insert( String::from(k_slice), v);
   dict1
}

fn my_initial_env() -> Env {
   immutable_insert( "a", 1, HashMap::new())
}   

fn main() {
  let modify_env = partial!(immutable_insert => "b", 2, _);

  // example with Vec as the nested monad
  
  let bloc = rdrt_mdo!{   // possible type restriction as ReaderT<'_, Env, Vec<_>>
  
       env1 <- ask(); // the macro adds the type annotation as ReaderT<'_, Env, Vec<Env>>
       
       // run a subblock with a modified env.
       pair <- local( modify_env, rdrt_mdo!{
       
               // x <- lift (5..9).collect::<Vec<_>>();
               x <- lift_iter 5..9;
               
               guard x.is_odd();
               
               let z = x + 1;
               y <- ask();
               
               pure (z, y)   // equivalent to lift Vec::pure((z, y))
             }) ;
             
       pure (env1.clone(), pair.0, pair.1)      
    };

  // applying the initial_env() to the transformer (env -> m a) 
  // returns the nested monad structure
  
  let res = bloc.initial_env( my_initial_env() );

  println!("result: {:?}", res);  
}

执行

$ cargo run --example reader_trans1

result: [({"a": 1}, 6, {"a": 1, "b": 2}), ({"a": 1}, 8, {"a": 1, "b": 2})]

Writer 单子宏 wrdo!

一个 Writer 单子 适配宏示例,使用 String 作为日志记录器,来自 examples/writer1.rs

//! examples/writer1.rs
//!
//! you may set the logger type 
//! by beginning with a `tell...` function within the macro `wrdo` 
//! or by declaring it as the result type 
//! where String is the default if omitted
//! as in `let res : Writer< _, String > = wrdo!{...}`
//!
//! `censor(), listen() and listens()` can be used as functions or as methods of a Writer bloc
#[allow(unused_imports)]
use monadic::{wrdo, writer::{Writer, tell, tell_str, censor, listen}};
use monadic::util::concat_string_str;
use partial_application::partial;

type Log = String;

fn main() {
    
    let modify_log = partial!( concat_string_str => _, "log2");
    
    let res : Writer< _, Log> = wrdo!{ 
    
        _ <- tell_str( "log1") ;
        
        // run a subbloc and modify the log afterwards
        pair <- censor( modify_log,
                   wrdo!{
                        _ <- tell_str("sub");
                        pure 2
                    }.listen());
        pure pair            
        }.listen() ;
    
    println!("result: {:?}", res.unwrap()); 
}

执行

$ cargo run --example writer1

result: ((2, "sub"), "log1sublog2")

示例 2 使用 Vec 作为日志记录器,来自 examples/writer2.rs

//! examples/writer2.rs
//! 
//! you may set the logger type 
//! by beginning with a `tell...` function within the macro `wrdo` 
//! or by declaring it as the result type 
//! where String is the default if omitted
//! as in `let res : Writer< _, Vec<_> > = wrdo!{...}`
//!
//! `censor(), listen() and listens()` can be used as functions or as methods of a Writer bloc
#[allow(unused_imports)]
use monadic::{wrdo, writer::{Writer, tell, censor, listen}};
use monadic::util::concat_vec_array;
use partial_application::partial;

type Log = Vec<i32>;

fn main() {

    let modify_log = partial!( concat_vec_array => _, &[4,5,6]);
    
    let res : Writer< _, Log> = wrdo!{ 
    
        _ <- tell( vec![1,2,3]) ;
        
        // run a subbloc and modify the log afterwards
        pair <- censor( modify_log,
                   wrdo!{
                        _ <- tell( vec![0]) ;
                        pure 2
                    }.listen());
        pure pair            
        }.listen() ;
    
    println!("result: {:?}", res.unwrap()); 
}
$ cargo run --example writer2

result: ((2, [0]), [1, 2, 3, 0, 4, 5, 6])

WriterT 单子变换宏 wrt_mdo!

仅适用于 Vec、LinkedList 或 VecDeque 作为内部单子。您可以提升任意单子的表达式,因为绑定是通过迭代和收集来完成的。

添加了宏关键字 tell_str、tell_array、tell_vec、tell_string,它们可以保存对单子的类型注解,因为宏输出会为您完成这项工作。它们使用宏输出类型注解中的 Log 类型别名。

现在,关键字 pure return_expresion 通过 Vec::pure(return_expression) 提升返回表达式

示例

//! examples/writer_trans1.rs
//!
//! you may set the logger type 
//! by beginning with a `tell...` function within the macro `wrdo` 
//! or by declaring it as the result type 
//! where String is the default if omitted
//! as in `let res : WriterT< _, Log > = wrdo!{...}`
//!
//! `censor(), listen() and listens()` can be used as functions or as methods of a Writer bloc

#[allow(unused_imports)]
use monadic::{wrt_mdo, monad::Monad, writer_trans::{WriterT, tell, tell_str, tell_array, censor, listen}};
use monadic::util::concat_string_str;
use partial_application::partial;
use num::Integer;

#[allow(dead_code)]
type Log = String; // used in some macro constructs

fn main() {
    
    let modify_log = partial!( concat_string_str => _, "log2");
    
    let bloc = wrt_mdo!{  // : WriterT< Vec<_>>  // type param. `log` defaults to String
    
        _ <- tell_str "log1" ;
        
        // x <- lift (5..9).collect::<Vec<_>>() ;
        x <- lift_iter 5..9 ;
        
        guard x.is_odd() ;
        let z = x + 1;
        
        // run a subbloc and modify its log afterwards
        pair <- censor( modify_log,
                        wrt_mdo!{
                            _ <- tell_str "sub";
                            pure 2
                        }.listen()
                      );
                    
        pure (z, pair.0, pair.1)
        }.listen() ;
        
    // unwrap() returns the nested monad structure       
    let res = bloc.unwrap(); 
    
    println!("result: {:?}", res); 
}

执行

$ cargo run --example writer_trans1

result: [((6, 2, "sub"), "log1sublog2"), ((8, 2, "sub"), "log1sublog2")]

使用 Vec 作为日志记录器的示例

//! examples/writer_trans2.rs
//!
//! you may set the logger type 
//! by beginning with a `tell...` function within the macro `wrdo` 
//! or by declaring it as the result type 
//! where String is the default if omitted
//! as in `let res : WriterT< _, Vec<_>> = wrdo!{...}`
//!
//! `censor(), listen() and listens()` can be used as functions or as methods of a Writer bloc

#[allow(unused_imports)]
use monadic::{wrt_mdo, monad::Monad, writer_trans::{WriterT, tell, tell_str, tell_array, censor, listen}};
use monadic::util::concat_vec_array;
use partial_application::partial;
use num::Integer;

/// mandatory type alias Log only if it is not the default
/// as it is used in the macro
/// to save you type annotations
type Log = Vec<i32>;

fn main() {
    
    let modify_log = partial!( concat_vec_array => _, &[4,5,6]);
    
    let bloc = wrt_mdo!{  // : WriterT< Vec<_>, Log>
    
        _ <- tell_array &[1,2,3] ;
        
        x <- lift (5..9).collect::<Vec<_>>() ;
        
        guard x.is_odd() ;
        let z = x + 1;
        
        // run a subbloc and modify its log afterwards
        pair <- censor( modify_log,
                        wrt_mdo!{
                            _ <- tell_array &[0];
                            pure 2
                        }.listen()
                      );
                    
        pure (z, pair.0, pair.1)            
        }.listen() ;
        
    // unwrap() returns the nested monad structure       
    let res = bloc.unwrap(); 
    
    println!("result: {:?}", res); 
}
$ cargo run --example writer_trans2

result: [((6, 2, [0]), [1, 2, 3, 0, 4, 5, 6]), ((8, 2, [0]), [1, 2, 3, 0, 4, 5, 6])]

State 单子宏 stdo!

一个 State 单子 适配宏示例,来自 examples/state1.rs

//! examples/state1.rs
//!
//! You may specify in a type restriction the type of the State bloc
//! or apply it directly to an initial_state without the type restriction

use monadic::{stdo, state::{State, get, put}};

type St = i32;

fn main() {

  let bloc: State<'_, St, _> = stdo!{ 
  
       x <- pure 9;
       y <- get();
       
       _ <- put( 1);
       z <- get(); 
       
       pure (x, y, z) 
       
    };
    
    let res = bloc.initial_state(0) ;

  println!("result: {:?}", res);  
}

执行。

$ cargo run --example state1

result: ((9, 0, 1), 1)

StateT 单子变换宏 stt_mdo!

use monadic::{stt_mdo, state_trans::{StateT, get, put}};
use num::Integer;

// mandatory type alias as it is used within the macro for type annotations
type St = i32;

fn main() {
  let bloc = stt_mdo!{ // : StateT<'_, St, Vec<_>, _>    // StateT<'a, St, Monad, A>
  
       // x <- lift (5..9).collect::<Vec<_>>() ;
       x <- lift_iter 5..9 ;                        // lift_iter iterator
       guard x.is_odd();
       
       y <- get() ; 
       
       _ <- put( 1) ; 
       z <- get() ;
       
       let v = x +1 ; 
       
       pure (v, y, z)
    };
  
  // returns the monad within the transformer boxed function (s -> m (a,s))
  let res = bloc.initial_state( 0);

  println!("result: {:?}", res);  
}

执行

$ cargo run --example state_trans1

result: [((6, 0, 1), 1), ((8, 0, 1), 1)]

一些测试

$ cargo test
running 1 test
test monad::tests::prop_monad_comprehension_vs_iteration ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

更改

v. 0.5.5: 清理说明

v. 0.5.4: 说明中存在错别字

v. 0.5.3: 基于功能的条件编译,功能 ["reader", "reader_trans", "writer", "writer_trans", "state", "state_trans"]。默认情况下,所有功能都会编译。模块 Monad 以及其宏 "mdo" 是无条件编译的。

[dependencies.monadic]
version = "~0.5"
default-features = false
features = ["reader_trans"]    # pick the modules of your interest.

v. 0.5.2: 向 ReaderT 和 WriterT 添加了 lift_iter,以及在宏中的引用绑定模式 (&v)

v. 0.5.1: StateT 变换器宏

v. 0.5.0: 更新 ReaderT 和 WriterT 变换器宏以减少类型注解的数量

  • 宏生产 "pure" $expr 转换为 lift(Vec::pure($exp))
  • ReaderT 宏生成器 "$v <- ask()" 在输出中使用类型别名 Env 生成类型注解。
  • WriterT 宏生成器 "_ <- tell_.. $expr" 在输出中使用类型别名 Log 生成类型注解。

v. 0.4.10: 向 ReaderT 和 WriterT 转换宏添加了 let 绑定。

v. 0.4.9: 修正了 README。

v. 0.4.8: 为 (Vec, LinkedList, VecDeque) 添加了 WriterT 转换器作为嵌套 monads。

v. 0.4.7: 为 (Vec, LinkedList, VecDeque) 添加了 ReaderT 转换器作为嵌套 monads。

v. 0.4.5 和 0.4.6: 清理了文档。

v. 0.4.4: 清理了旧的 intoiter 宏引用。抑制了实验性的 MonadPlus,它尚未准备好。

v. 0.4.3: 修正了 README 中的拼写错误。

v. 0.4.2: 添加了带有 quickcheck 测试的 MonadPlus。

v. 0.4.1: 使用克隆展示 String 返回的 console_io 示例。

v. 0.4.0

  • 将 writer 函数 censor_do 重命名为 censor。
  • 添加了 writer 函数 listen() 和 listens()。
  • 将 local_do() 重命名为 local()。
  • 移除了 intoiter 模块,因为它在功能上重复,且没有增加适用性,请使用模块 monad 的 mdo 宏代替。

v. 0.3.14: 添加了 writer 函数 censor_do

v. 0.3.13: 添加了 reader 函数 local_do

v. 0.3.12: 简化了 reader1 示例。

v. 0.3.11: 从 Writer 和 State monads 中抑制了形式 "&v <- ..."。

v. 0.3.10: 添加了 Reader 宏。它可以在可克隆环境中运行良好,例如 HashMap。State 宏已更新,使用非静态生命周期为boxed闭包。

v. 0.3.9: 添加了 (<-) 右侧的 pure

无运行时依赖