Holo314/Coeffect:使用Loom的ExtentLocals将部分Coeffect系统添加到Java


在 Java 中,通常有两种策略来管理方法所需的参数:

  1. 将值作为参数传递
  2. 将值作为类的字段

此外,为了确保线程安全,我们需要做更多的工作。对于第一种方法,问题不太明显,但对于后者,则更难处理。
确保安全的一种方法是使用 Java 的ThreadLocal,它可以确保参数不能通过不同的线程:
public class Example {
    private static ThreadLocal<String> TL = new ThreadLocal<>();

    public void foo() {
        System.out.println(Example.get());
    }

    public static void main(String[] args) {
        var x = new Example();
        CompletableFuture.runAsync(() -> {
           TL.set("^o^");
           Thread.sleep(3000);
// omitting exception handling
            x.foo();
        });
        CompletableFuture.runAsync(() -> {
            Thread.sleep(1000);
// omitting exception handling
            TL.set(
"o7");
            x.foo();
        });
    }
}

这将打印
o7
^o^

Project Loom 已经添加了ExtentLocal,它基本上就是一个结构化的ThreadLocal.

ThreadLocal和ExtentLocal的最有问题的是:我们失去了类型安全。对于ThreadLocal,你可以得到意想不到的空值,而对于ExtentLocal,你会得到一个异常。

任何对ThreadLocal或ExtentLocal的使用都应该附加一个null检查或绑定检查。此外,如果这两者中的一个不是私有的,就会产生耦合、安全问题和模糊的API。

另一方面,将依赖项作为参数发送还有其他问题,但我要谈的主要有两个:

  1. 参数膨胀
  2. 强制显式绑定

第一点很清楚,您可以获取具有 5/6 或更多参数的方法,这会在调用站点中创建长签名以及长签名。
第二点更容易忽略,但这里有一个例子:

public static void main(String[] args) {
    foo(666)
}
public static void foo(int x) {
    bar(x);
}
public static void bar(int x) {
   System.out.println(x);
}

请注意,foo接收一个参数只是为了将它传递给bar,它实际上并没有对它做任何事情。

解决方案
这个库提供的解决方案是创建一个(部分)Coeffect System
这个想法是使用ExtentLocal一个编译器插件来增加安全性和明确性。
Implementation note:无法创建此系统,ThreadLocal因为无法控制ThreadLocalremove.

在深入了解细节之前,让我们看看上面的例子是什么样子的:

public static void main(String[]args){
    Coeffect.with(666)
        .run(() -> foo());
}

@WithContext(Integer.class)
public static void foo(){
   bar();
}

@WithContext(Integer.class)
public static void bar(){
   System.out.println(Coeffect.get(Integer.class));
}

  • 我们在bar中使用Coeffect.get(Integer.class)来获取存储在全局Coeffect中的顶部整数。
  • 我们用@WithContext(Integer.class)对bar进行了注解,以表示我们在方法中使用Integer。
  • 我们在foo中调用了bar。
  • 我们用@WithContext(Integer.class)注释了foo,以表示我们正在使用一个需要Integer的方法。
  • 我们调用Coeffect.with(666)将666放在Integer.class的栈顶。
  • 我们在Coeffect.with(666)上调用run,在当前栈中运行一个Runnable。
  • 在Coeffect.with(666).run子句中,我们正在运行foo
  • 我们不需要在main方法中指定@WithContext(Integer.class),因为我们没有使用任何非绑定的依赖关系

请注意,所有这些点都是在编译时强制执行的,删除任何一个,@WithContext编译器都会对你大喊大叫。

Coeffect建立在ExtentLocal项目 Loom 附带的基础之上,以补充结构化并发,这意味着所有与线程一起工作并Coeffect一起使用的都应该使用结构化并发,任何非结构化并发的使用都可能导致误报。

名字Coeffect来自Effectsystem。Java确实有一个(部分)Effect System,checked exceptions,一个 effect 和一个 coeffect 的区别是比较细的,我希望以后给Coeffect类型系统和 Checked Exceptions 一样的力量

详细点击标题