Using a combination of lazy and either, decoders can become very slow.
The ~13 second runtime mentioned below would be a few milliseconds.
This may not be an issue. While writing the first iteration of this code I imagined it would be slow--but this does feel needlessly slow.
In the below example each AST type has a constant type field. I decreased the runtime of the below code by using the commented out "fast ast decoder." The fast version of this decoder first checks for the constant type field, then will choose the appropriate decoder to use (formerly returned in lazy calls) within a .transform
. Given these type fields are constant I think decoders could implement an optimization to handle this specific case.
// test.js
const value = {
"type": "label",
"tok": "eq",
"body": {
"type": "lambda",
"params": {
"type": "ident",
"tok": "x",
"sibling": {
"type": "ident",
"tok": "y"
}
},
"body": {
"type": "eq",
"left": {
"type": "ident",
"tok": "x"
},
"right": {
"type": "ident",
"tok": "y"
}
}
},
"sibling": {
"type": "label",
"tok": "not",
"body": {
"type": "lambda",
"params": {
"type": "ident",
"tok": "status"
},
"body": {
"type": "cond",
"body": {
"type": "condset",
"left": {
"type": "ident",
"tok": "status"
},
"right": {
"type": "false"
},
"sibling": {
"type": "condset",
"left": {
"type": "true"
},
"right": {
"type": "true"
}
}
}
}
},
"sibling": {
"type": "list",
"body": {
"type": "ident",
"tok": "not",
"sibling": {
"type": "list",
"body": {
"type": "ident",
"tok": "eq",
"sibling": {
"type": "strlit",
"tok": "this was a",
"sibling": {
"type": "strlit",
"tok": "triumph"
}
}
}
}
}
}
}
}
const decoders = require('decoders');
const typeDecoder = decoders.either(
decoders.constant("cdr"),
decoders.constant("cons"),
decoders.constant("cond"),
decoders.constant("condset"),
decoders.constant("lambda"),
decoders.constant("ident"),
decoders.constant("label"),
decoders.constant("list"),
decoders.constant("atom"),
decoders.constant("quote"),
decoders.constant("eq"),
decoders.constant("strlit"),
decoders.constant("numlit"),
decoders.constant("true"),
decoders.constant("false"),
decoders.constant("car"),
);
// fast ast decoder
// const astDecoder = decoders.inexact({type: typeDecoder}).transform(value => {
// switch (value.type) {
// case "cdr": return cdrDecoder.verify(value);
// case "cons": return consDecoder.verify(value);
// case "cond": return condDecoder.verify(value);
// case "condset": return condSetDecoder.verify(value);
// case "lambda": return lambdaDecoder.verify(value);
// case "ident": return identDecoder.verify(value);
// case "label": return labelDecoder.verify(value);
// case "list": return listDecoder.verify(value);
// case "atom": return atomDecoder.verify(value);
// case "quote": return quoteDecoder.verify(value);
// case "eq": return eqDecoder.verify(value);
// case "strlit": return StrlitDecoder.verify(value);
// case "numlit": return NumlitDecoder.verify(value);
// case "true": return TDecoder.verify(value);
// case "false": return NilDecoder.verify(value);
// case "car": return carDecoder.verify(value);
// }
// })
// slow ast decoder
const astDecoder = decoders.either(
decoders.lazy(() => TDecoder),
decoders.lazy(() => NilDecoder),
decoders.lazy(() => listDecoder),
decoders.lazy(() => identDecoder),
decoders.lazy(() => atomDecoder),
decoders.lazy(() => quoteDecoder),
decoders.lazy(() => eqDecoder),
decoders.lazy(() => StrlitDecoder),
decoders.lazy(() => NumlitDecoder),
decoders.lazy(() => carDecoder),
decoders.lazy(() => cdrDecoder),
decoders.lazy(() => consDecoder),
decoders.lazy(() => condDecoder),
decoders.lazy(() => lambdaDecoder),
decoders.lazy(() => labelDecoder),
)
const listDecoder = decoders.object({
sibling: decoders.optional(astDecoder),
type: decoders.constant('list'),
body: astDecoder,
});
const identDecoder = decoders.object({
sibling: decoders.optional(astDecoder),
type: decoders.constant('ident'),
tok: decoders.string,
})
const atomDecoder = decoders.object({
sibling: decoders.optional(astDecoder),
type: decoders.constant('atom'),
body: astDecoder,
})
const quoteDecoder = decoders.object({
sibling: decoders.optional(astDecoder),
type: decoders.constant('quote'),
body: astDecoder,
})
const eqDecoder = decoders.object({
sibling: decoders.optional(astDecoder),
type: decoders.constant('eq'),
left: astDecoder,
right: astDecoder,
})
const StrlitDecoder = decoders.object({
sibling: decoders.optional(astDecoder),
type: decoders.constant('strlit'),
tok: decoders.string,
})
const NumlitDecoder = decoders.object({
sibling: decoders.optional(astDecoder),
type: decoders.constant('numlit'),
tok: decoders.string,
})
const TDecoder = decoders.object({
sibling: decoders.optional(astDecoder),
type: decoders.constant('true'),
})
const NilDecoder = decoders.object({
sibling: decoders.optional(astDecoder),
type: decoders.constant('false'),
})
const carDecoder = decoders.object({
sibling: decoders.optional(astDecoder),
type: decoders.constant('car'),
body: astDecoder,
})
const cdrDecoder = decoders.object({
sibling: decoders.optional(astDecoder),
type: decoders.constant('cdr'),
body: astDecoder,
})
const consDecoder = decoders.object({
sibling: decoders.optional(astDecoder),
type: decoders.constant('cons'),
left: astDecoder,
right: astDecoder,
})
const condSetDecoder = decoders.object({
sibling: decoders.optional(decoders.lazy(() => condSetDecoder)),
type: decoders.constant('condset'),
left: astDecoder,
right: astDecoder,
})
const condDecoder = decoders.object({
sibling: decoders.optional(astDecoder),
type: decoders.constant('cond'),
body: condSetDecoder,
})
const lambdaArgDecoder = decoders.object({
sibling: decoders.optional(decoders.lazy(() => lambdaArgDecoder)),
type: decoders.constant('ident'),
tok: decoders.string,
})
const lambdaDecoder = decoders.object({
sibling: decoders.optional(astDecoder),
type: decoders.constant('lambda'),
params: decoders.optional(lambdaArgDecoder),
body: astDecoder,
})
const labelDecoder = decoders.object({
sibling: decoders.optional(astDecoder),
type: decoders.constant('label'),
tok: decoders.string,
body: lambdaDecoder,
})
const start = new Date().getTime();
astDecoder.verify(value);
const end = new Date().getTime();
console.log(`run time: ${(end - start) / 1000} seconds`)
The fast version of this decoder commonly runs in 0.002 seconds while the slow verson runs in ~13 seconds on my machine.