Skip to content

Commit

Permalink
Experiment: referential equality
Browse files Browse the repository at this point in the history
  • Loading branch information
bloodyowl committed Oct 18, 2024
1 parent 26331a5 commit 48a327d
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 34 deletions.
79 changes: 62 additions & 17 deletions benchmark/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,84 @@ Tested on a M1 Pro MacBook Pro.

## Option

### v8

```
fp-ts Option none x 134,019,171 ops/sec ±1.58% (94 runs sampled)
fp-ts Option some x 90,043,117 ops/sec ±3.50% (97 runs sampled)
fp-ts Option some chain x 2,797,176 ops/sec ±1.35% (97 runs sampled)
effect Option none x 28,233,747 ops/sec ±0.52% (97 runs sampled)
effect Option some x 24,837,619 ops/sec ±1.55% (90 runs sampled)
effect Option some chain x 23,184,057 ops/sec ±3.08% (94 runs sampled)
Boxed Option none x 610,271,309 ops/sec ±1.22% (98 runs sampled)
Boxed Option some x 27,055,907 ops/sec ±0.09% (96 runs sampled)
Boxed Option some flatMap x 26,768,922 ops/sec ±2.66% (88 runs sampled)
```

### jscore

```
fp-ts Option none x 136,289,948 ops/sec ±0.35% (99 runs sampled)
fp-ts Option some x 92,748,146 ops/sec ±0.36% (95 runs sampled)
fp-ts Option some chain x 2,831,502 ops/sec ±0.21% (98 runs sampled)
effect Option none x 28,316,950 ops/sec ±0.68% (90 runs sampled)
effect Option some x 24,920,467 ops/sec ±0.78% (92 runs sampled)
effect Option some chain x 24,060,988 ops/sec ±0.31% (99 runs sampled)
Boxed Option none x 613,986,228 ops/sec ±0.20% (99 runs sampled)
Boxed Option some x 445,172,964 ops/sec ±0.50% (100 runs sampled)
Boxed Option some flatMap x 447,362,963 ops/sec ±0.53% (98 runs sampled)
fp-ts Option none x 26,370,592 ops/sec ±3.01% (80 runs sampled)
fp-ts Option some x 22,306,443 ops/sec ±3.35% (80 runs sampled)
fp-ts Option some chain x 6,395,953 ops/sec ±0.72% (94 runs sampled)
effect Option none x 34,944,770 ops/sec ±3.60% (80 runs sampled)
effect Option some x 24,062,381 ops/sec ±3.20% (78 runs sampled)
effect Option some chain x 21,272,877 ops/sec ±3.03% (77 runs sampled)
Boxed Option none x 360,292,187 ops/sec ±52.07% (26 runs sampled)
Boxed Option some x 42,614,750 ops/sec ±2.65% (81 runs sampled)
Boxed Option some flatMap x 39,815,444 ops/sec ±2.15% (85 runs sampled)
```

## Result

### v8

```
fp-ts Result x 116,486,882 ops/sec ±1.58% (96 runs sampled)
effect Result x 26,664,690 ops/sec ±1.45% (101 runs sampled)
Boxed Result x 28,373,628 ops/sec ±2.47% (99 runs sampled)
```

### jscore

```
fp-ts Result x 116,379,320 ops/sec ±0.48% (95 runs sampled)
effect Result x 26,538,884 ops/sec ±0.43% (96 runs sampled)
Boxed Result x 422,758,104 ops/sec ±0.98% (94 runs sampled)
fp-ts Result x 24,241,558 ops/sec ±4.98% (75 runs sampled)
effect Result x 26,450,388 ops/sec ±2.68% (79 runs sampled)
Boxed Result x 39,799,836 ops/sec ±2.69% (81 runs sampled)
```

## Future

Careful on the interpretation of the following, as Future doesn't use microtasks and calls its listeners synchronously.

### v8

```
Promise x 13,345,062 ops/sec ±1.63% (86 runs sampled)
Future x 163,971 ops/sec ±50.30% (30 runs sampled)
```

### jscore

```
Promise x 13,399,997 ops/sec ±0.57% (81 runs sampled)
Future x 210,785 ops/sec ±7.41% (32 runs sampled)
Promise x 6,744,022 ops/sec ±3.20% (83 runs sampled)
Future x 162,704 ops/sec ±28.63% (24 runs sampled)
```

## Future with Result

### v8

```
fp-ts TaskEither x 524,318 ops/sec ±1.67% (90 runs sampled)
effect Effect x 193,219 ops/sec ±0.69% (90 runs sampled)
Future x 315,497 ops/sec ±10.37% (71 runs sampled)
```

### jscore

```
fp-ts TaskEither x 538,403 ops/sec ±0.29% (86 runs sampled)
effect Effect x 189,220 ops/sec ±0.94% (87 runs sampled)
Future x 575,626 ops/sec ±0.30% (90 runs sampled)
fp-ts TaskEither x 724,358 ops/sec ±7.41% (83 runs sampled)
effect Effect x 470,137 ops/sec ±0.86% (83 runs sampled)
Future x 707,572 ops/sec ±2.97% (72 runs sampled)
```
17 changes: 13 additions & 4 deletions src/AsyncData.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { keys, values } from "./Dict";
import { Option, Result } from "./OptionResult";
import { createStore } from "./referenceStore";
import { BOXED_TYPE } from "./symbols";
import { JsonAsyncData, LooseRecord } from "./types";
import { zip } from "./ZipUnzip";

const AsyncDataStore = createStore();

class __AsyncData<A> {
static P = {
Done: <const A>(value: A) => ({ tag: "Done", value }) as const,
Expand All @@ -14,10 +17,16 @@ class __AsyncData<A> {
* Create an AsyncData.Done value
*/
static Done = <A = never>(value: A): AsyncData<A> => {
const asyncData = Object.create(ASYNC_DATA_PROTO) as Done<A>;
asyncData.tag = "Done";
asyncData.value = value;
return asyncData;
const existing = AsyncDataStore.get(value);
if (existing === undefined) {
const asyncData = Object.create(ASYNC_DATA_PROTO) as Done<A>;
asyncData.tag = "Done";
asyncData.value = value;
AsyncDataStore.set(value, asyncData);
return asyncData;
} else {
return existing as Done<A>;
}
};

/**
Expand Down
48 changes: 36 additions & 12 deletions src/OptionResult.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
import { keys, values } from "./Dict";
import { createStore } from "./referenceStore";
import { BOXED_TYPE } from "./symbols";
import { JsonOption, JsonResult, LooseRecord } from "./types";
import { zip } from "./ZipUnzip";

const OptionStore = createStore();

class __Option<A> {
static P = {
Some: <const A>(value: A) => ({ tag: "Some", value }) as const,
None: { tag: "None" } as const,
};

static Some = <A = never>(value: A): Option<A> => {
const option = Object.create(OPTION_PROTO) as Some<A>;
option.tag = "Some";
option.value = value;
return option;
const existing = OptionStore.get(value);
if (existing === undefined) {
const option = Object.create(OPTION_PROTO) as Some<A>;
option.tag = "Some";
option.value = value;
OptionStore.set(value, option);
return option;
} else {
return existing as Some<A>;
}
};

static None = <A = never>(): Option<A> => NONE as None<A>;
Expand Down Expand Up @@ -284,24 +293,39 @@ interface None<A> extends __Option<A> {
export const Option = __Option;
export type Option<A> = Some<A> | None<A>;

const OkStore = createStore();
const ErrorStore = createStore();

class __Result<A, E> {
static P = {
Ok: <const A>(value: A) => ({ tag: "Ok", value }) as const,
Error: <const E>(error: E) => ({ tag: "Error", error }) as const,
};

static Ok = <A = never, E = never>(value: A): Result<A, E> => {
const result = Object.create(RESULT_PROTO) as Ok<A, E>;
result.tag = "Ok";
result.value = value;
return result;
const existing = OkStore.get(value);
if (existing === undefined) {
const result = Object.create(RESULT_PROTO) as Ok<A, E>;
result.tag = "Ok";
result.value = value;
OkStore.set(value, result);
return result;
} else {
return existing as Ok<A, E>;
}
};

static Error = <A = never, E = never>(error: E): Result<A, E> => {
const result = Object.create(RESULT_PROTO) as Error<A, E>;
result.tag = "Error";
result.error = error;
return result;
const existing = ErrorStore.get(error);
if (existing === undefined) {
const result = Object.create(RESULT_PROTO) as Error<A, E>;
result.tag = "Error";
result.error = error;
ErrorStore.set(error, result);
return result;
} else {
return existing as Error<A, E>;
}
};

static isResult = (value: unknown): value is Result<unknown, unknown> =>
Expand Down
20 changes: 20 additions & 0 deletions src/referenceStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export const createStore = () => {
const store = new Map<unknown, WeakRef<object>>();

const registry = new FinalizationRegistry((value) => {
store.delete(value);
});

return {
set: (key: unknown, value: object) => {
store.set(key, new WeakRef(value));
registry.register(value, key);
},
get: (key: unknown): unknown => {
const value = store.get(key);
if (value !== undefined) {
return value.deref();
}
},
};
};
7 changes: 7 additions & 0 deletions test/AsyncData.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,3 +389,10 @@ test("AsyncData JSON serialization", () => {
}),
).toEqual(data);
});

test("AsyncData equality", () => {
expect(AsyncData.Done(1)).toBe(AsyncData.Done(1));
expect(AsyncData.Done({})).not.toBe(AsyncData.Done({}));
const x = {};
expect(AsyncData.Done(x)).toBe(AsyncData.Done(x));
});
7 changes: 7 additions & 0 deletions test/Option.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,10 @@ test("Option JSON serialization", () => {
}),
).toEqual(option);
});

test("Option equality", () => {
expect(Option.Some(1)).toBe(Option.Some(1));
expect(Option.Some({})).not.toBe(Option.Some({}));
const x = {};
expect(Option.Some(x)).toBe(Option.Some(x));
});
10 changes: 10 additions & 0 deletions test/Result.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,3 +269,13 @@ test("Result JSON serialization", async () => {
}),
).toEqual(resultError);
});

test("Result equality", () => {
expect(Result.Ok(1)).toBe(Result.Ok(1));
expect(Result.Error(1)).toBe(Result.Error(1));
expect(Result.Ok({})).not.toBe(Result.Ok({}));
expect(Result.Error({})).not.toBe(Result.Error({}));
const x = {};
expect(Result.Ok(x)).toBe(Result.Ok(x));
expect(Result.Error(x)).toBe(Result.Error(x));
});
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"compilerOptions": {
"module": "ES2015",
"target": "ES2015",
"lib": ["DOM", "DOM.Iterable", "ES2017"],
"lib": ["DOM", "DOM.Iterable", "ES2021"],
"moduleResolution": "Node",
"outDir": "dist/",

Expand Down

0 comments on commit 48a327d

Please sign in to comment.