Holo314/Coeffect:使用Loom的ExtentLocals將部分Coeffect系統新增到Java

banq發表於2022-08-11

在 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 一樣的力量

詳細點選標題

相關文章