First class mapped (folded, appended, traversed, etc.) types
Dieses Issue hat noch niemand übernommen.
Bewertung
- Schwierigkeit
- 5/5
- Geschätzter Aufwand
- Über eine Woche
- Anfängerfreundlichkeit
- 15/100
- Issue-Typ
- Feature
- Klarheit
- Muss geklärt werden
- Aktivitätsstatus
- Veraltet
- Tech-Stack
- typescript
- Bereich
- compilers
Rechercherichtung
Der Issue nennt keine Dateien, Tests oder Einstiegspunkte. Beginne mit der Durchsicht der zugehörigen Vorschläge in #1213 und #6230 und untersuche anschließend die Implementierung des Typsystems sowie das bestehende Verhalten von mapped types. Ein konkreter Umfang, Abnahmekriterien und entsprechende Tests wären erforderlich, bevor dies für einen ersten Beitrag bereit ist.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
Search Terms
Mapped types, dependent typing, higher kinded types, type families
Suggestion
TypeScript currently has baked in support for mapped types, with special syntax for transforming type level maps and type level lists (tuples). Borrowing an example from #26063:
type Box<T> = { value: T }
type Boxified<T> = { [P in keyof T]: Box<T[P]> }
type A = [string, number]
type MappedA = Boxified<A> // => [Box<string>, Box<number>]
type B = { foo: string, bar: number }
type MappedB = Boxified<B> // => { foo: Box<string>, bar: Box<number> }
The logic for when a type is treated as a mappable type unto itself, and when it is simply treated as an heterogeneous map for the purposes of type mapping is built out of various edge cases.
The code above is analogous at the value level to writing the following:
const box = t => ({ value: t })
const boxified = t => MAP{ [p in t]: box(t[p]) } // Special syntax baked into the language
const a = ["hello", 1]
const mappedA = boxified(a) // => [{ value: "hello" }, { value: 1 }]
const b = { foo: "hello", bar: 1 }
const mappedB = boxified(b) // => { foo: { value: "hello" }, bar: { value: 1 } }
The MAP{ [p in t]: box(t[p]) } thing above stands out as something of an oddity; you don't usually see a "map operator" built directly into a language. Given a way to inductively break down and reconstruct data, users are perfectly capable of defining a map function for each type of data themselves:
// There are no linked lists in JS, so let's graft an algebra onto arrays
// for breaking them down and building them up recursively
const nil = []
const cons = x => xs => [x, ...xs]
const match = ({ nil, cons }) => xs =>
xs.length === 0 ? nil : cons(xs[0])(xs.slice(1))
// How to map over lists (ignoring the baked in `.map` operation from the prototype or a sec)
const map = f =>
match({
nil: nil,
cons: x => xs => cons(f(x))(map(f)(xs))
})
console.log(map(x => x * 2)([1, 2, 2, 3]))
// => [2, 4, 4, 6]
Similarly, for maps:
// Again, need to graft on an algebra for deconstructing and reconstructing objects
const empty = {}
const withKey = k => v => o => ({ [k]: v, ...o })
const match = ({ empty, withKey }) => o => {
const keys = Object.keys(o)
if (keys.length === 0) return empty
const [k, ...ks] = keys
const { [k]: v, ...o_ } = o
return withKey(k)(v)(o_)
}
// How to map over objects
const map = f =>
match({
empty: empty,
withKey: k => v => o => withKey(k)(f(v))(map(f)(o))
})
console.log(map(x => x * 2)({ foo: 1, bar: 2, baz: 2, quux: 3 }))
// => { foo: 2, bar: 4, baz: 4, quux: 6 }
The important thing to notice here is that once we have a way to break down and reconstruct the data recursively, we don't need any language-level features to express mapping over the data; both implementations of map require only the ability to define and apply functions, and for such functions to themselves be valid arguments and results (i.e. the features found in a minimal lambda calculus).
Moving back to the type level, we can promote our value level implementation of map to a type constructor in pseudo-TypeScript:
type Nil = []
type Cons<X, R> = [X, ...R]
type Map<F, A> =
A extends Nil ? Nil :
A extends Cons<infer X, infer R> ? Cons<F<X>, Map<F, R>> :
never
// NB: we need a type level bottom that inhabits all kinds (corresponding
// to a type error), never is not the right answer here
type Box<V> = { value: V }
type A = [number, string]
type MappedA = Map<Box, A> // => [{ value: number }, { value: string }]
Using the ability to abstract over type level functions and to apply them, i.e. utilizing the lambda calculus at the type level, the user is able to define "mapped types" without the need for this to be implemented in the language.
In fact, mapped types (i.e. structure preserving transformations), are just one useful tool for massaging types. It would similarly be useful for the user to be able to fold down a type level list into a type level union (using the binary | type constructor), or to fold down a type level list of object types into their intersection (using the binary & type constructor).
AFAICT the primary missing features here are HKTs (#1213) and recursive type constructors (#6230), but it's possible there are other features related to spread types, conditional types, and infer that are missing. This provides good motivation for implementing these features in a sound way; it would allow powerful abstractions like mapped types to be implemented and extended in libraries rather than in the core language.
Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript / JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. new expression-level syntax)
- Vorherrschende Sprache
- Go
- Sterne
- 111k
- Forks
- 14.4k
- Ø Merge
- 1 T. 19 Std.
- Gemergte PRs (30 T.)
- 117
Beitragsleitfaden
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus microsoft/TypeScript
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 88/100
microsoft/TypeScript#64322 · 2 Kommentare · 1 Reaktion · 2 zugewiesene Personen ·
-
Possible Improvement
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100
microsoft/TypeScript#64278 · 1 Kommentar · 1 Reaktion ·
-
Docs
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 70/100
microsoft/TypeScript#64118 · 1 Kommentar ·
-
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 88/100
microsoft/TypeScript#64094 ·
-
Docs
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 76/100
microsoft/TypeScript#63959 · 5 Kommentare ·
Alle Issues in microsoft/TypeScript
Ähnliche Issues
-
optimization optimization:agents-md-curator
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 86/100
githubnext/gh-aw-cao#13143 ·
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 84/100
blinklabs-io/bursa#904 ·
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 84/100
yanet-platform/ipfw-go#129 ·
-
bug confmap/provider/googlesecretmanagerprovider needs triage
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 72/100
open-telemetry/opentelemetry-collector-contrib#51273 · 2 Kommentare ·
-
bug: AI Gateway client filter lists "Unknown" twice when NULL and literal Unknown clients coexist Offenbug
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 90/100