在使用 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 模組底下一起重新導出方便直接取用。