Skip to content

Instantly share code, notes, and snippets.

@fvilante
Created January 21, 2020 18:19
Show Gist options
  • Save fvilante/6efcf00aa0904f58b4b25a8b0d954078 to your computer and use it in GitHub Desktop.
Save fvilante/6efcf00aa0904f58b4b25a8b0d954078 to your computer and use it in GitHub Desktop.
Among Algebraic type
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), ])
}
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,
})
}
@fvilante
Copy link
Author

Among is a Either<A,B> with arbitrary (but statically defined) length.
Useful for error & signal passing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment