2 unstable releases
0.1.0 | Jan 28, 2025 |
---|---|
0.0.0 | Jan 10, 2025 |
#4 in #proptest
66 downloads per month
15KB
406 lines
Experimenting around Functorial pattern in Rust
This repository aims at exploring the followings in Rust:
- Affine Data/Control Functor hierarchy
- Multiplicative Functor hierarchy
- A port of
QualifiedDo
in Haskell to Rust macro to use the above uniformly- Support
ApplicativeDo
and MonadFail as well.
- Support
Showcase
use functo_rs::control::*;
use qualified_do::*;
let ans: Option<i64> = qdo! {Optioned {
i <- Some(5);
j <- Some(6);
let k = 7i64;
return i + j + k
}};
assert_eq!(ans, Some(18));
use functo_rs::control::*;
use qualified_do::*;
let ans: Option<i64> = qdo! {Optioned {
i <- Some(5);
j <- Some(6);
_k <- None::<i64>;
let k = 7i64;
return i + j + k
}};
assert_eq!(ans, None);
use functo_rs::nonlinear::*;
use qualified_do_macro::qdo;
let is = vec![1, 2, 3];
let js = vec![4, 5, 6];
let ans: Vec<i64> = qdo! {UndetVec {
i <- is.clone();
j <- js.clone();
let k = 100i64;
UndetVec::guard(i % 2 == 1);
return i + j + k
}};
assert_eq!(
ans,
is.into_iter()
.flat_map(|i| js.iter().cloned().flat_map(move |j| if i % 2 == 1 {
Some(i + j + 100)
} else {
None
}))
.collect::<Vec<_>>()
);
use functo_rs::data::*;
use qualified_do_macro::qdo;
let is = vec![1, 2, 3];
let js = vec![4, 5, 6];
let ans: Vec<i64> = qdo! {ZipVec {
i <- is.clone();
j <- js.clone();
let k = 100i64;
return i + j + k
}};
assert_eq!(
ans,
is.into_iter()
.zip(js)
.map(|(i, j)| i + j + 100)
.collect::<Vec<_>>()
);
use either::Either::*;
let a = vec![Some(1), None, Some(3)];
let b = vec![Left(4), Left(5), Right(6)];
let answer = {
let a = a.clone();
let b = b.clone();
qdo! { Iter {
Some(x) <- a;
Left(y) <- b.clone();
let z = 100i64;
return x + y + z
}}
.collect::<Vec<_>>()
};
let c = a
.into_iter()
.flatten()
.flat_map(|x| {
b.iter()
.cloned()
.flat_map(|x| x.left())
.map(move |y| x + y + 100)
})
.collect::<Vec<_>>();
assert_eq!(answer, c);
fn gen_expr() -> impl Strategy<Value = Expr> {
use qualified_do::qdo;
let leaf = any::<i32>().prop_map(Expr::Num).boxed();
leaf.prop_recursive(8, 256, 10, |inner| {
prop_oneof![
qdo! { BoxedProptest {
l <- inner.clone();
r <- inner.clone();
return Expr::Add(l.into(), r.into())
}},
qdo! { BoxedProptest {
l <- inner.clone();
r <- inner.clone();
return Expr::Mul(l.into(), r.into())
}}
]
})
}
Syntax
The qdo
macro has the following syntax:
qdo!{ NAMESPACE {
stmt1;
stmt2;
...
last_stmt [;] // Last ; is optional and changes the return value
}}
NAMESPACE
: module or type path to qualify control functions.stmt
s are do-statement, which should be one of the followings:-
let pat = expr;
: let-statement for (non-effectful) local binding. -
return a
: which wraps (pure) valuea
into effectful context;- NOTE: This DOES NOT do any early return. It is interpreted as just a syntactic
sugar around
NAMESPACE::pure(a)
.
- NOTE: This DOES NOT do any early return. It is interpreted as just a syntactic
sugar around
-
[~]pat <- expr
: effectful local binding. Corresponding roughly toNAMESPACE::and_then
~
is omittable; if~
is specified, it tries to desugar into simple closure on infalliable pattern.
-
guard expr
: guarding expression. Filters outexpr
is false. Desugared intoNAMESPACE::guard(expr)
. -
expr
: effectful expression, with its result discarded.
-
last_stmt
MUST either bereturn expr
orexpr
.- If there is no
;
atfterlast_stmt
, the final effectful value(s) will be returned. - If
last_stmt
is followed by;
, the values are discarded and replaced with()
inside effectful context.
- If there is no
If pat
is just a single identifier, it is desugared to a simple closure.
If the pat
is falliable pattern, it desugars into closure with match
-expression, with default value calls NAMESPACE::fail
to report pattern-match failure.
Further more, if the following conditions are met, qdo
-expression will be desugared in ApplicativeDo
-mode, which desugars in terms of NAMESPACE::fmap
, NAMESPACE::zip_with
, and possibly NAMESPACE::pure
:
- All
stmt
s butlast_stmt
contains NO varibale bound inqdo
-context, - All binding patterns are identifiers, not a compound pattern,
- No
guard
condition instmtN
contains identifiers defined inqdo
-context, and - The
last_stmt
is of formreturn expr
, whereexpr
can refer to any identifier in scope including those bound in qdo.
In ApplicativeDo
mode, all binding can be chained independently so they are chained with NAMESPACE::zip_with
and finally mapped with fmap
[^1].
[^1]: In Haskell, ApplicativeDo
uses fmap
, ap
, and join
. The reason we don't use join is that join
needs nested container, which has less availability in Rust than Haskell.
ApplicativeDo
utilises the independence of each binding, so in some cases you need less clone()
s.
Dependencies
~3.5MB
~66K SLoC