From 4e13289a5803939e34cb1094331151c7fd675e66 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Tue, 3 Jan 2017 15:17:55 -0800 Subject: [PATCH 1/4] More discussion of homomorphic mapped types Especially the practical uses and comparison to non-homomorphic types. --- pages/Advanced Types.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/pages/Advanced Types.md b/pages/Advanced Types.md index 981b900a9..3720906f3 100644 --- a/pages/Advanced Types.md +++ b/pages/Advanced Types.md @@ -834,6 +834,10 @@ type Partial = { [P in keyof T]?: T[P] } In these examples, the properties list is `keyof T` and the resulting type is some variant of `T[P]`. This is a good template for any general use of mapped types. +That's because this kind of transformation is homomorphic, which means that the mapping applies to every property of `T` and no others. +The compiler knows that it can copy all the existing property modifiers before adding any new ones. +For example, if `Person.name` were readonly, `Partial.name` would be readonly and optional. + Here's one more example, in which `T[P]` is wrapped in a `Proxy` class: ```ts @@ -861,6 +865,16 @@ type Record = { } ``` +`Readonly` and `Partial` are homomorphic whereas `Pick` and `Record` are not. +One clue that `Pick` and `Record` are not homomorphic is that they both take a union of property names: + +```ts +type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string> +type PersonJustName = Pick; +``` + +These non-homomorphic types are essentially creating new properties (even though `Pick` uses `Person` as a source), so they don't copy property modifiers from anywhere; if `Person.name` were readonly, `Pick.name` would not be readonly. + ## Inference from mapped types Now that you know how to wrap the properties of a type, the next thing you'll want to do is unwrap them. @@ -878,8 +892,5 @@ function unproxify(t: Proxify): T { let originalProps = unproxify(proxyProps); ``` -Note that this unwrapping inference works best on *homomorphic* mapped types. -Homomorphic mapped types are mapped types that iterate over every property of some type, and only those properties: `{ [P in keyof T]: X }`. -In the examples above, `Nullable` and `Partial` are homomorphic whereas `Pick` and `Record` are not. -One clue is that `Pick` and `Record` both take a union of property names in addition to a source type, which they use instead of `keyof T`. +Note that this unwrapping inference works best on homomorphic mapped types. If the mapped type is not homomorphic you might have to explicitly give a type parameter to your unwrapping function. From 8938679c912a770f7156c799e0017f496905a78f Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Tue, 3 Jan 2017 16:29:49 -0800 Subject: [PATCH 2/4] Address PR comments --- pages/Advanced Types.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/pages/Advanced Types.md b/pages/Advanced Types.md index 3720906f3..ea91aee45 100644 --- a/pages/Advanced Types.md +++ b/pages/Advanced Types.md @@ -834,7 +834,7 @@ type Partial = { [P in keyof T]?: T[P] } In these examples, the properties list is `keyof T` and the resulting type is some variant of `T[P]`. This is a good template for any general use of mapped types. -That's because this kind of transformation is homomorphic, which means that the mapping applies to every property of `T` and no others. +That's because this kind of transformation is [homomorphic](https://en.wikipedia.org/wiki/Homomorphism), which means that the mapping applies only to properties of `T` and no others. The compiler knows that it can copy all the existing property modifiers before adding any new ones. For example, if `Person.name` were readonly, `Partial.name` would be readonly and optional. @@ -865,15 +865,14 @@ type Record = { } ``` -`Readonly` and `Partial` are homomorphic whereas `Pick` and `Record` are not. -One clue that `Pick` and `Record` are not homomorphic is that they both take a union of property names: +`Readonly`, `Partial` and `Pick` are homomorphic whereas `Record` is not. +One clue that `Record` is not homomorphic is that it doesn't take an input type to copy properties from: ```ts type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string> -type PersonJustName = Pick; ``` -These non-homomorphic types are essentially creating new properties (even though `Pick` uses `Person` as a source), so they don't copy property modifiers from anywhere; if `Person.name` were readonly, `Pick.name` would not be readonly. +Non-homomorphic types are essentially creating new properties, so they can't copy property modifiers from anywhere. ## Inference from mapped types @@ -892,5 +891,5 @@ function unproxify(t: Proxify): T { let originalProps = unproxify(proxyProps); ``` -Note that this unwrapping inference works best on homomorphic mapped types. -If the mapped type is not homomorphic you might have to explicitly give a type parameter to your unwrapping function. +Note that this unwrapping inference only works on homomorphic mapped types. +If the mapped type is not homomorphic you will have to explicitly give a type parameter to your unwrapping function. From 5eb56c0633d3228362b463aaf06120ec4269d42c Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 4 Jan 2017 01:09:41 -0500 Subject: [PATCH 3/4] Update Advanced Types.md --- pages/Advanced Types.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pages/Advanced Types.md b/pages/Advanced Types.md index ea91aee45..49870ad3e 100644 --- a/pages/Advanced Types.md +++ b/pages/Advanced Types.md @@ -777,7 +777,7 @@ In a mapped type, the new type transforms each property in the old type in the s For example, you can make all properties of a type `readonly` or optional. Here are a couple of examples: -```ts +```ts type Readonly = { readonly [P in keyof T]: T[P]; } @@ -892,4 +892,4 @@ let originalProps = unproxify(proxyProps); ``` Note that this unwrapping inference only works on homomorphic mapped types. -If the mapped type is not homomorphic you will have to explicitly give a type parameter to your unwrapping function. +If the mapped type is not homomorphic you'll have to give an explicit type parameter to your unwrapping function. From 4698ce438cc35e2dffa097d3719230ec56662c83 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Wed, 4 Jan 2017 08:28:12 -0800 Subject: [PATCH 4/4] Fix lint: Remove extraneous character --- pages/Advanced Types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/Advanced Types.md b/pages/Advanced Types.md index 49870ad3e..c289c7434 100644 --- a/pages/Advanced Types.md +++ b/pages/Advanced Types.md @@ -777,7 +777,7 @@ In a mapped type, the new type transforms each property in the old type in the s For example, you can make all properties of a type `readonly` or optional. Here are a couple of examples: -```ts +```ts type Readonly = { readonly [P in keyof T]: T[P]; }