I have a function that works with a enum to apply binary functions. This is for an interpreter:
use std::ops::*;
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum Scalar {
I64(i64),
I32(i32),
//many many others
}
pub trait TMath: Add + Mul + Sized {} //mark numerical types
impl<T: Add + Mul> TMath for T {}
fn add<T: TMath>(x: T, y: T) -> <T as Add>::Output {
x + y
}
pub type NatBinExpr<T: TMath> = Fn(&T, &T) -> T;
I want to do:
let result = bin_op(add, &Scalar::I32(1), &Scalar::I32(2));
but also to make it work for arbitrary binary functions:
let result = bin_op(Scalar::concat, &Scalar::I32(1), &Scalar::I32(2));
However, I haven't found a way to pass the closure without making bin_op
generic:
fn bin_op(apply: &NatBinExpr???, x: &Scalar, y: &Scalar) -> Scalar {
match (x, y) {
(Scalar::I64(a), Scalar::I64(b)) => Scalar::I64(apply(a, b)),
(Scalar::I32(a), Scalar::I32(b)) => Scalar::I32(apply(a, b)),
}
}
Making bin_op
generic is not right; bin_op
operates on Scalar
, but the internal operation is generic.
I originally asked this question on Reddit
There are essentially two distinct ways to talk about function types:
fn(A, B) -> C
,Fn(A, B) -> C
, FnMut(A, B) -> C
, FnOnce(A, B) -> C
.In either case, they are characterized by the arguments and result types.
So, what are the arguments and result types of apply
?
It depends.
From your example, we can see that it is FnOnce(T, T) -> T
for T
in [i64, i32, ...]
.
This is not one type, this is many types. Therefore it needs not a single function but many functions; or perhaps a function object implementing FnOnce
multiple times.
The function object route is only available on nightly, and requires an awful lot of boilerplate (for which macros would help):
#![feature(fn_traits)]
#![feature(unboxed_closures)]
use std::ops::*;
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum Scalar {
I64(i64),
I32(i32),
//many many others
}
pub trait TMath: Add + Mul + Sized {} //mark numerical types
impl<T: Add + Mul> TMath for T {}
struct Adder;
impl FnOnce<(i64, i64)> for Adder {
type Output = i64;
extern "rust-call" fn call_once(self, args: (i64, i64)) -> i64 {
args.0 + args.1
}
}
impl FnMut<(i64, i64)> for Adder {
extern "rust-call" fn call_mut(&mut self, args: (i64, i64)) -> i64 {
args.0 + args.1
}
}
impl Fn<(i64, i64)> for Adder {
extern "rust-call" fn call(&self, args: (i64, i64)) -> i64 {
args.0 + args.1
}
}
impl FnOnce<(i32, i32)> for Adder {
type Output = i32;
extern "rust-call" fn call_once(self, args: (i32, i32)) -> i32 {
args.0 + args.1
}
}
impl FnMut<(i32, i32)> for Adder {
extern "rust-call" fn call_mut(&mut self, args: (i32, i32)) -> i32 {
args.0 + args.1
}
}
impl Fn<(i32, i32)> for Adder {
extern "rust-call" fn call(&self, args: (i32, i32)) -> i32 {
args.0 + args.1
}
}
fn bin_op<F>(apply: &F, x: Scalar, y: Scalar) -> Scalar
where
F: Fn(i64, i64) -> i64,
F: Fn(i32, i32) -> i32,
{
match (x, y) {
(Scalar::I64(a), Scalar::I64(b))
=> Scalar::I64((apply as &Fn(i64, i64) -> i64)(a, b)),
(Scalar::I32(a), Scalar::I32(b))
=> Scalar::I32((apply as &Fn(i32, i32) -> i32)(a, b)),
_ => unreachable!(),
}
}
fn main() {
let result = bin_op(&Adder, Scalar::I32(1), Scalar::I32(2));
println!("{:?}", result);
}
Prints I32(3)
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With