Skip to content

Commit 41224a6

Browse files
authored
Merge pull request #66 from KubaO/staging
Extend the documentation for generic types. Fixes issue #2
2 parents 1624f21 + 007bfc3 commit 41224a6

File tree

1 file changed

+235
-21
lines changed

1 file changed

+235
-21
lines changed

docs/Features/Language/Generics.md

Lines changed: 235 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,46 +7,260 @@ permalink: /Features/Language/Generics
77

88
# Generics
99

10-
Generics have basic support in methods and classes.
10+
> [!IMPORTANT]
11+
> Generics are syntactic sugar for copy-pasting code followed by a search-and-replace of type names.
12+
>
13+
> Everything that the generic syntax provides can be achieved without it by writing repetitive code.
1114
12-
## Generic Functions
15+
This repetition is error-prone and tedious, however, and thus the generic syntax keeps the code DRY[^1].
16+
17+
The generic syntax introduces *type parameters* / *type variables* whose *type-values* exist during compilation, as opposed to regular parameters and their values that exist during run-time only.
18+
19+
Procedures, **Class**es and **Type**s (UDTs) can be made generic.
20+
21+
> [!WARNING]
22+
> Generic **Type**s (UDTs) don't yet support member procedures (error TB5124).
23+
24+
## Generic Procedures
25+
26+
Syntax:
27+
28+
* **Definition**
29+
( **Function** | ... ) *name* **(Of** *type-variable-list* **)** **(** *parameter-list* **)** **As** *return-type*
30+
31+
* In detail:
32+
( **Function** | **Sub** | **Property** (**Get** | **Let** | **Set**) ) *name* **(Of** *type-var1* [ **,** *type-var2* ...]**)** **(** *parameter-list* **)** **As** *return-type*
33+
The *parameter-list* can reference any of the type variables, e.g.
34+
`Sub MyPrint(Of T)(ByVal file&, value As T)`
35+
36+
* **Invocation** or **Call Site**
37+
*name* [ **(Of** *type-argument-list* **)** ] [ **(** *argument-list* **)** ]
38+
39+
* In detail:
40+
*name* [ **(Of** *type-arg1* [ **,** *type-arg2* ] **)** ] [ **(** *argument-list* **)** ]
41+
The type variables from the definition's *parameter-list* are substituted with concrete or arguments types provided in the *argument-list*, unless provided explicitly as a type argument in the *type-argument-list*.
42+
The type variables that were not referenced in the *parameter-list* have to be provided in the *type-argument-list* as *type-arguments*.
43+
44+
In the definition, the *type-variable-list*, i.e. **(Of** *type-var* ... **)**, introduces genericity. The type variables (*type-var*) introduce identifiers of arbitrary types that can be referenced within:
45+
46+
- *parameter-list*,
47+
- *return-type*, and
48+
- the body of the procedure.
49+
50+
In the invocation, the *type-argument-list*, i.e. **(Of** *type-arg* ... **)**, is optional as needed to provide types arguments for those type variables that don't appear in the *parameter-list* of the definition. The type variables that are used within the *parameter-list* are assigned type values of the respective arguments at the call site *unless their values are explicitly provided* in the *type-argument-list*.
51+
52+
### Call site type arguments
53+
54+
Type variables that correspond to types that could be deduced from the call argument types must form a trailer of the *type-variable-list*:
55+
56+
```vb
57+
Sub MySub1(Of T, U, V)(argu As U, argv As V): End Sub
58+
MySub1(Of Long)(33%, 42%) ' Valid: deduced U, V = Integer
59+
MySub1(Of Long, Single)(33%, 42%) ' Valid: provided U = Single, deduced V = Integer
60+
MySub1(Of Long, Single, Double)(33%, 42%)' Valid: provided U = Single, provided V = Double
61+
MySub1(Of Long, , Double)(33%, 42%) ' Invalid: omitted deduced type must be trailing
62+
```
63+
64+
Thus, to suppress deduction, put the type variable in the type list *before* the non-deducible type parameters:
1365

1466
```vb
15-
Public Function TCast(Of T)(ByRef Expression As T) As T
16-
Return Expression
67+
' T must be provided, it won't be deduced
68+
Function MyFn1(Of T, U)(argu As T) As U: End Function
69+
MyFn1(Of Single, String)(10%) ' Valid: provided T = Single, U = String
70+
MyFn1(Of, String)(10%) ' Invalid: T is not trailing so it can't be omitted
71+
' Effectively, the definition of MyFn1
72+
' suppresses deduction of T
73+
```
74+
75+
Only the unused type variables may have their arguments omitted at positions *after the first* in the *type-variable-list*.:
76+
77+
```vb
78+
Sub MySub2(Of T, U, V)(argt As T, argv As V): End Sub
79+
Sub MySub3(Of U, V)(argv As V): End Sub
80+
81+
MySub2(Of Single, , Double)(1%, 2%) ' Valid: unused U can be omitted as it's not the first
82+
' in the type-parameter-list
83+
MySub3(Of, Single)(22%) ' Invalid: unused U can't be omitted as it's the first
84+
' variable in the type-parameter-list
85+
```
86+
87+
### Example 1
88+
89+
In this example, the invocations of the generic **First** and **Last** subs don't need to explicitly provide type argument values using the *type-argument-list*, i.e. **(Of** ... **)**, since they can be deduced from the argument types.
90+
91+
```vb
92+
Public Function First(Of T)(Array() As T) As T
93+
If IsArrayInitialized(Array) Then Return Array(LBound(Array))
1794
End Function
95+
96+
Public Function Last(Of T)(Array() As T) As T
97+
If IsArrayInitialized(Array) Then Return Array(UBound(Array))
98+
End Function
99+
100+
Sub Test()
101+
Dim data() As String = Array("A", "B", "C")
102+
Debug.Assert First(data) = "A"
103+
Debug.Assert Last(data) = "C"
104+
End Sub
18105
```
19106

20-
This could be used e.g. to return a `Date` typed variable with `TCast(Of Date)("2021-01-01")`
107+
Without the generic syntax, the procedure would have had to be written for every type *T* it's used on. In the example below, that would be `T=String` and `T=Integer`:
21108

22-
## Generic Classes
109+
```vb
110+
Public Function First(Array() As String) As String
111+
If IsArrayInitialized(Array) Then Return Array(LBound(Array))
112+
End Function
113+
114+
Public Function First(Array() As Integer) As Integer
115+
If IsArrayInitialized(Array) Then Return Array(LBound(Array))
116+
End Function
117+
118+
Sub Test()
119+
Dim strings() As String = Array("A", "B", "C")
120+
Dim ints() As Integer = Array(1, 2, 3)
121+
Debug.Assert First(strings) = "A" AndAlso First(ints) = 1
122+
End Sub
123+
```
23124

24-
A Class generic allows the type in methods throughout the class. The following example shows this to make a generic List class:
125+
### Example 2 with some type variables not appearing in the *parameter-list*
126+
127+
There are two common cases when a type variable might not appear in the *parameter-list*:
128+
129+
- when it is the *result-type*, and/or
130+
- when it is used in the body of the procedure.
131+
132+
The example below illustrates those possibilities:
133+
134+
```vb
135+
Public Function Caster(Of R, U, T)(value As T) As R
136+
Dim intermediate As U = CType(Of U)(value)
137+
Return CType(Of R)(intermediate)
138+
End Function
139+
140+
Sub Test()
141+
' Type T is deduced to be Single, from the argument 1.23!
142+
Debug.Assert Example(Of String, Integer)(1.23!) = "1"
143+
' Type T is explicitly provided as Double. The argument is cast to that type.
144+
Debug.Print Example(Of String, Integer, Double)(1.23!) = "1"
145+
End Sub
146+
```
147+
148+
The function **Caster** introduces three type variables within its scope:
149+
150+
- **T** is by default deduced from the type of the **value** argument, or can be provided on invocation,
151+
- **R** is the result type and must be provided on invocation,
152+
- **U** is a type used in the body of the function and must be provided on invocation.
153+
154+
> [!TIP]
155+
> The order of the type variables in the definition can be chosen so that the trailing variable(s) are used in the *parameter-list*. The type-values of those type variable can thus be omitted if the types inferred from the argument types at the call site are appropriate.
156+
157+
1. In the invocation `Example(Of String, Integer)(1.23!)`,
158+
*T* is deduced to be **Single**, *U* is provided and set to **Integer**, and **R** is provided and set to **String**.
159+
160+
2. In the invocation `Example(Of String, Integer, Double)(1.23!)`,
161+
*T* is provided and set to **Double**, *U* is provided and set to **Integer**, and *R* is provided and set to **String**.
162+
* First, the compiler will cast `1.23!` to the type of the formal parameter, that is to a **Double** `1.23#`.
163+
* Then, in the body of the function, the *value* is cast to **Integer** when it's assigned to **intermediate**.
164+
* Finally, also in the body, the **intermediate** is cast to the result type of **String**, and returned.
165+
166+
167+
## Generic Classes And UDTs
25168

169+
Syntax:
170+
171+
* **Definition**
172+
[ **Class** | ... ] *name* **(Of** *type-variable-list* **)**
173+
* In Detail:
174+
[ **Class** | **Type** ] *name* **(Of** *type-var1* [ **,** *type-var2* ... ] **)**
175+
* **Instantiation**
176+
*name* **(Of** *type-argument-list* **)**
177+
* In Detail:
178+
*name* **(Of** *type-arg1* [ **,** *type-arg2* ... ] **)**
179+
180+
The type variables (*type-var*) introduce identifiers of arbitrary types that can be referenced anywhere within the body of the class.
181+
182+
> [!IMPORTANT]
183+
> When instantiating generic classes and UDTs, **all of the type arguments** have to be provided.
184+
>
185+
> If they aren't, code generation errors and silent failures at runtime may occur.
186+
187+
### Example of correct and incorrect instantiation
188+
189+
```vb
190+
Class MyClass(Of T, U)
191+
Function DumpT%(value As T): Debug.Print value: End Function
192+
Function DumpU%(value As U): Debug.Print value: End Function
193+
End Class
194+
195+
Dim i As New MyClass(Of Integer) ' Invalid, U is not provided, silent error
196+
i.DumpT(12) ' Valid, uses T = Integer
197+
i.DumpU(12) ' Invalid, uses undefined U, causes a codegen/silent error
198+
199+
Dim j As New MyClass(Of Integer, Single) ' Correct instantiation
200+
j.DumpU(12) ' Valid, uses U = Single
26201
```
202+
203+
### Type-instances vs object-instances
204+
205+
A generic class enables substitution of type variables with type arguments provided in an instantiation. Every utterance of a generic class name with type arguments instantiates the generic class type into a regular class type.
206+
207+
> [!NOTE]
208+
> Compile Time: A generic class is instantiated by calling out its name with arguments.
209+
>
210+
> Run Time: Objects of those instantiated types can be created.
211+
212+
In the example below, two class types are instantiated: **MyClass**(**Integer**) and **MyClass**(**String**). This happens at compile time. No instances of **MyClass** are created at runtime, since both variables default to **Nothing**:
213+
214+
```vb
215+
Class MyClass(Of T) ' ...
216+
217+
Sub Test()
218+
Dim intVar As MyClass(Integer)
219+
Dim strVar As MyClass(String)
220+
Debug.Assert intVar Is Nothing AndAlso strVar Is Nothing
221+
End Sub
222+
```
223+
224+
### List Class Example
225+
226+
A Class generic allows the type in methods throughout the class. The following example shows this to make a generic List class:
227+
228+
```vb
27229
[COMCreatable(False)]
28230
Class List(Of T)
29-
Private src() As T
30-
Private c As Long
31-
Sub New(p() As T)
32-
src = p
231+
Private mData() As T
232+
233+
Sub New(preset() As T)
234+
mData = preset
33235
End Sub
236+
34237
[DefaultMember]
35-
Function GetAt(ByVal idx As Long) As T
36-
Return src(idx)
238+
Function GetAt(ByVal index&) As T
239+
Return mData(index)
37240
End Function
38-
Public Property Get Count() As Long
39-
Return c
40-
End Property
41241
End Class
242+
243+
Sub Test()
244+
Dim li As Any = New List(Of Integer)(Array(5, 6, 7))
245+
Debug.Assert li(0) = 5 AndAlso li(2) = 7
246+
End Sub
247+
42248
```
43249

44-
## Usage Example
250+
### List UDT Example
251+
252+
While generic UDTs don't support member procedures yet in twinBASIC, the data members are supported:
45253

46254
```vb
47-
Private Sub TestListGenericClass()
48-
Dim names As List(Of String) = New List(Of String)(Array("John", "Smith", "Kane", "Tessa", "Yoland", "Royce", "Samuel"))
49-
Dim s As String = names(0)
50-
Debug.Print s
255+
Type ListU(Of T)
256+
value() As T
257+
End Type
258+
259+
Sub Test()
260+
Dim lu As ListU(Of Long)
261+
ReDim lu.value(10)
262+
lu.value(0) = 5
51263
End Sub
52264
```
265+
266+
[^1]: DRY = Don't Repeat Yourself

0 commit comments

Comments
 (0)