mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-20 01:07:18 +03:00
daml2js: Memoize lazily constructed decoders (#6892)
Currently, if you have a record type `T` with a field of type, say, `Optional S` and you're decoding a list of type `[T]`, then the decoder for `S` has to be reconstructed for each element of the list. This is because the `lazy` combinator from the `json-type-validation` does not memoize the decoder it receives as a thunk. This PR adds a new combinator `lazyMemo` which behaves like `lazy` but also memoizes the decoder on its first invocation. All use sites of the old `lazy` combinator are then replaced with `lazyMemo`. We could consider upstreaming `lazyMemo` but I'm not sure how much effort this is given that `json-type-validation` seems to be in maintenance mode rather than active development. CHANGELOG_BEGIN CHANGELOG_END
This commit is contained in:
parent
a9fcf965ba
commit
11c4fe0727
@ -505,7 +505,7 @@ renderDecoder = \case
|
||||
"})"
|
||||
DecoderConstant c -> "jtv.constant(" <> renderDecoderConstant c <> ")"
|
||||
DecoderRef t -> snd (genType t) <> ".decoder()"
|
||||
DecoderLazy d -> "jtv.lazy(function () { return " <> renderDecoder d <> "; })"
|
||||
DecoderLazy d -> "damlTypes.lazyMemo(function () { return " <> renderDecoder d <> "; })"
|
||||
|
||||
data TypeDef
|
||||
= UnionDef T.Text [T.Text] [(T.Text, TypeRef)]
|
||||
|
@ -1,6 +1,6 @@
|
||||
// Copyright (c) 2020 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import { Optional, Text } from './index';
|
||||
import { Optional, Text, memo } from './index';
|
||||
|
||||
describe('@daml/types', () => {
|
||||
it('optional', () => {
|
||||
@ -22,3 +22,14 @@ describe('@daml/types', () => {
|
||||
expect(dict.decoder().run([null]).ok).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
test('memo', () => {
|
||||
let x = 0;
|
||||
const f = memo(() => {
|
||||
x += 1;
|
||||
return x;
|
||||
});
|
||||
expect(f()).toBe(1);
|
||||
expect(f()).toBe(1);
|
||||
expect(x).toBe(1);
|
||||
});
|
||||
|
@ -90,6 +90,32 @@ export const lookupTemplate = (templateId: string): Template<object> => {
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal Turn a thunk into a memoized version of itself. The memoized thunk
|
||||
* invokes the original thunk only on its first invocation and caches the result
|
||||
* for later uses. We use this to implement a version of `jtv.lazy` with
|
||||
* memoization.
|
||||
*/
|
||||
export function memo<A>(thunk: () => A): () => A {
|
||||
let memoized: () => A = () => {
|
||||
const cache = thunk();
|
||||
memoized = (): A => cache;
|
||||
return cache;
|
||||
};
|
||||
// NOTE(MH): Since we change `memoized` when the resultung thunk is invoked
|
||||
// for the first time, we need to return it "by reference". Thus, we return
|
||||
// a closure which contains a reference to `memoized`.
|
||||
return (): A => memoized();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal Variation of `jtv.lazy` which memoizes the computed decoder on its
|
||||
* first invocation.
|
||||
*/
|
||||
export function lazyMemo<A>(mkDecoder: () => jtv.Decoder<A>): jtv.Decoder<A> {
|
||||
return jtv.lazy(memo(mkDecoder));
|
||||
}
|
||||
|
||||
/**
|
||||
* The counterpart of DAML's `()` type.
|
||||
*/
|
||||
@ -211,7 +237,7 @@ export type List<T> = T[];
|
||||
* Companion object of the [[List]] type.
|
||||
*/
|
||||
export const List = <T>(t: Serializable<T>): Serializable<T[]> => ({
|
||||
decoder: (): jtv.Decoder<T[]> => jtv.lazy(() => jtv.array(t.decoder())),
|
||||
decoder: (): jtv.Decoder<T[]> => lazyMemo(() => jtv.array(t.decoder())),
|
||||
});
|
||||
|
||||
/**
|
||||
@ -279,7 +305,7 @@ class OptionalWorker<T> implements Serializable<Optional<T>> {
|
||||
constructor(private payload: Serializable<T>) { }
|
||||
|
||||
decoder(): jtv.Decoder<Optional<T>> {
|
||||
return jtv.oneOf(jtv.constant(null), jtv.lazy(() => this.innerDecoder()));
|
||||
return jtv.oneOf(jtv.constant(null), lazyMemo(() => this.innerDecoder()));
|
||||
}
|
||||
|
||||
private innerDecoder(): jtv.Decoder<OptionalInner<T>> {
|
||||
@ -323,7 +349,7 @@ export type TextMap<T> = { [key: string]: T };
|
||||
* Companion object of the [[TextMap]] type.
|
||||
*/
|
||||
export const TextMap = <T>(t: Serializable<T>): Serializable<TextMap<T>> => ({
|
||||
decoder: (): jtv.Decoder<TextMap<T>> => jtv.lazy(() => jtv.dict(t.decoder())),
|
||||
decoder: (): jtv.Decoder<TextMap<T>> => lazyMemo(() => jtv.dict(t.decoder())),
|
||||
});
|
||||
|
||||
// TODO(MH): `Map` type.
|
||||
|
Loading…
Reference in New Issue
Block a user