Last active
October 11, 2020 07:19
-
-
Save KeenS/08da4caba8951600e1a57b93f537d08a to your computer and use it in GitHub Desktop.
With semi-group, FizzBuzz can be branchless, Try https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=08da4caba8951600e1a57b93f537d08a
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! 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