知識網是由各個知識區塊互相連結,只有知識網並不夠,我們還需要智慧,這樣才是完整的我。
2025/01/20
平常想要在 dune 專案裡做一些測試時,時常會遇到未使用函數或變數的錯誤導致無法編譯,這是 dune 預設的行為,雖然加個底線能夠解決,但還是太麻煩。偶然在 X 上看到有人討論到這個問題,並且提供了解決方法。
How to Make Warnings Non-Fatal
(env (dev (flags :standard -warn-error -27-32)))
flags 是將一些參數帶到 ocamlc
, ocamlopt
身上,所以這些代號的意義實際上要到 ocamlc
文件裡去找
2025/01/16
OCaml 的求值策略採用 call by value,也就是參數表達式在傳遞前會優先求出結果再傳遞,但是 OCaml 的 lazy
可以無視這個規則。
今天將一個表達式前面加上 lazy
,OCaml 是不會自動幫你執行的
let f s =
print_endline s;
s
;;
let lazy_value = lazy (f "lazy")
當你需要執行取結果時可以主動使用 Lazy.force lazy_value
。
另一個特性是 lazy 被執行一次後會幫你記憶結果,所以再次使用 Lazy.force lazy_value
不會再執行運算,而是直接返回第一次的結果,因為這樣,擺在 lazy
後的表達式不應該有副作用,否則會有非預期結果。
另外在官方文件中有提到 Lazy.force
本身並不是執行緒安全的,必須要有相應的措施。
2025/01/13
以往在專案中在做一些比較會使用 if else
, switch
等等判斷式,但兩種適用場景不同,if else
在較少並且多個不相關聯的資訊判定時使用,一旦條件多起來以該語法結構來說會變得不易讀,而 switch
則是針對單一資訊判定做出不同行為。
switch
看起來適用場景相當受限,雖然可以用一些方法作到 if else
效果,但不管是哪一種都有個問題是他們都不是表達式,也就是說在一個大型函數中某些值需要使用 if else
, switch
這種判斷會使函數變得混亂,因為你必須得用附值的方式來得到結果給下方的其他操作繼續使用,在沒有良好的分類及區隔上會變得難以閱讀。
function bigFn(){
// ...
let a = null;
if (condition){
a = "a";
} else if (condition){
a = "b";
} else {
a = "c";
}
let b = null;
switch (value){
case "a":
b = "a";
break;
case "b":
b = "b";
break;
case "c":
b = "c";
break;
default:
b = "d";
}
// ...
}
為了解決這種問題,剛好 lodash/ramda 有提供 cond
函數來做到這件事,雖然解決不是表達式的問題,卻衍生另一個型別推斷問題。
在不直接指定輸入輸出型別的情況下會依據你每個條件內的判斷式輸入得到輸入型別,輸出型別是由第二的函數輸出所提供。
// 自動推斷輸入是 number 輸出是 string
cond([
[(x: number) => x === 0, (x: number) => (x + 1).toString()],
])
大部分推斷並不是那麼理想,很多時候會使用 eq
函數,但是 eq
函數的型別長這樣
interface LodashEq {
(self: any, other: any): boolean;
(self: any): (other: any) => boolean;
}
這時你就會發現你的輸入會被自動推斷成 any
// 自動推斷輸入是 any 輸出是 string
cond([
[eq(0), (x: number) => (x + 1).toString()],
])
如果你很聰明的像 ramda 那樣,不如就兩者都必須是相同型別才能用 eq
判斷,但這種型別的覆寫跟原始 eq
函數所提供的功能就沒有完全相同,並且會影響到整個專案 eq
的使用,還有這不是只有 eq
有問題,其他函數也有可能有相同狀況。
如果你真的很勤勞的寫上輸入輸出的型別,那非常棒,但是在前端這個到處都是角括號的世界寫上型別似乎讓解讀上更困難了,如果是簡單的 string
, number
等等原始型別可能影響沒那麼大,但如果是物件呢?
cond<{ fieldA: string; fieldB: number }, string>([
[where({ fieldA: eq("a"), fieldB: eq(1) }), (x) => {/* ... */}]
])
因為以上幾種狀況,導致 cond
這個函數處在一個尷尬的狀態,用起來不像 if else
, switch
那麼符合直覺,自動型別推斷上也有諸多缺陷,那我們不如直接找一個功能較為完整的 Pattern Matching 函式庫來取代。
目前 JS TC39 雖然有關於 Pattern Matching 的提案,不過還在 stage 1,似乎沒什麼推進,等他出來我不知道我還有沒有寫 JS 🙃,所以目前有兩個主要選擇:
我能簡單的說這兩個函式庫所提供的功能幾乎是一模一樣,只差在風格不同,ts-pattern 是走鏈式風格,Effect-TS 是走組合風格,在比對分支上的撰寫模式個人認為幾乎無差異。
那主要的差異就在鏈式風格相對於組合風格不容易做 Tree Shacking,但我認為這也不成問題,ts-pattern 本身就只提供 Pattern Matching 的功能,做不做 Tree Shacking 好像差異並不那麼明顯,Effect-TS 本身會有一些函數會跟自己其他模組功能做一些連動,走組合式風格本來就更適合他們。
在我嘗試使用之前我似乎太低估他們了,兩者皆在型別上做到了未涵蓋的情境判定(exhaustive),原本我只是認為頂多未配對到就會報錯而已,但在巢狀結構的錯誤訊息上 ts-pattern 處裡得更好一些,更一目了然的缺少哪些情境,而 Effect-TS 的 exhaustive
目前似乎沒有做到很智慧的多層巢狀判定,必須從輸入的型別層面用 union type 的方式撰寫可能情境,有些麻煩。
2024/12/15
2024/11/19
2024/11/15
2024/10/29
2024/10/21
2024/10/20