Schema to Equivalence
Schema-derived equivalence works in Effeck exactly as in Effect. It compares decoded values using the structure described by a schema, making it useful for domain comparisons and round-trip checks. It does not decode or validate the compared values. Plain builders follow the trusted call rules.
Deriving equality from schemas
schema Shipment {
reference: string
stops: ReadonlyArray<string>
}
function sameShipment(left: unknown, right: unknown) {
const a = Schema.decodeUnknownEffect(Shipment)(left)
const b = Schema.decodeUnknownEffect(Shipment)(right)
return try((schema, a, b) => Schema.toEquivalence(schema)(a, b), Shipment, a, b)
}toEquivalence derives a comparison for the schema's decoded type. Arrays and nested structs are compared recursively. For a nonempty struct, only the fields described by the schema participate; unmodeled fields do not become extra equality conditions. Decode untrusted inputs before comparing them.
Handling broad schemas and custom declarations
function sameUnknown(left: unknown, right: unknown) {
return try((left, right) => Schema.toEquivalence(Schema.Unknown)(left, right), left, right)
}
class ShipmentRef {
constructor(readonly id: string, readonly label: string) {}
}
const ShipmentRefSchema = Schema.instanceOf(ShipmentRef, {
toEquivalence: () => (left, right) => left.id === right.id
})
function sameRef() {
const morning = new ShipmentRef("ref-61", "morning")
const afternoon = new ShipmentRef("ref-61", "afternoon")
return try((schema, a, b) => Schema.toEquivalence(schema)(a, b), ShipmentRefSchema, morning, afternoon) // true
}Any, Unknown, ObjectKeyword, and the empty struct use Equal.equals, including its structural handling of arrays and objects. Opaque declarations also default to Equal.equals; add a toEquivalence annotation when domain identity follows a particular field. Parameterized declarations receive the derived comparator for each type parameter.
Overriding generated comparison behavior
const ShipmentIdentity = Shipment.pipe(Schema.overrideToEquivalence(
() => (left, right) => left.reference === right.reference
))
function sameIdentity(left: typeof Shipment.Type, right: typeof Shipment.Type) {
return try((schema, left, right) => Schema.toEquivalence(schema)(left, right), ShipmentIdentity, left, right)
}overrideToEquivalence replaces the comparison derived at that schema node. This comparison intentionally ignores stops. Keep custom comparisons reflexive, symmetric, and transitive; otherwise consumers cannot treat them as an equivalence relation. A comparator callback is synchronous and should not call services or perform effects.