Skip to content

Instantly share code, notes, and snippets.

@KeenS
Last active October 11, 2020 07:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save KeenS/08da4caba8951600e1a57b93f537d08a to your computer and use it in GitHub Desktop.
Save KeenS/08da4caba8951600e1a57b93f537d08a to your computer and use it in GitHub Desktop.
//! rust port of this
//! https://twitter.com/gakuzzzz/status/1314499876969881602
//! requires nightly
// just for ease
#![feature(bool_to_option)]
// mandatory
#![feature(type_alias_impl_trait)]
// code
fn mod_then(
m: i32,
then: impl Into<String>,
) -> Judge<i32, Option<String>, impl Fn(i32) -> Option<String>> {
let then = then.into();
Judge::new(move |i| (i % m == 0).then(|| then.clone()))
}
fn main() {
let fizz = mod_then(3, "fizz");
let buzz = mod_then(5, "buzz");
let fizz_buzz = fizz + buzz;
let fizz_buzz_num = fizz_buzz.map_with(|i, opt| opt.unwrap_or_else(|| i.to_string()));
for i in 0..20 {
println!("{}", fizz_buzz_num.run(i));
}
}
// preparation
use std::marker::PhantomData;
/// Semi-group
// Same signature as `Add` but redefining here
// to workaround orphan rule around `Option` implementation
trait SemiGroup<Other = Self> {
type Output;
fn append(self, other: Other) -> Self::Output;
}
// String is a semi-group
// same as `Add`
impl SemiGroup for String {
type Output = Self;
fn append(self, other: Self) -> Self::Output {
self + &other
}
}
// Option<T> is a semi-group when T is a semi-group
impl<T: SemiGroup<Output = T>> SemiGroup for Option<T> {
type Output = Self;
fn append(self, other: Self) -> Self::Output {
match (self, other) {
(None, x) | (x, None) => x,
(Some(x), Some(y)) => Some(x.append(y)),
}
}
}
// Same as `impl Fn(A) -> T` but it's a concrete type
struct Judge<A, T, F> {
f: F,
_marker: PhantomData<fn(A) -> T>,
}
impl<A, T, F> Judge<A, T, F>
where
F: Fn(A) -> T,
{
fn new(f: F) -> Self {
Self {
f,
_marker: PhantomData,
}
}
fn run(&self, a: A) -> T {
(self.f)(a)
}
}
impl<A, T, F> Judge<A, T, F>
where
F: Fn(A) -> T,
A: Clone,
{
fn map_with<U>(self, f: impl Fn(A, T) -> U) -> Judge<A, U, impl Fn(A) -> U> {
let Self { f: judge, .. } = self;
Judge::new(move |a: A| f(a.clone(), judge(a)))
}
}
// `Judge<A, T, F>` is a semi-group when `T` is a semi-group
// This is same as Haskell's `SemiGroup t => SemiGroup a -> t`
impl<A, T, F1, F2> SemiGroup<Judge<A, T, F2>> for Judge<A, T, F1>
where
F1: Fn(A) -> T,
F2: Fn(A) -> T,
A: Clone,
T: SemiGroup<Output = T>,
{
type Output = Judge<A, T, impl Fn(A) -> T>;
fn append(self, other: Judge<A, T, F2>) -> Self::Output {
let Judge { f: f1, .. } = self;
let Judge { f: f2, .. } = other;
Judge::new(move |a: A| f1(a.clone()).append(f2(a)))
}
}
// just for ease
use std::ops;
impl<A, T, F1, F2> ops::Add<Judge<A, T, F2>> for Judge<A, T, F1>
where
F1: Fn(A) -> T,
F2: Fn(A) -> T,
A: Clone,
T: SemiGroup<Output = T>,
{
type Output = Judge<A, T, impl Fn(A) -> T>;
fn add(self, other: Judge<A, T, F2>) -> Self::Output {
self.append(other)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment