Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/witty-horses-hammer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@tsplus/runtime": patch
"@tsplus/stdlib": patch
---

Change Equals to work Structurally, change Either/Maybe to be plain objects
43 changes: 34 additions & 9 deletions packages/runtime/_src/Guard.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { Cons, Nil } from "@tsplus/stdlib/collections/List/definition"
import { Left, Right } from "@tsplus/stdlib/data/Either/definition"
import { None, Some } from "@tsplus/stdlib/data/Maybe/definition"

/**
* A Guard<A> is a type representing the ability to identify when a value is of type A at runtime
Expand Down Expand Up @@ -156,12 +154,24 @@ export function deriveOption<A extends Maybe<any>>(
: never
): Guard<A> {
return Guard((u): u is A => {
if (u instanceof None) {
if (typeof u !== "object" || u == null) {
return false
}

if (Equals.equals(Maybe.none, u)) {
return true
}
if (u instanceof Some) {
return element.is(u.value)

const keys = Object.keys(u)

if (keys.length !== 2) {
return false
}

if ("_tag" in u && "value" in u && u["_tag"] === "Some") {
return element.is(u["value"])
}

return false
})
}
Expand Down Expand Up @@ -270,12 +280,27 @@ export function deriveEither<A extends Either<any, any>>(
: never
): Guard<A> {
return Guard((u): u is A => {
if (u instanceof Left) {
return left.is(u.left)
if (typeof u !== "object" || u == null) {
return false
}
if (u instanceof Right) {
return right.is(u.right)

const keys = Object.keys(u)

if (keys.length !== 2) {
return false
}

if ("_tag" in u) {
switch (u["_tag"]) {
case "Left": {
return "left" in u && left.is(u["left"])
}
case "Right": {
return "right" in u && right.is(u["right"])
}
}
}

return false
})
}
Expand Down
56 changes: 19 additions & 37 deletions packages/stdlib/_src/data/Either/definition.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
/**
* adapted from https://github.com/gcanti/fp-ts
*/
const _leftHash = Hash.string("Either.Left")
const _rightHash = Hash.string("Either.Right")

/**
* @tsplus type Either
*/
Expand Down Expand Up @@ -39,33 +38,17 @@ export declare namespace Either {
/**
* @tsplus type Either.Left
*/
export class Left<E> implements Equals {
readonly _tag = "Left"

constructor(readonly left: E) {}

[Equals.sym](that: unknown): boolean {
return that instanceof Left && Equals.equals(this.left, that.left)
}
[Hash.sym](): number {
return Hash.combine(_leftHash, Hash.unknown(this.left))
}
export interface Left<E> {
readonly _tag: "Left"
readonly left: E
}

/**
* @tsplus type Either.Right
*/
export class Right<A> implements Equals {
readonly _tag = "Right"

constructor(readonly right: A) {}

[Equals.sym](that: unknown): boolean {
return that instanceof Right && Equals.equals(this.right, that.right)
}
[Hash.sym](): number {
return Hash.combine(_rightHash, Hash.unknown(this.right))
}
export interface Right<A> {
readonly _tag: "Right"
readonly right: A
}

/**
Expand Down Expand Up @@ -126,23 +109,15 @@ export function getRight<E, A>(self: Either<E, A>): Maybe<A> {
return self._tag === "Right" ? Maybe.some(self.right) : Maybe.none
}

/**
* Constructs a new `Either` holding a `Right` value. This usually represents a
* successful value due to the right bias of this structure.
*
* @tsplus static Either.Ops __call
*/
export function apply<A>(a: A): Either<never, A> {
return new Right(a)
}
/**
* Constructs a new `Either` holding a `Right` value. This usually represents a
* successful value due to the right bias of this structure.
*
* @tsplus static Either.Ops right
* @tsplus static Either.Ops __call
*/
export function right<A>(a: A): Either<never, A> {
return new Right(a)
return { _tag: "Right", right: a }
}

/**
Expand All @@ -152,7 +127,7 @@ export function right<A>(a: A): Either<never, A> {
* @tsplus static Either.Ops rightW
*/
export function rightW<A, E = never>(a: A): Either<E, A> {
return new Right(a)
return { _tag: "Right", right: a }
}

/**
Expand All @@ -162,7 +137,7 @@ export function rightW<A, E = never>(a: A): Either<E, A> {
* @tsplus static Either.Ops left
*/
export function left<E>(e: E): Either<E, never> {
return new Left(e)
return { _tag: "Left", left: e }
}

/**
Expand All @@ -172,7 +147,7 @@ export function left<E>(e: E): Either<E, never> {
* @tsplus static Either.Ops leftW
*/
export function leftW<E, A = never>(e: E): Either<E, A> {
return new Left(e)
return { _tag: "Left", left: e }
}

/**
Expand Down Expand Up @@ -222,3 +197,10 @@ export function widenA<A1>() {
<E, A>(self: Either<E, A>): Either<E, A | A1> => self
)
}

/**
* @tsplus operator Either ==
*/
export function equals<E, A, E1, B>(a: Either<E, A>, b: Either<E1, B>) {
return Equals.equals(a, b)
}
39 changes: 14 additions & 25 deletions packages/stdlib/_src/data/Maybe/definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,39 +27,21 @@ export const Maybe: MaybeOps = {
*/
export interface MaybeAspects {}

const _noneHash = Hash.string("Maybe.None")
const _someHash = Hash.string("Maybe.Some")

/**
* Definitions
*
* @tsplus type Maybe.None
*/
export class None implements Equals {
readonly _tag = "None";

[Equals.sym](that: unknown): boolean {
return that instanceof None
}
[Hash.sym](): number {
return _noneHash
}
export interface None {
readonly _tag: "None"
}

/**
* @tsplus type Maybe.Some
*/
export class Some<A> implements Equals {
readonly _tag = "Some"

constructor(readonly value: A) {}

[Equals.sym](that: unknown): boolean {
return that instanceof Some && Equals.equals(this.value, that.value)
}
[Hash.sym](): number {
return Hash.combine(_someHash, Hash.unknown(this.value))
}
export interface Some<A> {
readonly _tag: "Some"
readonly value: A
}

/**
Expand All @@ -82,7 +64,7 @@ export type ArrayOfMaybies<Ts extends Maybe<any>[]> = {
*
* @tsplus static Maybe.Ops none
*/
export const none: Maybe<never> = new None()
export const none: Maybe<never> = { _tag: "None" }

/**
* Constructs `None`.
Expand All @@ -99,7 +81,7 @@ export function empty<A = never>(): Maybe<A> {
* @tsplus static Maybe.Ops some
*/
export function some<A>(a: A): Maybe<A> {
return new Some(a)
return { _tag: "Some", value: a }
}

/**
Expand Down Expand Up @@ -131,3 +113,10 @@ export function isMaybe(u: unknown): u is Maybe<unknown> {
(u["_tag"] === "Some" || u["_tag"] === "None")
)
}

/**
* @tsplus operator Maybe ==
*/
export function equals<A, B>(a: Maybe<A>, b: Maybe<B>) {
return Equals.equals(a, b)
}
92 changes: 87 additions & 5 deletions packages/stdlib/_src/structure/Equals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,76 @@ export function sameValueZeroEqual(a: any, b: any) {
return a === b || (a !== a && b !== b)
}

const protoMap = new Map<any, (a: any, b: any) => boolean>([
[
Array.prototype,
(a: Array<any>, b: Array<any>) => a.length === b.length && a.every((v, i) => equals(v, b[i]))
],
[
Set.prototype,
(a: Set<any>, b: Set<any>) => {
if (a.size !== b.size) {
return false
}
for (const va of a.values()) {
let found = false
for (const vb of b.values()) {
if (equals(va, vb)) {
found = true
break
}
}
if (!found) {
return false
}
}
return true
}
],
[
Object.prototype,
(a: object, b: object) => {
const keysA = Object.keys(a).sort()
const keysB = Object.keys(b).sort()
if (keysA.length !== keysB.length) {
return false
}
if (!equals(keysA, keysB)) {
return false
}
for (const ka of keysA) {
const va = a[ka]
const vb = b[ka]
if (!equals(va, vb)) {
return false
}
}
return true
}
],
[
Map.prototype,
(a: Map<any, any>, b: Map<any, any>) => {
if (a.size !== b.size) {
return false
}
for (const [ka, va] of a.entries()) {
let found = false
for (const [kb, vb] of b.entries()) {
if (equals(ka, kb) && equals(va, vb)) {
found = true
break
}
}
if (!found) {
return false
}
}
return true
}
]
])

/**
* @tsplus static Equals.Ops equals
* @tsplus fluent Equals equals
Expand All @@ -41,12 +111,24 @@ export function equals(a: unknown, b: unknown): boolean {
if (a === b) {
return true
}
if (!sameValueZeroEqual(Hash.unknown(a), Hash.unknown(b))) {
return false
} else if (isEquals(a)) {
if (isEquals(a)) {
if (!isEquals(b)) {
return false
}
if (!sameValueZeroEqual(Hash.unknown(a), Hash.unknown(b))) {
return false
}
return a[Equals.sym](b)
} else if (isEquals(b)) {
return b[Equals.sym](a)
}
if (typeof a === "object" && typeof b === "object") {
const protoA = Object.getPrototypeOf(a)
const protoB = Object.getPrototypeOf(b)
if (protoA === protoB) {
const compare = protoMap.get(protoA)
if (compare) {
return compare(a, b)
}
}
}
return sameValueZeroEqual(a, b)
}
Loading