Как я могу передать замыкание с обобщениями в функцию, не делая эту функцию обобщенной? - PullRequest
0 голосов
/ 27 сентября 2018

У меня есть функция, которая работает с перечислением для применения бинарных функций.Это для интерпретатора:

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;

Я хочу сделать:

let result = bin_op(add, &Scalar::I32(1), &Scalar::I32(2));

, но также заставить его работать для произвольных двоичных функций:

let result = bin_op(Scalar::concat, &Scalar::I32(1), &Scalar::I32(2));

Однако, Я не нашел способ передать замыкание, не сделав bin_op универсальный:

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)),
    }
}

Создание bin_op универсального не правильно;bin_op работает на Scalar, но внутренняя операция является общей.

Я изначально задавал этот вопрос на Reddit

1 Ответ

0 голосов
/ 28 сентября 2018

Существует два основных способа говорить о типах функций:

  • указатели: fn(A, B) -> C,
  • черты: Fn(A, B) -> C, FnMut(A, B) -> C, FnOnce(A, B) -> C.

В любом случае они характеризуются аргументами и типами результатов.

Итак, каковы аргументы и типы результатов apply?

Это зависит.

Из вашего примера мы видим, что это FnOnce(T, T) -> T для T в [i64, i32, ...].

Это не one тип, это много типов.Поэтому ему нужна не одна функция, а много функций;или, возможно, функциональный объект, реализующий FnOnce несколько раз.


Маршрут функционального объекта доступен только ночью и требует очень много шаблонов (для которых помогут макросы):

#![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);
}

Отпечатки I32(3).

...