From d2b9118c46cdd58a214c6bffb93d747f6d0a342f Mon Sep 17 00:00:00 2001 From: Kasper Isager Date: Wed, 10 Feb 2021 15:22:54 +0100 Subject: [PATCH 1/2] Initial draft of `Selective` --- packages/alfa-selective/package.json | 37 +++++++ packages/alfa-selective/src/index.ts | 1 + packages/alfa-selective/src/selective.ts | 103 ++++++++++++++++++ .../alfa-selective/test/selective.spec.ts | 48 ++++++++ packages/alfa-selective/tsconfig.json | 34 ++++++ packages/tsconfig.json | 1 + 6 files changed, 224 insertions(+) create mode 100644 packages/alfa-selective/package.json create mode 100644 packages/alfa-selective/src/index.ts create mode 100644 packages/alfa-selective/src/selective.ts create mode 100644 packages/alfa-selective/test/selective.spec.ts create mode 100644 packages/alfa-selective/tsconfig.json diff --git a/packages/alfa-selective/package.json b/packages/alfa-selective/package.json new file mode 100644 index 0000000000..f43b9f6fcf --- /dev/null +++ b/packages/alfa-selective/package.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json.schemastore.org/package", + "name": "@siteimprove/alfa-selective", + "homepage": "https://siteimprove.com", + "version": "0.10.0", + "license": "MIT", + "description": "An implementation of a selective functor for modelling conditional function application", + "repository": { + "type": "git", + "url": "https://github.com/siteimprove/alfa.git", + "directory": "packages/alfa-selective" + }, + "bugs": "https://github.com/siteimprove/alfa/issues", + "main": "src/index.js", + "types": "src/index.d.ts", + "files": [ + "src/**/*.js", + "src/**/*.d.ts" + ], + "dependencies": { + "@siteimprove/alfa-either": "^0.10.0", + "@siteimprove/alfa-equatable": "^0.10.0", + "@siteimprove/alfa-functor": "^0.10.0", + "@siteimprove/alfa-hash": "^0.10.0", + "@siteimprove/alfa-json": "^0.10.0", + "@siteimprove/alfa-mapper": "^0.10.0", + "@siteimprove/alfa-predicate": "^0.10.0", + "@siteimprove/alfa-refinement": "^0.10.0" + }, + "devDependencies": { + "@siteimprove/alfa-test": "^0.10.0" + }, + "publishConfig": { + "access": "public", + "registry": "https://npm.pkg.github.com/" + } +} diff --git a/packages/alfa-selective/src/index.ts b/packages/alfa-selective/src/index.ts new file mode 100644 index 0000000000..0d92aa5c85 --- /dev/null +++ b/packages/alfa-selective/src/index.ts @@ -0,0 +1 @@ +export * from "./selective"; diff --git a/packages/alfa-selective/src/selective.ts b/packages/alfa-selective/src/selective.ts new file mode 100644 index 0000000000..5e1fc60f63 --- /dev/null +++ b/packages/alfa-selective/src/selective.ts @@ -0,0 +1,103 @@ +import { Either, Left, Right } from "@siteimprove/alfa-either"; +import { Equatable } from "@siteimprove/alfa-equatable"; +import { Functor } from "@siteimprove/alfa-functor"; +import { Hash, Hashable } from "@siteimprove/alfa-hash"; +import { Serializable } from "@siteimprove/alfa-json"; +import { Mapper } from "@siteimprove/alfa-mapper"; +import { Predicate } from "@siteimprove/alfa-predicate"; +import { Refinement } from "@siteimprove/alfa-refinement"; + +export class Selective + implements + Functor, + Iterable, + Equatable, + Hashable, + Serializable> { + public static of(value: T): Selective { + return new Selective(Left.of(value)); + } + + private readonly _value: Either; + + private constructor(value: Either) { + this._value = value; + } + + public map(mapper: Mapper): Selective { + return new Selective( + this._value.either( + (value) => Left.of(value) as Either, + (value) => Right.of(mapper(value)) + ) + ); + } + + public if

