use frunk_core::hlist::*;
#[derive(PartialEq, Debug, Eq, Clone, Copy, PartialOrd, Ord, Hash)]
pub enum Coproduct<H, T> {
Inl(H),
Inr(T),
}
#[derive(PartialEq, Debug, Eq, Clone, Copy, PartialOrd, Ord, Hash)]
pub enum CNil {}
#[macro_export]
macro_rules! Coprod {
() => { $crate::coproduct::CNil };
($single: ty) => {
$crate::coproduct::Coproduct<$single, $crate::coproduct::CNil>
};
($first: ty, $( $repeated: ty ), +) => {
$crate::coproduct::Coproduct<$first, Coprod!($($repeated), *)>
};
($single: ty,) => {
Coprod![$single]
};
($first: ty, $( $repeated: ty, ) +) => {
Coprod![$first, $($repeated),*]
};
}
pub trait CoprodInjector<InjectType, Index> {
fn inject(to_insert: InjectType) -> Self;
}
impl<I, Tail> CoprodInjector<I, Here> for Coproduct<I, Tail> {
fn inject(to_insert: I) -> Self {
Coproduct::Inl(to_insert)
}
}
impl<Head, I, Tail, TailIndex> CoprodInjector<I, There<TailIndex>> for Coproduct<Head, Tail>
where Tail: CoprodInjector<I, TailIndex>
{
fn inject(to_insert: I) -> Self {
let tail_inserted = <Tail as CoprodInjector<I, TailIndex>>::inject(to_insert);
Coproduct::Inr(tail_inserted)
}
}
pub trait CoproductSelector<S, I> {
fn get(&self) -> Option<&S>;
}
impl<Head, Tail> CoproductSelector<Head, Here> for Coproduct<Head, Tail> {
fn get(&self) -> Option<&Head> {
use self::Coproduct::*;
match *self {
Inl(ref thing) => Some(thing),
_ => None, }
}
}
impl<Head, FromTail, Tail, TailIndex> CoproductSelector<FromTail, There<TailIndex>>
for Coproduct<Head, Tail>
where Tail: CoproductSelector<FromTail, TailIndex>
{
fn get(&self) -> Option<&FromTail> {
use self::Coproduct::*;
match *self {
Inr(ref rest) => rest.get(),
_ => None, }
}
}
pub trait CoproductTaker<S, I> {
fn take(self) -> Option<S>;
}
impl<Head, Tail> CoproductTaker<Head, Here> for Coproduct<Head, Tail> {
fn take(self) -> Option<Head> {
use self::Coproduct::*;
match self {
Inl(thing) => Some(thing),
_ => None, }
}
}
impl<Head, FromTail, Tail, TailIndex> CoproductTaker<FromTail, There<TailIndex>>
for Coproduct<Head, Tail>
where Tail: CoproductTaker<FromTail, TailIndex>
{
fn take(self) -> Option<FromTail> {
use self::Coproduct::*;
match self {
Inr(rest) => rest.take(),
_ => None, }
}
}
pub trait CoproductFoldable<Folder, Output> {
fn fold(self, f: Folder) -> Output;
}
impl<F, R, FTail, CH, CTail> CoproductFoldable<HCons<F, FTail>, R> for Coproduct<CH, CTail>
where F: FnOnce(CH) -> R,
CTail: CoproductFoldable<FTail, R>
{
fn fold(self, f: HCons<F, FTail>) -> R {
use self::Coproduct::*;
let f_head = f.head;
let f_tail = f.tail;
match self {
Inl(r) => (f_head)(r),
Inr(rest) => rest.fold(f_tail),
}
}
}
impl<'a, F, R, FTail, CH, CTail> CoproductFoldable<HCons<F, FTail>, R> for &'a Coproduct<CH, CTail>
where F: FnOnce(&'a CH) -> R,
&'a CTail: CoproductFoldable<FTail, R>
{
fn fold(self, f: HCons<F, FTail>) -> R {
use self::Coproduct::*;
let f_head = f.head;
let f_tail = f.tail;
match *self {
Inl(ref r) => (f_head)(r),
Inr(ref rest) => <&'a CTail as CoproductFoldable<FTail, R>>::fold(rest, f_tail),
}
}
}
#[doc(hidden)]
impl<F, R> CoproductFoldable<F, R> for CNil {
fn fold(self, _: F) -> R {
unreachable!()
}
}
#[doc(hidden)]
impl<'a, F, R> CoproductFoldable<F, R> for &'a CNil {
fn fold(self, _: F) -> R {
unreachable!()
}
}
impl<CH, CTail> AsRef<Coproduct<CH, CTail>> for Coproduct<CH, CTail> {
fn as_ref(&self) -> &Coproduct<CH, CTail> {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use super::Coproduct::*;
#[test]
fn test_coproduct_inject() {
type I32StrBool = Coprod!(i32, &'static str, bool);
let co1 = I32StrBool::inject(3);
assert_eq!(co1, Inl(3));
let get_from_1a: Option<&i32> = co1.get();
let get_from_1b: Option<&bool> = co1.get();
assert_eq!(get_from_1a, Some(&3));
assert_eq!(get_from_1b, None);
let co2 = I32StrBool::inject(false);
assert_eq!(co2, Inr(Inr(Inl(false))));
let get_from_2a: Option<&i32> = co2.get();
let get_from_2b: Option<&bool> = co2.get();
assert_eq!(get_from_2a, None);
assert_eq!(get_from_2b, Some(&false));
}
#[test]
fn test_coproduct_fold_consuming() {
type I32F32StrBool = Coprod!(i32, f32, bool);
let co1 = I32F32StrBool::inject(3);
let folded = co1.fold(hlist![|i| format!("int {}", i),
|f| format!("float {}", f),
|b| (if b { "t" } else { "f" }).to_string()]);
assert_eq!(folded, "int 3".to_string());
}
#[test]
fn test_coproduct_fold_non_consuming() {
type I32StrBool = Coprod!(i32, f32, bool);
let co1 = I32StrBool::inject(3);
let co2 = I32StrBool::inject(true);
let co3 = I32StrBool::inject(42f32);
assert_eq!(co1.as_ref()
.fold(hlist![|&i| format!("int {}", i),
|&f| format!("float {}", f),
|&b| (if b { "t" } else { "f" }).to_string()]),
"int 3".to_string());
assert_eq!(co2.as_ref()
.fold(hlist![|&i| format!("int {}", i),
|&f| format!("float {}", f),
|&b| (if b { "t" } else { "f" }).to_string()]),
"t".to_string());
assert_eq!(co3.as_ref()
.fold(hlist![|&i| format!("int {}", i),
|&f| format!("float {}", f),
|&b| (if b { "t" } else { "f" }).to_string()]),
"float 42".to_string());
}
}