fizzyelt avatar

FizzyElt

知識網是由各個知識區塊互相連結,只有知識網並不夠,我們還需要智慧,這樣才是完整的我。

Effect-TS Typescript

在使用 Effect-TS 時我們應避免直接將容器類型的值直接作為資料傳遞或儲存,可能有部份 Effect 掛勾的相關模組比較明顯知道,但是 EitherOption 卻容易被忽略,相比於 fp-ts ,Effect-TS 的實作並不是像 fp-ts 一樣建立一般的物件,而是將有關於容器類型相關的標注及方法以Prototype 的形式綁定在物件之中。

因此當 Effect-TS 容器類型的值脫離 JS/TS 程式以資料的形式傳遞或儲存時,容器的相關資訊將會被抹除,進而導致讀取時也無法透過該模組本身的方法識別。

fp-ts Either

export const right = <A, E = never>(a: A): Either<E, A> => ({ _tag: 'Right', right: a })

Effect-TS Either

const RightProto = Object.assign(Object.create(CommonProto), {
  _tag: "Right",
  _op: "Right",
  [Equal.symbol]<L, R>(this: Either.Right<L, R>, that: unknown): boolean {
    return isEither(that) && isRight(that) && Equal.equals(this.right, that.right)
  },
  [Hash.symbol]<L, R>(this: Either.Right<L, R>) {
    return Hash.combine(Hash.hash(this._tag))(Hash.hash(this.right))
  },
  toJSON<L, R>(this: Either.Right<L, R>) {
    return {
      _id: "Either",
      _tag: this._tag,
      right: toJSON(this.right)
    }
  }
})

// 傳遞或儲存時只會保留 right 欄位
export const right = <R>(right: R): Either.Either<R> => {
  const a = Object.create(RightProto)
  a.right = right
  return a
}

解決辦法是不以容器類型作為資料格式傳遞儲存,換句話說就是在丟出去之前先將容器類型的值轉換成另一種合法且可視別的物件,只有在程式內進行容器相關轉換及操作。

如果真的喜歡且想要以容器資料形式傳遞,一個折衷方案是另外定義一組專門用於傳遞的容器型別及函式,那在送出或接收時做轉換即可,剛好原始碼中的 toJSON 有給予一個範本當作參考。

import * as Either from "effect/Either";
import { isObject } from "effect/Predicate";

export type DataLeft<L, R> = {
  readonly _id: "Either";
  readonly _tag: "Left";
  readonly left: L;
};

type DataRight<L, R> = {
  readonly _id: "Either";
  readonly _tag: "Right";
  readonly right: R;
};

type DataEither<A, E> = DataLeft<E, A> | DataRight<E, A>;

const isDataRight = <A, E>(
  dataEither: DataEither<A, E>,
): dataEither is DataRight<E, A> => dataEither._tag === "Right";

const isDataLeft = <A, E>(
  dataEither: DataEither<A, E>,
): dataEither is DataLeft<E, A> => dataEither._tag === "Left";

const isDataEither = (
  input: unknown,
): input is DataEither<unknown, unknown> => {
  return (
    isObject(input) &&
    "_id" in input &&
    "_tag" in input &&
    input._id === "Either" &&
    (input._tag === "Right" || input._tag === "Left")
  );
};

const toDataEither = <A, E>(either: Either.Either<A, E>): DataEither<A, E> =>
  either.toJSON() as DataEither<A, E>;

const fromDataEither = <A, E>(
  dataEither: DataEither<A, E>,
): Either.Either<A, E> => {
  if (isDataRight(dataEither)) {
    return Either.right(dataEither.right);
  }

  if (isDataLeft(dataEither)) {
    return Either.left(dataEither.left);
  }

  throw new Error("Invalid DataEither");
};

那這個小模組可以依附於 Either 模組底下一起重新導出方便直接取用。

dune OCaml

平常想要在 dune 專案裡做一些測試時,時常會遇到未使用函數或變數的錯誤導致無法編譯,這是 dune 預設的行為,雖然加個底線能夠解決,但還是太麻煩。偶然在 X 上看到有人討論到這個問題,並且提供了解決方法。

How to Make Warnings Non-Fatal

(env (dev (flags :standard -warn-error -27-32)))

flags 是將一些參數帶到 ocamlc, ocamlopt 身上,所以這些代號的意義實際上要到 ocamlc 文件裡去找

#8 OCaml lazy

2025/01/16

Functional Programming OCaml

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 本身並不是執行緒安全的,必須要有相應的措施。

Functional Programming Typescript
Dev Tool OCaml Dune
Dev Tool Typescript Javascript
#2 Svelte 5

2024/10/21

Javascript Svelte
#1 起點

2024/10/20