( + refinement: Refinement, + mapper: Mapper + ): Selective, T | U>; + + public if( + predicate: Predicate, + mapper: Mapper + ): Selective; + + public if( + predicate: Predicate, + mapper: Mapper + ): Selective { + return this._value.either( + (value) => + predicate(value) ? new Selective(Right.of(mapper(value))) : this, + () => this + ); + } + + public else(mapper: Mapper): Selective { + return new Selective( + Right.of( + this._value.either( + (value) => mapper(value), + (value) => value + ) + ) + ); + } + + public get(): S | T { + return this._value.get(); + } + + public equals(value: Selective): boolean; + + public equals(value: unknown): value is this; + + public equals(value: unknown): boolean { + return value instanceof Selective && value._value.equals(this._value); + } + + public hash(hash: Hash): void { + this._value.hash(hash); + } + + public *iterator(): Iterator { + yield this._value.get(); + } + + public [Symbol.iterator](): Iterator { + return this.iterator(); + } + + public toJSON(): Selective.JSON { + return this._value.toJSON(); + } + + public toString(): string { + return `Selective { ${this._value} }`; + } +} + +export namespace Selective { + export type JSON = Either.JSON; +} diff --git a/packages/alfa-selective/test/selective.spec.ts b/packages/alfa-selective/test/selective.spec.ts new file mode 100644 index 0000000000..ca90de1004 --- /dev/null +++ b/packages/alfa-selective/test/selective.spec.ts @@ -0,0 +1,48 @@ +import { test } from "@siteimprove/alfa-test"; + +import { Refinement } from "@siteimprove/alfa-refinement"; + +import { Selective } from "../src/selective"; + +const isFoo: Refinement = (string): string is "foo" => + string === "foo"; + +const isBar: Refinement = (string): string is "bar" => + string === "bar"; + +test("#if() conditionally applies a function to a selective value", (t) => { + Selective.of("foo") + .if(isFoo, (value) => { + t.equal(value, "foo"); + }) + .if(isBar, () => { + t.fail(); + }); +}); + +test(`#else() applies a function to a selective value that matched no other + conditions`, (t) => { + Selective.of("bar") + .if(isFoo, () => { + t.fail(); + }) + .else((value) => { + t.equal(value, "bar"); + }); +}); + +test("#get() returns the value of a selective", (t) => { + t.equal( + Selective.of("foo") + .if(isFoo, () => "was foo") + .get(), + "was foo" + ); + + t.equal( + Selective.of("bar") + .if(isFoo, () => "was foo") + .get(), + "bar" + ); +}); diff --git a/packages/alfa-selective/tsconfig.json b/packages/alfa-selective/tsconfig.json new file mode 100644 index 0000000000..5ca01b7524 --- /dev/null +++ b/packages/alfa-selective/tsconfig.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "extends": "../tsconfig.json", + "files": ["src/index.ts", "src/selective.ts", "test/selective.spec.ts"], + "references": [ + { + "path": "../alfa-either" + }, + { + "path": "../alfa-equatable" + }, + { + "path": "../alfa-functor" + }, + { + "path": "../alfa-hash" + }, + { + "path": "../alfa-json" + }, + { + "path": "../alfa-mapper" + }, + { + "path": "../alfa-predicate" + }, + { + "path": "../alfa-refinement" + }, + { + "path": "../alfa-test" + } + ] +} diff --git a/packages/tsconfig.json b/packages/tsconfig.json index 3449bc36f7..6a2b0b978d 100644 --- a/packages/tsconfig.json +++ b/packages/tsconfig.json @@ -78,6 +78,7 @@ { "path": "alfa-rules" }, { "path": "alfa-scraper" }, { "path": "alfa-sarif" }, + { "path": "alfa-selective" }, { "path": "alfa-selector" }, { "path": "alfa-sequence" }, { "path": "alfa-set" }, From b14d61926f89be1c603327e0d95128d78310b889 Mon Sep 17 00:00:00 2001 From: Kasper Isager Date: Wed, 10 Feb 2021 15:48:31 +0100 Subject: [PATCH 2/2] Add additional test case --- packages/alfa-selective/test/selective.spec.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/alfa-selective/test/selective.spec.ts b/packages/alfa-selective/test/selective.spec.ts index ca90de1004..dedd7cdc02 100644 --- a/packages/alfa-selective/test/selective.spec.ts +++ b/packages/alfa-selective/test/selective.spec.ts @@ -46,3 +46,13 @@ test("#get() returns the value of a selective", (t) => { "bar" ); }); + +test(`#map() applies a function to a matched selective value`, (t) => { + t.equal( + Selective.of("foo") + .if(isFoo, () => "was foo") + .map((string) => string.toUpperCase()) + .get(), + "WAS FOO" + ); +});