Prototype lifecycle
This commit is contained in:
parent
e220b7930c
commit
450745011a
@ -1,34 +1,26 @@
|
|||||||
import { type CursorPos } from 'node:readline'
|
import { type CursorPos } from 'node:readline'
|
||||||
import { type Either, fold, type Left, left, right } from '../util/basic/either'
|
import { type Either, left, right } from '../util/basic/either'
|
||||||
|
import { hasTag, type Tag } from '../util/basic/utility'
|
||||||
import { PaintCursorError } from './errors'
|
import { PaintCursorError } from './errors'
|
||||||
|
|
||||||
// Cursor stuffs
|
export interface CursorPosAxis extends Tag<'X' | 'Y'> {
|
||||||
interface CursorPosX {
|
|
||||||
readonly _tag: 'X'
|
|
||||||
readonly value: number
|
readonly value: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CursorPosY {
|
type CursorPosVal = (tag: 'X' | 'Y') => (x: number) => CursorPosAxis
|
||||||
readonly _tag: 'Y'
|
export const cursorPosVal: CursorPosVal = tag => x => ({_tag: tag, value: x})
|
||||||
readonly value: number
|
|
||||||
}
|
|
||||||
|
|
||||||
type CursorPosVal = (str: 'X' | 'Y') => (x: number) => CursorPosX | CursorPosY
|
export const cursorPos = (x: number) => (y: number): CursorPos => ({
|
||||||
const cursorPosVal: CursorPosVal = str => x => ({_tag: str, value: x})
|
rows: x,
|
||||||
|
cols: y,
|
||||||
|
})
|
||||||
|
|
||||||
type IsCursorPos = (x: 'X' | 'Y') => (y: CursorPosX | CursorPosY) => boolean
|
|
||||||
const isCursorPos: IsCursorPos = x => y => y._tag === x
|
|
||||||
|
|
||||||
const cursorPos = (x: number) => (y: number): CursorPos => ({rows: x, cols: y})
|
|
||||||
|
|
||||||
// Cursor Position
|
|
||||||
type SafeCursorPos =
|
type SafeCursorPos =
|
||||||
(x: CursorPosX | CursorPosY) =>
|
(x: CursorPosAxis) => (y: CursorPosAxis) => Either<CursorPos, Error>
|
||||||
(y: CursorPosX | CursorPosY) => Either<CursorPos, Error>
|
export const safeCursorPos: SafeCursorPos = (x) => y => {
|
||||||
const safeCursorPos: SafeCursorPos = (x) => y => {
|
return hasTag('X')(x) && hasTag('Y')(y)
|
||||||
return isCursorPos('X')(x) && isCursorPos('Y')(y)
|
|
||||||
? right(cursorPos(x.value)(y.value))
|
? right(cursorPos(x.value)(y.value))
|
||||||
: isCursorPos('Y')(x) && isCursorPos('X')(y)
|
: hasTag('Y')(x) && hasTag('X')(y)
|
||||||
? right(cursorPos(y.value)(x.value))
|
? right(cursorPos(y.value)(x.value))
|
||||||
: left(
|
: left(
|
||||||
new PaintCursorError(
|
new PaintCursorError(
|
||||||
@ -38,56 +30,30 @@ const safeCursorPos: SafeCursorPos = (x) => y => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(
|
export interface CanvasOrigin extends Tag<'Origin'> {
|
||||||
safeCursorPos(cursorPosVal('X')(5))(cursorPosVal('X')(7)),
|
|
||||||
)
|
|
||||||
|
|
||||||
// Tagged types
|
|
||||||
interface CanvasOrigin {
|
|
||||||
readonly _tag: 'Origin'
|
|
||||||
readonly value: CursorPos
|
readonly value: CursorPos
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CanvasEnd {
|
export interface CanvasEnd extends Tag<'End'> {
|
||||||
readonly _tag: 'End'
|
|
||||||
readonly value: CursorPos
|
readonly value: CursorPos
|
||||||
}
|
}
|
||||||
|
|
||||||
type CanvasOriginConstructor = (x: SafeCursorPos) => Either<CanvasOrigin, E>
|
export const origin = (pos: CursorPos): CanvasOrigin => ({
|
||||||
const canvasOrigin = (x: CursorPos): CanvasOrigin => ({
|
|
||||||
_tag: 'Origin',
|
|
||||||
value: x,
|
|
||||||
})
|
|
||||||
|
|
||||||
const canvasEnd = (x: CursorPos): CanvasEnd => ({
|
|
||||||
_tag: 'End',
|
|
||||||
value: x,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Canvas type using tagged positions
|
|
||||||
type Canvas = {
|
|
||||||
origin: CanvasOrigin,
|
|
||||||
end: CanvasEnd,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constructor helpers
|
|
||||||
const origin = (pos: CursorPos): CanvasOrigin => ({
|
|
||||||
_tag: 'Origin',
|
_tag: 'Origin',
|
||||||
value: pos,
|
value: pos,
|
||||||
})
|
})
|
||||||
|
|
||||||
const end = (pos: CursorPos): CanvasEnd => ({
|
export const end = (pos: CursorPos): CanvasEnd => ({
|
||||||
_tag: 'End',
|
_tag: 'End',
|
||||||
value: pos,
|
value: pos,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Curried canvas creator with type safety
|
export interface Canvas {
|
||||||
const canvas = (origin: CanvasOrigin) => (end: CanvasEnd): Canvas => ({
|
origin: CanvasOrigin
|
||||||
|
end: CanvasEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
export const canvas = (origin: CanvasOrigin) => (end: CanvasEnd): Canvas => ({
|
||||||
origin,
|
origin,
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Usage with proper type safety
|
|
||||||
const myCanvas = canvas(origin({rows: 3, cols: 1}))(
|
|
||||||
end({rows: 8, cols: 1}),
|
|
||||||
)
|
|
||||||
|
|||||||
101
src/lib/painter/lifecycle.ts
Normal file
101
src/lib/painter/lifecycle.ts
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import { type CursorPos } from 'node:readline'
|
||||||
|
import type { Either } from '../util/basic/either'
|
||||||
|
import {
|
||||||
|
DoOption,
|
||||||
|
getOrElse,
|
||||||
|
none,
|
||||||
|
type Option,
|
||||||
|
some,
|
||||||
|
} from '../util/basic/option'
|
||||||
|
import { hasTag, pipe, type Tag } from '../util/basic/utility'
|
||||||
|
import {
|
||||||
|
type CanvasEnd,
|
||||||
|
type CanvasOrigin,
|
||||||
|
cursorPos,
|
||||||
|
end,
|
||||||
|
origin,
|
||||||
|
} from './canvas'
|
||||||
|
|
||||||
|
interface PainterExit extends Tag<'PainterExit'> {}
|
||||||
|
|
||||||
|
interface PainterTask<T extends object = {}> extends Tag<'PainterTask'> {
|
||||||
|
task: {
|
||||||
|
origin: CanvasOrigin,
|
||||||
|
end: CanvasEnd,
|
||||||
|
effect: PainterEffect<T & Tag<'PainterState'>>,
|
||||||
|
state: Option<T & Tag<'PainterState'>>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TaskConstructor<T extends object = {}> = (
|
||||||
|
x: CursorPos,
|
||||||
|
y: CursorPos,
|
||||||
|
) => (f: PainterEffect, state?: T & Tag<'PainterState'>) => PainterTask<T>
|
||||||
|
const task: TaskConstructor = (x, y) => (f, state?) => {
|
||||||
|
return {
|
||||||
|
_tag: 'PainterTask',
|
||||||
|
task: {
|
||||||
|
origin: origin(x),
|
||||||
|
end: end(y),
|
||||||
|
effect: f,
|
||||||
|
state: !state ? none : some(state),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type PainterEffect<
|
||||||
|
T extends object = {},
|
||||||
|
> = (task: PainterTask<T>) => Either<PainterTask<T>, PainterExit>
|
||||||
|
|
||||||
|
type EffectState = <T extends object = {}>(x: T) => T & Tag<'PainterState'>
|
||||||
|
const effectState: EffectState = x => ({_tag: 'PainterState', ...x})
|
||||||
|
|
||||||
|
export { type PainterEffect, task }
|
||||||
|
|
||||||
|
//
|
||||||
|
// testing
|
||||||
|
//
|
||||||
|
|
||||||
|
type TestEffect = (x: PainterTask<{message: Option<string>}>) => PainterTask<
|
||||||
|
{message: Option<string>}
|
||||||
|
>
|
||||||
|
const testEffect: PainterEffect<{message: string, counter: number}> = x => {
|
||||||
|
const state = DoOption.start()
|
||||||
|
.bind('state', x.task.state)
|
||||||
|
.doL(ctx => {
|
||||||
|
console.log('Message:', ctx.state.message)
|
||||||
|
return some(undefined)
|
||||||
|
})
|
||||||
|
.doL(ctx => {
|
||||||
|
console.log('Counter:', ctx.state.counter)
|
||||||
|
return some(undefined)
|
||||||
|
})
|
||||||
|
.return(ctx =>
|
||||||
|
(ctx.state.counter < 100000)
|
||||||
|
? {...ctx.state, counter: ctx.state.counter + 1}
|
||||||
|
: none
|
||||||
|
)
|
||||||
|
if (pipe(state, getOrElse(none), hasTag('None'))) return {_tag: 'PainterExit'}
|
||||||
|
|
||||||
|
return task(x.task.origin.value, x.task.end.value)(
|
||||||
|
x.task.effect,
|
||||||
|
getOrElse(none)(state),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const textTask = task<{message: string, counter: number}>(
|
||||||
|
cursorPos(2)(0),
|
||||||
|
cursorPos(4)(0),
|
||||||
|
)(
|
||||||
|
testEffect,
|
||||||
|
{
|
||||||
|
_tag: 'PainterState',
|
||||||
|
message: 'test message',
|
||||||
|
counter: 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
const testFn = (x: PainterTask | PainterExit) => {
|
||||||
|
return hasTag('PainterExit')(x) ? 'Exit' : testFn(x.task.effect(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
testFn(textTask)
|
||||||
Loading…
Reference in New Issue
Block a user