如何通过一系列 lambdas 传递数据,其中参数类型是根据前一个 lambda 的返回类型推断的?

Posted

技术标签:

【中文标题】如何通过一系列 lambdas 传递数据,其中参数类型是根据前一个 lambda 的返回类型推断的?【英文标题】:How can I pass data through a chain of lambdas where the parameter type is inferred based on the return type of the previous lambda? 【发布时间】:2021-09-16 08:10:49 【问题描述】:

我正在尝试一个接一个地运行多个任务(lambda 函数)来转换一些数据。比如:

// Pass some initial data in the constructor or just generate some in the first lambda
// The execute is a callback with the final data because the task will be async
new MyTask(null).start((d) -> 
    return 1.25f;
).then((f) -> 
    return (int)(f*4);
).then((i) -> 
    return ""+i;
).then((s) -> 
    return "The answer is "+s;
).execute((r) -> 
    System.out.println(r);
);

我一直试图围绕完成此类事情所需的泛型进行思考,但我认为我对泛型的理解不够清楚,还不知道这是否或如何实现。

基本上我有一个这样的功能界面:

@FunctionalInterface
public interface Middleware<I, O> 
    O run(I data);

然后我有一个 MyTask 类,如下所示:

public class MyTask<T> 
    private List<Middleware<?, ?>> middlewares = new ArrayList<>();
    private T data;

    public MyTask(T data) 
        this.data = data;
    

    public <O> MyTask<T> start(Middleware<T, O> middleware) 
        middlewares.add(middleware);
        return this;
    
    public <I, O> MyTask<T> then(Middleware<I, O> middleware) 
        middlewares.add(middleware);
        return this;
    
    public void execute(Callback<?> callback) 
        // not sure what to do here, how to run the middlewares and callback with
        // the last return value
    

因此,每当我调用 then(...) 时,我都会将 lambdas 存储在 List 中,但我不知道如何实际运行列表并将数据放入,然后获取结果并传递它到下一个。我想我需要使用泛型,因为我需要推断类型,但我有点困惑,似乎无法让它工作。任何帮助将不胜感激。我什至不知道我目前所拥有的方向是否正确。

【问题讨论】:

是否有充分的理由不只是将其编写为单个 lambda 或方法? 【参考方案1】:

你在这里所做的基本上都是函数式组合。

为此,标准做法是使用Function 接口和Function.andThen method(和/或Function.compose,在当前函数之前应用新函数)。

Function<A, B> function1 = ...
Function<B, C> function2 = ...

Function<A, C> composed = function1.andThen(function2);

composedfunction1 应用于输入,然后获取结果并应用function2

即使您想尝试自己实现类似的机制,也可能值得研究Function 是如何实现这一点的,以给您一些想法。


实现我认为您正在尝试做的事情的关键是您不希望您的合成方法返回this:您实际上只需要MyTask 来捕获输出 em> 类型,因为组合和回调不需要关心初始输入的类型。

public class MyTask<OutT> 
  private final Supplier<OutT> supplier;

  private MyTask(Supplier<OutT> supplier) 
    this.supplier = supplier;
  

请注意,您实际上不需要将datamiddleware 分开:您始终使用data 作为middleware 的输入,因此您实际上只是提供了一个值:您所需要的一切要存储的是Supplier&lt;OutT&gt;

现在,让我们添加一些内容。请注意,我将构造函数设为私有:这是因为我想添加一个工厂方法,所以您不能只使用任何旧类型创建一个实例:

  public <InT> MyTask<InT> create(InT data) 
    return new MyTask<>(() -> data);
  

这将创建一个身份实例,将输入数据转换为自身。有点无聊,但让我们从那里开始吧。

通过身份转换,您不再需要start 方法,因为您已经“开始”了。但是让我们添加一个then

  public <NewOutT> MyTask<NewOutT> then(Middleware<OutT, NewOutT> middleware) 
    return new MyTask<>(() -> middleware.run(supplier.get()));

因此,您传递了一个middleware,它可以转换供应商提供的值,然后将它们组合在一起。

然后你可以有一个execute 方法,接受一个接受OutT 实例的回调:

  public void execute(Callback<OutT> callback) 
    callback.accept(supplier.get());
  

(为清楚起见,上面省略了:您还可以使用通配符使方法参数更灵活,例如Middleware&lt;A, B&gt; middleware -&gt; Middleware&lt;? super A, ? extends B&gt; middlewareCallback&lt;? super OutT&gt; callback)。

【讨论】:

我对您 then 方法中发生的事情有点困惑。您正在返回一个新的 MyTask 实例,但您只向构造函数传递了一个参数,但我们只定义了一个带有两个参数的构造函数:I data, Middleware 中间件 这对我来说是错误的。我不应该先传递一些数据,然后传递中间件吗? 另外,create 方法应该是静态的吗? 我想我明白了。我将“create”方法访问修饰符更改为静态,并将“then”方法中的返回调用更改为:return MyTask.create(middleware.run(supplier.get()));现在它按预期工作了。 @leisheng 很抱歉造成混乱,我在写这篇文章时改变了主意。您需要将供应商传递给构造函数,因此只需提供数据即可。

以上是关于如何通过一系列 lambdas 传递数据,其中参数类型是根据前一个 lambda 的返回类型推断的?的主要内容,如果未能解决你的问题,请参考以下文章

《Java 8 in Action》Chapter 2:通过行为参数化传递代码

C ++ 11 lambda函数-如何传递参数

如何传递和使用任意lambda函数作为参数[重复]

使用nodejs中的AWS Lambda函数上传音频文件

我应该通过 const 引用传递一个 lambda。

python tkinter, 通过lambda表达式传递参数到按钮的点击事件函数