Created
January 21, 2020 18:19
-
-
Save fvilante/6efcf00aa0904f58b4b25a8b0d954078 to your computer and use it in GitHub Desktop.
Among Algebraic type
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Result } from "../result" | |
// ------------------------------------------------ | |
// pseudo-type | |
// ------------------------------------------------ | |
// todo: Similar concept of Maybe.Nothing. Reuse the nothing of maybe ? Or should be the other way around, make Nothing sub type of PseudoType | |
// todo: should be minimized and reused by Nothing | |
// purpose of this type is to save a type and use it in static-time but without have to | |
// construct an real instance of that type. There's a little cost at run-time, but | |
// in TS probably do not exists other alternative. Maybe.nothing uses same concept | |
export type PseudoType<A> = { | |
readonly kind: 'PseudoType' | |
readonly type: A | |
} ; type AnyPseudoType = PseudoType<any> | |
export const PseudoType = <A>() => <N extends string>(name: N):PseudoType<A> => | |
({ kind: 'PseudoType', type: undefined as unknown as A}) | |
const _Type = <A>() => PseudoType<A>() | |
// informal test | |
const aaaa = PseudoType<number>('number') | |
const ffff = <A extends AnyPseudoType>(): PseudoType<A> => { | |
return PseudoType<A>('AnyPseudoType') | |
} | |
const bbbbb = _Type<number>() | |
// ------------------------------------------------ | |
// Bound | |
// ------------------------------------------------ | |
// todo: this is generic should have its own place / its is more generic than Message concept? | |
type Bound<A,B> = (source: A) => B | |
// ------------------------------------------------ | |
// Unbounded Message | |
// ------------------------------------------------ | |
// eager | |
type UnboundedMessage<K,A> = { | |
readonly kind: 'UnboundedMessage' | |
readonly unsafeRun: () => { readonly name: K, readonly payload: A } | |
readonly identity: () => UnboundedMessage<K,A> | |
readonly mapName: <K1>(f: (_: K) => K1) => UnboundedMessage<K1,A> | |
readonly mapPayload: <B>(f: (_: A) => B) => UnboundedMessage<K,B> | |
readonly bimap: <K1,B>(f: (_:K) => K1, g: (_:A) => B) => UnboundedMessage<K1,B> | |
} | |
const UnboundedMessage = <K,A>(name: K, payload: A): UnboundedMessage<K,A> => { | |
type H = UnboundedMessage<K,A> | |
const unsafeRun: H['unsafeRun'] = () => { | |
return { name, payload } | |
} | |
const identity: H['identity'] = () => { | |
return UnboundedMessage(name, payload) | |
} | |
const mapName: H['mapName'] = f => { | |
return UnboundedMessage(f(name), payload) | |
} | |
const mapPayload: H['mapPayload'] = f => { | |
return UnboundedMessage(name, f(payload)) | |
} | |
const bimap: H['bimap'] = (f,g) => { | |
return UnboundedMessage(f(name), g(payload)) | |
} | |
return { | |
kind: 'UnboundedMessage', | |
unsafeRun, | |
identity, | |
mapName, | |
mapPayload, | |
bimap, | |
} | |
} | |
// ------------------------------------------------ | |
// Bounded Message | |
// ------------------------------------------------ | |
type Message<T, K extends keyof T> = { | |
readonly kind: 'Message' | |
readonly unsafeRun: () => { readonly name: K, readonly payload: T[K], readonly pseudoType: undefined } | |
readonly identity: () => Message<T,K> | |
readonly toUnboundedMessage: () => UnboundedMessage<K, T[K]> | |
} | |
const Message = <T>(pseudoType: PseudoType<T>) => <K extends keyof T>(name: K, payload: T[K]):Message<T,K> => { | |
type H = Message<T,K> | |
const unsafeRun: H['unsafeRun'] = () => ({ name, payload, pseudoType: undefined }) | |
const identity: H['identity'] = () => Message(pseudoType)(name, payload) | |
const toUnboundedMessage: H['toUnboundedMessage'] = () => { | |
return UnboundedMessage(name, payload) | |
} | |
return { | |
kind: 'Message', | |
unsafeRun, | |
identity, | |
toUnboundedMessage, | |
} | |
} | |
// static part | |
type Message_ = { | |
// todo: I think when rebounding an unbounded message a run-time type check should run. What happen if the unbounded message being joned is came from a bad cast ? Investigate it. | |
readonly fromUnbondedMessage: <T>(a: PseudoType<T>) => <K extends keyof T>(m: UnboundedMessage<K, T[K]>) => Message<T,K> | |
} | |
const fromUnbondedMessage: Message_['fromUnbondedMessage'] = a => m => { | |
const {name, payload} = m.unsafeRun() | |
return Message(a)(name, payload) | |
} | |
const Message_: Message_ = { | |
fromUnbondedMessage, | |
} | |
// ------------------------------------------------ | |
// Message Matcher | |
// ------------------------------------------------ | |
// informal test | |
const Test00 = () => { | |
type Input = { | |
readonly msg1: number | |
readonly msg2: string | |
readonly homogeneous: string | |
} | |
type Output = { | |
readonly master: [number, 'master'] | |
readonly slave: [number, 'slave'] | |
readonly homogeneous: string | |
} | |
const m1 = (a: Input):Output => { | |
return { | |
master: [a.msg1*10, 'master'], | |
slave: [Number(a.msg2), 'slave'], | |
homogeneous: `not_available`, | |
} | |
} | |
const msg1 = UnboundedMessage('msg1', 10) | |
const LinearMatch: LinearMatch = (Input) | |
} | |
const Test10 = () => { | |
type Input = { | |
readonly msg1: number | |
readonly msg2: string | |
readonly homogeneous: string | |
} | |
type TransicaoAB = { | |
readonly msg1: ['nn', string] | |
readonly msg2: ['aa', string] | |
readonly homogeneous: ['juca', number] | |
} | |
const _return = <T>(_:T):T => _ | |
type Chegada<A> = { | |
readonly [K in keyof A]: [keyof any, any] | |
} | |
type Matcher<A,B extends Chegada<A>> = { | |
readonly [K in keyof A]: readonly [ | |
/*mapName*/ (name: K) => B[K][0], | |
/*mapPayload*/ (payload: A[K]) => B[K][1] | |
] | |
} | |
type Output<A, M extends Matcher<A,any>> = { | |
readonly [K in keyof A]: {readonly key2: ReturnType<M[K][0]>, readonly value: ReturnType<M[K][1]>} | |
} | |
type cccMama = Matcher<Input, TransicaoAB> | |
type ooo = Output<Input, cccMama> | |
type ooooo1 = ooo[keyof ooo]['key1'] | |
type ooooo2 = ooo[keyof ooo]['key2'] | |
type vvvvv = ooo[keyof ooo]['value'] | |
type X<T> = { | |
readonly [K in keyof T]: | |
} | |
type ffff = X<ooooo> | |
const m = ():Matcher<Input> => { | |
const identity = [_return, _return] as const | |
return { | |
msg1: [ m => m as unknown , ], | |
msg2: identity, | |
homogeneous: identity, | |
} | |
} | |
const h = () => ({ | |
msg1: { f: a => 'oi', g: p => validate(p) } | |
msg2: _return, | |
homogeneous: _return, | |
}) | |
} | |
// ------------------------------------------------ | |
// Matcher | |
// ------------------------------------------------ | |
// todo: is bellow same as: Matcher<A, Result<E,A>> ? | |
// todo: this is just an envelop of Result over the Payload. Should change named Result-something... ? | |
type MatchFlatten<A> = { readonly kind: 'MatchFlatten'} & { | |
readonly [K in keyof A]: Bound<UnboundedMessage<K, A[K]>, UnboundedMessage<K, A[K]>> | |
} | |
type MatchCrossed<A> = { readonly kind: 'MatchCrossed'} & { | |
readonly [K in keyof A]: Bound<UnboundedMessage<K, A[K]>, UnboundedMessage<keyof A, A[keyof A]>> | |
} | |
type MatchExported<A,B> = { readonly kind: 'MatchExported'} & { | |
readonly [K in keyof A]: Bound<UnboundedMessage<K, A[K]>, UnboundedMessage<keyof B, B[keyof B]>> | |
} | |
type NarrowMessageAmong<A> = <M extends UnboundedMessage<keyof A, A[keyof A]>>(m: M) => Among<A> | |
const NarrowMessageAmong = <A>():NarrowMessageAmong<A> => msg => { | |
const { name, payload } = msg.unsafeRun() | |
const narrowedPayload = payload | |
const a = Among<A>(name, ) | |
} | |
// ------------------------------------------------ | |
// Among | |
// ------------------------------------------------ | |
// todo: this is generic should live in its own file ? | |
/** generic matcher */ | |
type Matcher<A,R> = { | |
readonly [K in keyof A]: (payload: A[K], msg: K) => R | |
} | |
type Among<A> = { | |
readonly kind: 'Among' | |
readonly unsafeRun: () => ({readonly msg: keyof A, readonly payload: A[keyof A]}) | |
readonly identity: () => Among<A> | |
readonly reset: <K extends keyof A>(msg: K, payload: A[K]) => Among<A> | |
readonly match: <R>(matcher: Matcher<A,R>) => R | |
readonly matchA: (matcher: Matcher<A,Among<A>>) => Among<A> | |
readonly matchB: <B>(matcher: Matcher<A,Among<B>>) => Among<B> | |
readonly toMessage: () => UnboundedMessage<keyof A, A[keyof A]> | |
//readonly concat: <B>(b: Among<B>) => Among<Or<Among<A>,Among<B>>> | |
//readonly single: <B>(f: (_:Among<A>) => Among<B>) => Among<B> | |
//readonly flatten: (mma: Among<Among<A>>) => Among<A> | |
} | |
/** Eager / todo: should A be constrained to not accept Symbol (?)*/ | |
const Among = <A>() => <K extends keyof A>(_msg: K, _payload: A[K]): Among<A> => { | |
type H = Among<A> | |
const _Among = Among<A>() | |
const unsafeRun: H['unsafeRun'] = () => { | |
return {msg: _msg, payload: _payload} | |
} | |
const identity: H['identity'] = () => _Among(_msg, _payload) | |
const reset: H['reset'] = (m, p) => { | |
return _Among(m, p) | |
} | |
const match: H['match'] = matcher => { | |
return matcher[_msg](_payload, _msg) | |
} | |
const matchA: H['matchA'] = matcher => { | |
return match(matcher) | |
} | |
const matchB: H['matchB'] = matcher => { | |
return match(matcher) | |
} | |
const concat: H['concat'] = b => { | |
return Or(identity(), b) | |
} | |
const toMessage: H['toMessage'] = () => { | |
return UnboundedMessage(name, payload) | |
} | |
return { | |
kind: 'Among', | |
unsafeRun, | |
identity, | |
reset, | |
match: match, | |
matchA, | |
matchB, | |
toMessage, | |
//concat, | |
} | |
} | |
// usage | |
// construction part | |
const Test1 = () => { | |
type ParserMessages = { | |
readonly juca: number | |
readonly alvaro: string | |
readonly prop: 'hi' | |
} | |
const ParserMessages = Among<ParserMessages>() | |
type FileHandlerErrors = { | |
readonly CannotOpen: { readonly filename: string } | |
readonly ConnotClose: { readonly numberOfReties: number } | |
readonly 'Write_Error': undefined | |
readonly "Read_Error": undefined | |
} | |
const FileHandlerErrors = Among<FileHandlerErrors>() | |
type CMPP9900LH = { | |
readonly 'Posicao inicial': number, | |
readonly 'Posicao final': number, | |
readonly 'Aceleracao de avanco': number, | |
readonly 'Aceleracao de retorno': number, | |
} | |
const CMPP9900LH = Among<CMPP9900LH>() | |
type Fails<I,E> = { | |
readonly Signal: Among<I>, | |
readonly Exception: Among<E>, | |
} | |
type SystemFail = { | |
readonly InitializationSystem: Among<FileHandlerErrors> | |
readonly RunTimeSystem: Among<FileHandlerErrors> | |
readonly FinalizationSystem: Among<CMPP9900LH> | |
readonly BackupSystemRetries: number | |
} | |
const SystemFail = Among<SystemFail>() | |
//type Continuation = | |
type ContinuationFail = Fails<FileHandlerErrors, ParserMessages> | |
const ContinuationFail = Among<ContinuationFail>() | |
const c = ContinuationFail('Signal', FileHandlerErrors('CannotOpen', { filename: 'todoist.csv' })) | |
const a = [ | |
ParserMessages('juca', 10), | |
FileHandlerErrors('Write_Error', undefined), | |
CMPP9900LH('Posicao inicial', 10) | |
] as const | |
type Continuation<A,E,I> = [A, Among<E>, Among<I>] | |
type AcceptedImageExtensions = { | |
readonly bmp: '' | |
} | |
type FileSystemControler = { | |
readonly HddLocalControler: unknown //HddLocalControler | |
readonly HttpSystem: unknown //HttpControler | |
} | |
type FileControler = { | |
readonly domain: Among<FileSystemControler> | |
readonly name: string, | |
readonly extension: Among<unknown /*ImageExtensions*/> | |
} | |
const err1 = FileHandlerErrors('ConnotClose', { numberOfReties: 10}) | |
const err2 = FileHandlerErrors('Read_Error', undefined) | |
const err3 = SystemFail( 'RunTimeSystem', FileHandlerErrors('CannotOpen', { filename: 'todo.txt' })) | |
} | |
// normal matcher | |
const Test2 = () => { | |
// generic | |
// specific | |
type Input = { | |
readonly fromSourceA: number | |
readonly fromSourceB: string | |
} | |
type InputValidatorMatcher = MatchFlatten<Input> | |
const inputValidator = (): Resultfy<string, Input> => { | |
type H = Resultfy<string, Input> | |
const fromSourceA: H['fromSourceA'] = (a, msg) => { | |
const isValid = a >= 0 | |
return isValid ? Result.Value(a) : | |
Result.Error(`a propriedade: ${msg}, deve ser um numero maior que zero porém esta igual a ${a}`) | |
} | |
const fromSourceB: H['fromSourceB'] = (a, msg) => { | |
const minLength = 5 | |
const isValid = a.length > minLength | |
return isValid ? Result.Value(a) : | |
Result.Error(`a propriedade: ${msg}, deve conter uma string maior do que ${minLength} porem a string atual ${a} contem tamanho de ${a.length}`) | |
} | |
return { | |
'fromSourceA': fromSourceA, | |
'fromSourceB': fromSourceB, | |
} | |
} | |
const matchLocked = <A>(a: Among<A>, matcher: LockedMatcher<A>) => { | |
const b = a.match(matcher) | |
} | |
} | |
const Test3 = () => { | |
const _Time = Among<_Time>() ; type _Time = { | |
readonly 'second': number, | |
readonly 'minute': number, | |
readonly 'mTick': number, //microcontroler tick | |
} | |
const _Space = Among<_Space>() ; type _Space = { | |
readonly 'milimiter': number, | |
readonly 'meter': number, | |
} | |
const Aceleracao = Among<Aceleracao>() ; type Aceleracao = { | |
readonly 'mm/s2': number, | |
readonly 'm/s2': number, | |
readonly 'pulse/mtick2': number, | |
readonly 'custom': [Among<_Space>, Among<_Time>, ] | |
} | |
const Velocidade = Among<Velocidade>() ; type Velocidade = { | |
readonly 'mm/s': number, | |
readonly 'm/s': number, | |
} | |
const CMPP9900LG = Among<CMPP9900LG>() ; type CMPP9900LG = { | |
readonly PosicaoInicial: Among<Aceleracao> | |
readonly VelocidadeDeAvanco: Among<Aceleracao> | |
} | |
const CMPP9900LH = Among<CMPP9900LH>() ; type CMPP9900LH = { | |
readonly PosicaoInicial: Among<Aceleracao> | |
readonly NumeroDeMensagensNoAvanco: number | |
} | |
type CmppDriver = typeof CmppDriver ; | |
const CmppDriver = { | |
CMPP9900LG: CMPP9900LG, | |
CMPP9900LH: CMPP9900LH, | |
} as const | |
type Driver_<N extends keyof CmppDriver> = { | |
readonly kind: 'Driver' | |
readonly parameter: <C extends CmppDriver[N]>(msg: Parameters<C>[0], payload: Parameters<C>[1]) => ReturnType<C> | |
} ; type AnyDriver = Driver_<keyof CmppDriver> | |
const Driver_ = <N extends keyof CmppDriver>(driverName: N): Driver_<N> => { | |
// Unsafe function take care. | |
type DriverConstructor = (_msg: any, _payload: any) => any | |
const driver: DriverConstructor = CmppDriver[driverName] | |
return { | |
kind: 'Driver', | |
parameter: (msg, payload) => driver(msg, payload) | |
} | |
} | |
const gg = <N extends keyof CmppDriver>(driverName: N) => <D extends Driver_<N>, P extends Parameters<D['parameter']>[0], L extends Parameters<D['parameter']>[1]>(param: P, payload: L) => { | |
return Driver_(driverName).parameter(param, payload) | |
} | |
const tDriver = gg('CMPP9900LG') | |
const a = tDriver('PosicaoInicial', Aceleracao('custom', [_Space('meter', 10), _Time('mTick', 10), ]) | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Message } from "./message" | |
// ========================== | |
type PatternMatch<A, R> = { | |
readonly [K in keyof A]: (key: K, payload: A[K]) => R | |
} | |
// models an arbitrary length Either | |
type Among<A> = { | |
readonly kind: 'Among' | |
readonly match: <R>(m: PatternMatch<A,R>) => R | |
readonly matchAmong: <R>(m: PatternMatch<A,Among<R>>) => Among<R> | |
} | |
const Among = <A, K extends keyof A>(key: K, payload: A[K]): Among<A> => { | |
type H = Among<A> | |
const match: H['match'] = m => { | |
return m[key](key, payload) | |
} | |
const matchToAmong: H['matchAmong'] = m => match(m) | |
return { | |
kind: 'Among', | |
match, | |
matchAmong: matchToAmong, | |
} | |
} | |
// static part | |
export type Among_ = { | |
readonly fromMessage: <A, K extends keyof A>(m: Message<A,K,A[K]>) => Among<A> | |
} | |
type H = Among_ | |
const fromMessage: H['fromMessage'] = m => Among(m.unsafeGetKey(), m.unsafeGetPayload()) | |
export const Among_: Among_ = { | |
fromMessage, | |
} | |
// informal test | |
const Test00 = () => { | |
type MyContext = { | |
readonly error1: string | |
readonly error2: number | |
} | |
interface NewContext extends MyContext { | |
readonly error3: string | |
readonly error4: string | |
} | |
const MessageCreator = Message<MyContext>() | |
const MessageCreator2 = Message<NewContext>() | |
const m1 = MessageCreator('error1', 'Ola') | |
const m2 = MessageCreator('error2', 12) | |
const a1 = Among_.fromMessage(m1) | |
const a2 = a1.matchAmong<NewContext>( { | |
error1: p => Among_.fromMessage(MessageCreator2('error1', 'oi')), | |
error2: p => Among_.fromMessage(MessageCreator2('error4', 'world')), | |
}) | |
const a3 = a1.matchAmong<NewContext>( { | |
error1: (key, payload) => Among('error1', payload), | |
error2: (key, payload) => Among('error4', String(payload)), | |
}) | |
const a4 = a3.match({ | |
error1: (key, value) => undefined, | |
error2: (key, value) => undefined, | |
error3: (key, value) => undefined, | |
error4: (key, value) => undefined, | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Among is a
Either<A,B>
with arbitrary (but statically defined) length.Useful for error & signal passing