From 40a0012488d2272d451ebdc4ec50bec48c9a6ece Mon Sep 17 00:00:00 2001 From: Brandon Blaylock Date: Tue, 28 Nov 2023 15:59:51 -0700 Subject: [PATCH] feat: modify Newtype and add Nest Added a Nest type to kind.ts that allows for easier nesting of kinds. Modified Newtype to use kind.ts#Hold instead of a fake object/accessor pattern. Also played with various ideas in contrib and examples. --- contrib/generator.ts | 21 ++++++++ contrib/most.ts | 18 +++---- examples/fetch_archives.ts | 2 +- examples/profunctor_optics.ts | 92 +++++++++++++++++++++++++++++++++++ examples/tree.ts | 39 +++++++++++++++ kind.ts | 14 +++++- newtype.ts | 43 ++++++++-------- optic.ts | 2 +- 8 files changed, 196 insertions(+), 35 deletions(-) create mode 100644 contrib/generator.ts create mode 100644 examples/profunctor_optics.ts create mode 100644 examples/tree.ts diff --git a/contrib/generator.ts b/contrib/generator.ts new file mode 100644 index 0000000..efc6b47 --- /dev/null +++ b/contrib/generator.ts @@ -0,0 +1,21 @@ +import type { In, Kind, Out } from "../kind.ts"; +import type { Flatmappable } from "../flatmappable.ts"; + +import { todo } from "../fn.ts"; + +export interface KindGenerator extends Kind { + readonly kind: Generator, Out, In>; +} + +export const FlatmappableGenerator: Flatmappable = { + wrap: (a) => { + const ret = function* () { + yield a; + return undefined; + }; + return ret; + }, + map: todo(), + apply: todo(), + flatmap: todo(), +}; diff --git a/contrib/most.ts b/contrib/most.ts index 96165dc..0aafd57 100644 --- a/contrib/most.ts +++ b/contrib/most.ts @@ -1,5 +1,5 @@ import type { Scheduler, Stream } from "npm:@most/types@1.1.0"; -import type { $, In, InOut, Kind, Out } from "../kind.ts"; +import type { $, Kind, Nest, Out } from "../kind.ts"; import type { Wrappable } from "../wrappable.ts"; import type { Mappable } from "../mappable.ts"; import type { Applicable } from "../applicable.ts"; @@ -76,7 +76,12 @@ export const map: Mappable["map"] = M.map; export const apply: Applicable["apply"] = M.ap; -export const flatmap: Flatmappable["flatmap"] = M.chain; +export const flatmap: Flatmappable["flatmap"] = (faui) => (ua) => + pipe( + ua, + M.map(faui), + M.switchLatest, + ); export const WrappableStream: Wrappable = { wrap }; @@ -98,14 +103,7 @@ export const bindTo = createBindTo(FlatmappableStream); export const tap = createTap(FlatmappableStream); export interface TransformStream extends Kind { - readonly kind: Stream< - $< - U, - [Out, Out, Out], - [In], - [InOut] - > - >; + readonly kind: Stream>; } export function transformStream( diff --git a/examples/fetch_archives.ts b/examples/fetch_archives.ts index 1fd82da..55efcc7 100644 --- a/examples/fetch_archives.ts +++ b/examples/fetch_archives.ts @@ -2,7 +2,7 @@ import * as D from "../decoder.ts"; import * as E from "../either.ts"; import * as S from "../schemable.ts"; import * as AE from "../async_either.ts"; -import * as O from "../optics.ts"; +import * as O from "../optic.ts"; import * as J from "../json_schema.ts"; import { flow, pipe } from "../fn.ts"; diff --git a/examples/profunctor_optics.ts b/examples/profunctor_optics.ts new file mode 100644 index 0000000..9dd1850 --- /dev/null +++ b/examples/profunctor_optics.ts @@ -0,0 +1,92 @@ +// deno-lint-ignore-file no-explicit-any +// +import type { $, Hold, In, Kind, Out } from "../kind.ts"; +import type { Pair } from "../pair.ts"; +import type { Mappable } from "../mappable.ts"; + +import * as O from "../option.ts"; +import * as F from "../fn.ts"; +import * as P from "../pair.ts"; +import { flow, pipe, todo } from "../fn.ts"; + +export interface Profunctor { + readonly dimap: ( + fld: (l: L) => D, + fai: (a: A) => I, + ) => (ua: $) => $; +} + +export interface Forget extends Hold { + readonly runForget: (a: A) => R; +} + +export interface KindForget extends Kind { + readonly kind: Forget, In, Out>; +} + +export const ProfunctorForget: Profunctor = { + dimap: (fld, _fai) => (ua) => ({ + runForget: flow(fld, ua.runForget), + }), +}; + +export interface Star { + readonly runStar: ( + x: X, + ) => $; +} + +export interface KindStar extends Kind { + readonly kind: Star, Out>; +} + +export function profunctorStar( + M: Mappable, +): Profunctor> { + return { + dimap: (fld, fai) => (ua) => ({ + // This should work but we need to do some type spelunking + runStar: (flow(fld, ua.runStar, M.map(fai))) as any, + }), + }; +} + +export interface Strong extends Profunctor { + readonly first: < + A, + X = never, + B = never, + C = never, + D = unknown, + E = unknown, + >( + ua: $, + ) => $, B, C], [Pair], [E]>; + readonly second: < + A, + X = never, + B = never, + C = never, + D = unknown, + E = unknown, + >( + ua: $, + ) => $, B, C], [Pair], [E]>; +} + +export function strong({ dimap }: Profunctor): Strong { + return { + dimap, + first: dimap(P.getFirst, (a) => P.pair(a, null as any)), + second: dimap(P.getSecond, (a) => P.pair(null as any, a)), + }; +} + +export const ProfunctorStarFn: Profunctor> = profunctorStar( + F.MappableFn, +); +export const StrongStarFn: Strong> = strong( + ProfunctorStarFn, +); + +// Hmm diff --git a/examples/tree.ts b/examples/tree.ts new file mode 100644 index 0000000..113c61d --- /dev/null +++ b/examples/tree.ts @@ -0,0 +1,39 @@ +import type { $, Kind, Out } from "../kind.ts"; +import type { Option } from "../option.ts"; + +import * as O from "../option.ts"; +import * as A from "../array.ts"; +import { pipe } from "../fn.ts"; + +export type Tree = { + readonly value: Option; + readonly forest: Forest; +}; + +export type Forest = ReadonlyArray>; + +// deno-lint-ignore no-explicit-any +export type AnyTree = Tree; + +export type Type = U extends Tree ? A : never; + +export interface KindTree extends Kind { + readonly kind: Tree>; +} + +export function tree(value: Option, forest: Forest = []): Tree { + return { value, forest }; +} + +export function init(forest: Forest = []): Tree { + return { value: O.none, forest }; +} + +export function wrap(value: A, forest: Forest = []): Tree { + return { value: O.some(value), forest }; +} + +export function map(fai: (a: A) => I): (ua: Tree) => Tree { + const mapValue = O.map(fai); + return (ua) => tree(mapValue(ua.value), pipe(ua.forest, A.map(map(fai)))); +} diff --git a/kind.ts b/kind.ts index fa7273e..de44800 100644 --- a/kind.ts +++ b/kind.ts @@ -118,8 +118,7 @@ export interface Fix extends Kind { /** * Create a scoped symbol for use with Hold. */ -const HoldSymbol = Symbol("Hold"); -type HoldSymbol = typeof HoldSymbol; +declare const HoldSymbol: unique symbol; /** * The Hold interface allows one to trick the typescript compiler into holding @@ -130,6 +129,17 @@ export interface Hold { readonly [HoldSymbol]?: A; } +/** + * Some Flatmappable Transform kinds only have one out type, in those cases we + * use this Nest type to make the transform kind cleaner. + */ +export type Nest = $< + U, + [Out, Out, Out], + [In], + [InOut] +>; + /** * Spread the keys of a struct union into a single struct. */ diff --git a/newtype.ts b/newtype.ts index d6f45e4..57b24be 100644 --- a/newtype.ts +++ b/newtype.ts @@ -23,12 +23,8 @@ import { fromPredicate } from "./option.ts"; import { iso as _iso, prism as _prism } from "./optic.ts"; import { identity, unsafeCoerce } from "./fn.ts"; -/** - * These are phantom types used by Newtype to both identify and distinquish - * between a Newtype and its representation value. - */ -declare const Brand: unique symbol; -declare const Value: unique symbol; +declare const BrandSymbol: unique symbol; +declare const ValueSymbol: unique symbol; /** * Create a branded type from an existing type. The branded @@ -45,8 +41,8 @@ declare const Value: unique symbol; * * type Integer = Newtype<'Integer', number>; * - * const int = 1 as unknown as Integer; - * const num = 1; + * const int = 1 as Integer; + * const num = 1 as number; * * declare function addOne(n: Integer): number; * @@ -56,7 +52,18 @@ declare const Value: unique symbol; * * @since 2.0.0 */ -export type Newtype = { readonly [Brand]: B; readonly [Value]: A }; +export type Newtype = A & { + readonly [ValueSymbol]: A; + readonly [BrandSymbol]: B; +}; + +/** + * A type alias for Newtype that is useful when constructing + * Newtype related runtime instances. + * + * @since 2.0.0 + */ +export type AnyNewtype = Newtype; /** * Extracts the inner type value from a Newtype. @@ -72,15 +79,9 @@ export type Newtype = { readonly [Brand]: B; readonly [Value]: A }; * * @since 2.0.0 */ -export type ToValue> = T[typeof Value]; - -/** - * A type alias for Newtype that is useful when constructing - * Newtype related runtime instances. - * - * @since 2.0.0 - */ -export type AnyNewtype = Newtype; +export type ToValue = T extends Newtype + ? A + : never; /** * Retype an existing Comparable from an inner type to a Newtype. @@ -100,7 +101,7 @@ export type AnyNewtype = Newtype; export function getComparable( eq: Comparable>, ): Comparable { - return eq as Comparable; + return eq as unknown as Comparable; } /** @@ -121,7 +122,7 @@ export function getComparable( export function getSortable( ord: Sortable>, ): Sortable { - return ord as Sortable; + return ord as unknown as Sortable; } /** @@ -227,6 +228,6 @@ export function prism( ): Prism, T> { return _prism, T>( fromPredicate(predicate) as (s: ToValue) => Option, - identity, + identity as (a: T) => ToValue, ); } diff --git a/optic.ts b/optic.ts index da2984e..3b54001 100644 --- a/optic.ts +++ b/optic.ts @@ -1235,7 +1235,7 @@ export function traverse( first: Optic>, ) => Optic, S, A> { return compose(fold( - T.fold((as, a) => [...as, a], A.init()), + T.fold((as, a) => [...as, a], ((): A[] => [])()), T.map, )); }