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