Skip to content

Instantly share code, notes, and snippets.

@karlhorky
Last active November 1, 2022 10:20
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save karlhorky/99ecb289504dd96d1510ee3961aeeac0 to your computer and use it in GitHub Desktop.
Save karlhorky/99ecb289504dd96d1510ee3961aeeac0 to your computer and use it in GitHub Desktop.
Flow: DeepReadOnly utility function
// Source: https://github.com/facebook/flow/issues/5844
// $ReadOnly doesn't recursively apply invariance to nested object types, and there
// doesn't seem to be any way to do this besides making a separate copy of obj A
// that is invariant. I still want the variant A around in case I want to mutate it
// in certain contexts.
// It would be nice to have some way to use an object as if it's fully, deeply
// invariant without needing to declare a separate variant and invariant type for it.
// Solution 1 (https://github.com/facebook/flow/issues/5844#issuecomment-397775631):
type PrimitiveValue = boolean | number | string;
type PrimitiveValueArray = Array<boolean> | Array<number> | Array<string>;
type PrimitiveNonValue = null | void;
type PrimitiveNonValueArray = Array<null> | Array<void>;
type Primitive = PrimitiveNonValue | PrimitiveValue;
type PrimitiveArray = PrimitiveValueArray | PrimitiveNonValueArray;
type ReadOnlyArrayFrom<T: PrimitiveArray> = $ReadOnlyArray<$ElementType<T, number>>;
type DeepReadOnly<T0: Object> = $ReadOnly<
$ObjMap<
T0,
// prettier-ignore
& (<T1: Primitive | Function>(T1) => T1)
& (<T1: Object>(T1) => DeepReadOnly<T1>)
& (<T1: PrimitiveArray>(T1) => ReadOnlyArrayFrom<T1>)
& (<T1: Array<Object>>(T1) => DeepReadOnlyArrayFrom<T1>)
>
>;
type DeepReadOnlyArray<T: Object> = $ReadOnlyArray<DeepReadOnly<T>>;
type DeepReadOnlyArrayFrom<T: Array<Object>> = DeepReadOnlyArray<
$ElementType<T, number>
>;
// Solution 2 (https://github.com/facebook/flow/issues/5844#issuecomment-407169272):
// I kept thinking on this while I was having lunch and I came up with this other
// minimal version that addresses all the cases with the exception of non standard
// types (unions, intersections, etc) where will be left untouched, so these ones
// needs to be set up manually, but for all the other cases (object, arrays, and
// nested), the following utility type makes them deep read only by just wrapping
// them with DeepReadOnly (it works for arrays and for objects as well!):
// Note:
// We are using $ReadOnlyArray to refine Array generics to allow them to be used
// also with tuples
type ArrayValue<T: $ReadOnlyArray<any>> = $ElementType<T, number>;
type ReadOnlyArrayFrom<T: $ReadOnlyArray<any>> = $ReadOnlyArray<ArrayValue<T>>;
type ToReadOnly =
& (<T: Object>(T) => DeepReadOnlyObject<T>)
& (<T: $ReadOnlyArray<any>>(T) => DeepReadOnlyArrayFrom<T>)
& (<T>(T) => T);
type DeepReadOnlyArrayFrom<T: $ReadOnlyArray<any>> = ReadOnlyArrayFrom<
$TupleMap<T, ToReadOnly>
>;
type DeepReadOnlyObject<T: Object> = $ReadOnly<$ObjMap<T, ToReadOnly>>;
// We need to do it this way and not use $Call<ToReadOnly, T> as in flow < 0.72
// overloaded functions were only correctly choosen when passing them to map utility
// types ($TupleMap or $ObjMap)
type DeepReadOnly<T> = ArrayValue<$TupleMap<[T], ToReadOnly>>;
Enjoy 😃 !
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment