mirror of
https://github.com/luau-lang/luau.git
synced 2025-05-04 10:33:46 +01:00
Highlighting + typeof example
This commit is contained in:
parent
fa1ad391b3
commit
a1670da4bd
1 changed files with 38 additions and 18 deletions
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue