Highlighting + typeof example

This commit is contained in:
Arseny Kapoulkine 2021-11-17 19:43:12 -08:00
parent fa1ad391b3
commit a1670da4bd

View file

@ -92,7 +92,7 @@ To define a record, you need to create the shape, which you can do using the new
Syntax A: Syntax A:
``` ```lua
record Person = { name: string, age: number } record Person = { name: string, age: number }
-- types can be omitted and default to any -- types can be omitted and default to any
@ -101,7 +101,7 @@ record Point = { x, y }
Syntax B: Syntax B:
``` ```lua
record Person(name: string, age: number) record Person(name: string, age: number)
-- types can be omitted and default to any -- types can be omitted and default to any
record Point(x, y) record Point(x, y)
@ -111,7 +111,7 @@ This defines `Point` simultaneously as a local variable that corresponds to the
The resulting shape table automatically is set up to be a valid record shape, but can still be modified by adding methods to it: The resulting shape table automatically is set up to be a valid record shape, but can still be modified by adding methods to it:
``` ```lua
function Point.__add(l, r) function Point.__add(l, r)
return Point(l.x + r.x, l.y + r.y) return Point(l.x + r.x, l.y + r.y)
end end
@ -132,13 +132,13 @@ To create a record, you need to use a record constructor. This is done using cal
Syntax A: Syntax A:
``` ```lua
local person = Person { name = "Bob", age = 42 } local person = Person { name = "Bob", age = 42 }
``` ```
Syntax B: Syntax B:
``` ```lua
local person = Person("Bob", 42) local person = Person("Bob", 42)
``` ```
@ -155,7 +155,7 @@ In variant B, it would probably make sense to require exact number of values to
Note that since records are first class objects, you can export or import a record through a module boundary in the usual way: Note that since records are first class objects, you can export or import a record through a module boundary in the usual way:
``` ```lua
local HR = require(path) local HR = require(path)
local r = HR.Person { name = "Bob", age = 42 } -- or HR.Person(1, 2) in variant B local r = HR.Person { name = "Bob", age = 42 } -- or HR.Person(1, 2) in variant B
``` ```
@ -164,19 +164,19 @@ local r = HR.Person { name = "Bob", age = 42 } -- or HR.Person(1, 2) in variant
At definition point, records can have generic arguments that can be used in the field type specification: At definition point, records can have generic arguments that can be used in the field type specification:
``` ```lua
record Point<V> = { x: V, y: V } record Point<V> = { x: V, y: V }
``` ```
When record names are used in type context, they use the standard generic instantiation syntax to specify the generic parameters: When record names are used in type context, they use the standard generic instantiation syntax to specify the generic parameters:
``` ```lua
local p: Point<number> local p: Point<number>
``` ```
When record names are used in record literals, they don't specify the generic parameters. This is to avoid complexity with parsing `<` in expression context: When record names are used in record literals, they don't specify the generic parameters. This is to avoid complexity with parsing `<` in expression context:
``` ```lua
local p: Point<number> = Point { x = 1, y = 2 } local p: Point<number> = Point { x = 1, y = 2 }
``` ```
@ -194,7 +194,7 @@ this is the one big issue we haven't yet resolved with metatable-based OOP for t
When `self` is explicit, the type needs to be specified manually, e.g. these definitions are equivalent: When `self` is explicit, the type needs to be specified manually, e.g. these definitions are equivalent:
``` ```lua
function Point:sum(): number function Point:sum(): number
return self.x + self.y return self.x + self.y
end end
@ -224,7 +224,7 @@ In the latter case, the type variable needs to carry a stable identifier, for ex
This allows to carry these types across modules via `require` while maintaining the stable identity; for example: This allows to carry these types across modules via `require` while maintaining the stable identity; for example:
``` ```lua
-- module A -- module A
export record R { ... } export record R { ... }
@ -244,7 +244,7 @@ local C = require(C)
In either case, the subtyping relationship between tables and records is structural and follows the is-a substitution principle. This is important because in code like this the inferred type is a table: In either case, the subtyping relationship between tables and records is structural and follows the is-a substitution principle. This is important because in code like this the inferred type is a table:
``` ```lua
function f(p) function f(p)
return p.x + p.y return p.x + p.y
end end
@ -252,7 +252,7 @@ end
... and we'd like to be able to call `f` with a record as an argument. This also allows us to use table types as interfaces that records comply to, for example this would typecheck: ... and we'd like to be able to call `f` with a record as an argument. This also allows us to use table types as interfaces that records comply to, for example this would typecheck:
``` ```lua
type Writer = { write: (Writer, string) -> () } type Writer = { write: (Writer, string) -> () }
record Printer = {} record Printer = {}
@ -300,7 +300,7 @@ way to model the world, we will be very careful in selecting features that we ad
Today it's possible to define objects using tables with metatables; this requires remembering a certain pattern that contains two magical lines, both relating to metatables: Today it's possible to define objects using tables with metatables; this requires remembering a certain pattern that contains two magical lines, both relating to metatables:
``` ```lua
local Point = {} local Point = {}
Point.__index = Point Point.__index = Point
@ -320,7 +320,7 @@ end
This gets tricky when types are involved. The code specified above doesn't typecheck in strict mode; in particular, it doesn't contain a definition of the type Point. This gets tricky when types are involved. The code specified above doesn't typecheck in strict mode; in particular, it doesn't contain a definition of the type Point.
It's tempting to fix it as follows: It's tempting to fix it as follows:
``` ```lua
type Point = { x: number, y: number } type Point = { x: number, y: number }
local Point = {} local Point = {}
@ -348,16 +348,36 @@ use of `.` vs `:` in certain type error scenarios.
Finally, note that the `Point` type here is incorrect as it doesn't contain the definitions of any methods so it's not useful externally. It's possible to use `typeof` like this: Finally, note that the `Point` type here is incorrect as it doesn't contain the definitions of any methods so it's not useful externally. It's possible to use `typeof` like this:
``` ```lua
type Point = typeof(Point.new(0, 0)) type Point = typeof(Point.new(0, 0))
``` ```
... but this doesn't always work due to complex issues with toposort in real-world code, is not intuitive, requires a specific non-intuitive order of declarations, makes it hard ... but this doesn't always work due to complex issues with toposort in real-world code, is not intuitive, requires a specific non-intuitive order of declarations, makes it hard
to specify the exact shape of the fields, and is even more difficult for generic code. to specify the exact shape of the fields, and is even more difficult for generic code. For this simple example it does work, and along with `self` tweak this results in the following
type-safe code:
```lua
local Point = {}
Point.__index = Point
function Point.new(x: number, y: number): Point
return setmetatable({x = x, y = y}, Point)
end
type Point = typeof(Point.new(0, 0))
function Point.__add(l: Point, r: Point): Point
return Point.new(l.x + r.x, l.y + r.y)
end
function Point.sum(self: Point): number
return self.x + self.y
end
```
Records solve all of these issues without requiring complex workarounds and result in code that is easier to read and reason about, and easier to teach: Records solve all of these issues without requiring complex workarounds and result in code that is easier to read and reason about, and easier to teach:
``` ```lua
record Point = { x: number, y: number } record Point = { x: number, y: number }
function Point.new(x: number, y: number): Point function Point.new(x: number, y: number): Point