From 23e8948b735f0ea5479e8e37150ed23d29acd6b5 Mon Sep 17 00:00:00 2001 From: Peter Mikkelsen Date: Thu, 21 May 2026 13:02:15 +0200 Subject: [PATCH 01/19] Initial draft of updates to the .NET interface guide for v21.0 generics --- .../dotnet-classes/advanced-techniques.md | 218 +++++++++++++++--- 1 file changed, 183 insertions(+), 35 deletions(-) diff --git a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md index c84ab5e96c6..c47ac53bc03 100644 --- a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md +++ b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md @@ -159,40 +159,117 @@ For example, if MyClass is a .NET class wit ## Generics -The .NET interface supports creating concrete versions of _generic classes_, instantiating them, and calling generic methods. This is done by calling `43⌶` with a right argument of `632` (for more information on `43⌶`, see the _Dyalog APL Language Reference Guide_). Note that this I-beam will be replaced with something that is better integrated into the language in a future Dyalog version. +In .NET, a method, interface, or class can be _generic_, which means it it is a template or recipe for a _concrete_ method, interface or class. What makes them generic is that they have a list of type parameters, and the user must apply a matching number of type arguments to get a concrete version. In the case of methods, it is not always necessary to apply type arguments, as the .NET interface can sometimes perform [type inference](#type-inference) to deduce the type arguments from the types of the method arguments. -A generic class is a class that has type parameters, which must be given values to create a concrete version of the class. Similarly, a generic method has type parameters that must be specified before the method can be called. +The syntax used to apply type arguments to both methods, classes, and interfaces, is the square brackets, as in `G[T]` where `G` is the generic thing, and `T` is a .NET type, or a vector of .NET types, which themselves might be the result of applying types to a generic .NET class. -The result of `43⌶632` is a monadic operator, which is used to apply the type arguments of generic classes or methods. +The square bracket syntax means that working with generics in Dyalog APL and C# looks visually similar, except that C# uses angle brackets instead, as illustrated by the example below: + +```C# +// Instantiate a concrete version of a generic class in C# +new System.Collections.Generic.List(); + +// Call a concrete version of a generic method in C# +System.Decimal.CreateChecked(5); +``` + +And the corresponding APL +```apl +⍝ Instantiate a concrete version of a generic class in APL +⎕NEW System.Collections.Generic.List[System.Int32] + +⍝ Call a concrete version of a generic method in APL +System.Decimal.CreateChecked[System.Int32] 5 +``` ### Creating a Concrete Version of a Generic Class -The class System.Collections.Generic.List is a generic class with one type parameter, which is the type of the elements of the list. +The class System.Collections.Generic.List is a generic class with one type parameter, which is the type of the elements of the list. The display form of the type indicates that it is generic: -A concrete version of the List class can be created using `43⌶632`. For example, a list class that contains integers can be created as follows: ```apl - IntList←System.Collections.Generic.List(43⌶632)System.Int32 - IntList -(System.Collections.Generic.List`1[System.Int32]) + ⎕USING←'' + System.Collections.Generic.List +(System.Collections.Generic.List[T]) +``` + +A concrete version of the List class can be created using square brackets. For example, a list class that contains integers can be created as follows: +```apl + ⎕USING←'' + IntList←System.Collections.Generic.List[System.Int32] + IntList +(System.Collections.Generic.List[System.Int32]) ``` The shared members of the IntList class can then be accessed, and the class instantiated using `⎕NEW`. It is not necessary to give the constructed class a name. -The operations can also be combined and multiple type argument specified. For example: +The operations can also be combined and multiple type arguments specified. For example: ```apl - types←System.Char System.Int32 - ⎕NEW System.Collections.Generic.Dictionary (43⌶632) types + ⎕USING←'' + types←System.Char System.Int32 + ⎕NEW System.Collections.Generic.Dictionary[types] System.Collections.Generic.Dictionary`2[System.Char,System.Int32] +``` +Attempting to instantiate a generic class without the expected number of type arguments generates an error. For example: +```apl + ⎕USING←'' + ⎕NEW System.Collections.Generic.List +LENGTH ERROR: No overload of the type expects the given number of generic type arguments: 0 + ⎕NEW System.Collections.Generic.List + ∧ ``` -Attempting to instantiate a generic class without the expected number of type arguments generates an exception. For example: +Similarly, applying too many type arguments also results in an error: + ```apl - ⎕USING←'' - ⎕NEW System.Collections.Generic.List -EXCEPTION: Invalid number of generic type arguments applied (expected 1, got 0) - ⎕NEW System.Collections.Generic.List - ∧ + ⎕USING←'' + System.Collections.Generic.List[3⍴System.Int32] +LENGTH ERROR: No overload of the type expects the given number of generic type arguments: 3 + System.Collections.Generic.List[3⍴System.Int32] +``` +### Creating a Concrete Version of a Generic Interface + +Applying type arguments to generic interfaces looks just like with generic classes. The example below defines a function `IsBoolCollection`, which checks whether a given .NET type implements the concrete version `ICollection[Boolean]` of the generic `ICollection` interface, which is often implemented by data structures that act as collections of elements of a specific type. + +```apl + ⎕USING←'System' 'System.Collections.Generic' + + IsBoolCollection←{ICollection[Boolean]∊∊⎕CLASS ⍵} + + a←⎕NEW HashSet[Int32] + b←⎕NEW List[Boolean] + c←⎕NEW Dictionary[Int32 Boolean] + + IsBoolCollection¨a b c +0 1 0 +``` + +### Multiple Overloads of .NET Classes and Interfaces +Some .NET classes and interfaces have multiple overloads, varying in the number of generic type parameters. The display form of the type makes this clear, and the the .NET interface will automatically use the appropriate overload based on context. + +```apl + ⎕USING←'System' + ValueTuple +(System.ValueTuple) +(System.ValueTuple[T1]) +(System.ValueTuple[T1,T2]) +(System.ValueTuple[T1,T2,T3]) +(System.ValueTuple[T1,T2,T3,T4]) +(System.ValueTuple[T1,T2,T3,T4,T5]) +(System.ValueTuple[T1,T2,T3,T4,T5,T6]) +(System.ValueTuple[T1,T2,T3,T4,T5,T6,T7]) +(System.ValueTuple[T1,T2,T3,T4,T5,T6,T7,TRest]) +``` + +The ValueTuple class has one non-generic overload, and 8 generic overloads. + +```apl + ⎕USING←'System' + ValueTuple[Int32] ⍝ Create concrete version of overload with 1 generic parameter +(System.ValueTuple[System.Int32]) + + ValueTuple[Int32 Boolean] ⍝ Create concrete version of overload with 2 generic parameters +(System.ValueTuple[System.Int32,System.Boolean]) ``` ### Calling a Generic Method @@ -206,34 +283,105 @@ System.Decimal CreateChecked[TOther](TOther) The CreateChecked function has one type parameter, shown in square brackets, and one regular parameter, shown in parentheses. -The generic type argument can be applied using `43⌶632`. When the monadic operator `43⌶632` is run with a .NET method as its left argument, and a scalar type or a vector of .NET types as its right argument, the result is a derived function that calls the specified .NET method with both the generic type arguments and regular arguments applied. For example: +The generic type argument can be applied using square brackets, and the result is concrete version of the generic method. The method can either be given a name, or evaluated directly. The display form indicates that the type parameters have been replaced by a concrete type. + ```apl - fn←System.Decimal.CreateChecked (43⌶632) System.Int32 - fn 123 -123 + ⎕USING←'System' + fn←Decimal.CreateChecked[Int32] + fn +System.Decimal CreateChecked[Int32](Int32) + fn 10 +10 + Decimal.CreateChecked[Int32] 50 +50 ``` -Attempting to call a method without specifying the type argument generates an exception. For example: +If a generic method has overloads with different numbers of type parameters, apply type arguments will narrow down the list of overloads which are applicable, as in the example below where only one overload expects a single type argument. + ```apl - System.Decimal.CreateChecked 123 -EXCEPTION: Invalid number of generic type arguments applied (expected 1, got 0) - System.Decimal.CreateChecked 123 - ∧ + ⎕USING←'System' + ValueTuple.Create +System.ValueTuple Create() +System.ValueTuple`1[T1] Create[T1](T1) +System.ValueTuple`2[T1,T2] Create[T1,T2](T1, T2) +System.ValueTuple`3[T1,T2,T3] Create[T1,T2,T3](T1, T2, T3) +... + + ValueTuple.Create[Boolean] +System.ValueTuple`1[System.Boolean] Create[Boolean](Boolean) ``` -### Calling a Niladic Generic Method +.NET methods with only a single overload which expects no arguments, are usually imported into APL as niladic functions, but when they are generic, they are imported as monadic functions so that the type arguments can be applied. -.NET methods that only export overloads with zero parameters are exported as niladic functions, which means they cannot be passed as an operand to `43⌶632` without being evaluated. To accomodate this, `43⌶632` also accepts a character vector as its left argument. In this case, it runs the .NET method with that name, with the generic type arguments applied. For example: ```apl - r←'System.Array.Empty'(43⌶632)System.Int32 - r≡⍬ + ⎕USING←'System' + Array.Empty +T[] Empty[T]() + + Array.Empty[Int32] +Int32[] Empty[Int32]() + + r←Array.Empty[Int32] ⍬ + r≡⍬ 1 ``` -Attempting to evaluate the niladic function without calling `43⌶632` generates an exception. For example: +Applying an incorrect number of type arguments to a method will generate an error: + ```apl - System.Array.Empty -EXCEPTION: Invalid number of generic type arguments applied (expected 1, got 0) - System.Array.Empty - ∧ + ⎕USING←'System' + Decimal.CreateChecked 50 +LENGTH ERROR: No overload of the method expects the given number of generic type arguments: 0 + Decimal.CreateChecked 50 + ∧ + + ValueTuple.Create[10⍴Int32] +LENGTH ERROR: No overload of the method expects the given number of generic type arguments: 10 + ValueTuple.Create[10⍴Int32] + ∧ ``` + +However, in some cases [type inference](#type-inference) can take place, and no error will be generated. + +#### Type Inference + +If the arguments to a generic method have a concrete .NET type, then their type information might be enough for the .NET bridge to unambiguously select a method overload, and to automatically apply the needed type arguments. But if there is any doubt about the type, such as when the arguments are regular APL arrays, such as the scalar `0`, which can be converted into a number of different .NET types, type inference will not take place. + +``` + ⎕USING←'System' 'System.Threading.Tasks' + Task.FromResult +System.Threading.Tasks.Task`1[TResult] FromResult[TResult](TResult) + + Task.FromResult 123 +LENGTH ERROR: No overload of the method expects the given number of generic type arguments: 0 + Task.FromResult 123 + ∧ + + ⍝ Explicitly apply type arguments + Task.FromResult[Int128] 123 +System.Threading.Tasks.Task`1[System.Int128] + + ⍝ Explicitly apply type arguments, but pass in a .NET object + i128←Int128.Parse ⊂'123' + Task.FromResult[Int128] i128 +System.Threading.Tasks.Task`1[System.Int128] + + ⍝ Let the bridge infer the type argument from argument's .NET type + Task.FromResult i128 +System.Threading.Tasks.Task`1[System.Int128] +``` + +Type inference can save the programmer from doing some typing which feels unnecessary, as shown in the last lines of the example above, but it is still allowed to manually apply type arguments. + +If the user has [specified an overload](#specifying-overloads), the type information is taken into account, which means another way of doing the above would be: + +```apl + ⎕USING←'System' 'System.Threading.Tasks' + Task.FromResult +System.Threading.Tasks.Task`1[TResult] FromResult[TResult](TResult) + + Task.FromResult⍠Int128⊢123 +System.Threading.Tasks.Task`1[System.Int128] +``` + +which works because we tell the .NET bridge that we want the overload that takes an Int128 as its argument, which makes the type parameter `TResult` *must* be Int128, and therefore there is no need to explicitly apply the type arguments using square brackets. \ No newline at end of file From 6e9cef3d6c99da4941d49ff6265e31c2769f884c Mon Sep 17 00:00:00 2001 From: Peter Mikkelsen Date: Tue, 26 May 2026 10:33:07 +0200 Subject: [PATCH 02/19] Small tweaks to .NET generics docs --- .../docs/dotnet-classes/advanced-techniques.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md index c47ac53bc03..4cba76dbb17 100644 --- a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md +++ b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md @@ -200,9 +200,9 @@ A concrete version of the List class can be (System.Collections.Generic.List[System.Int32]) ``` -The shared members of the IntList class can then be accessed, and the class instantiated using `⎕NEW`. It is not necessary to give the constructed class a name. +The shared members of the IntList class can then be accessed, and the class instantiated using `⎕NEW`. -The operations can also be combined and multiple type arguments specified. For example: +It is not necessary to give the constructed class a name before creating instances of it. Multiple type arguments can also be specified. For example: ```apl ⎕USING←'' types←System.Char System.Int32 @@ -296,7 +296,7 @@ System.Decimal CreateChecked[Int32](Int32) 50 ``` -If a generic method has overloads with different numbers of type parameters, apply type arguments will narrow down the list of overloads which are applicable, as in the example below where only one overload expects a single type argument. +If a generic method has overloads with different numbers of type parameters, applying type arguments will narrow down the list of overloads which are applicable, as in the example below where only one overload expects a single type argument. ```apl ⎕USING←'System' @@ -341,7 +341,7 @@ LENGTH ERROR: No overload of the method expects the given number of generic type ∧ ``` -However, in some cases [type inference](#type-inference) can take place, and no error will be generated. +However when no type arguments are applied, in some cases [type inference](#type-inference) can take place, and no error will be generated. #### Type Inference @@ -361,7 +361,7 @@ LENGTH ERROR: No overload of the method expects the given number of generic type Task.FromResult[Int128] 123 System.Threading.Tasks.Task`1[System.Int128] - ⍝ Explicitly apply type arguments, but pass in a .NET object + ⍝ Explicitly apply type arguments, and pass in a .NET object of that type i128←Int128.Parse ⊂'123' Task.FromResult[Int128] i128 System.Threading.Tasks.Task`1[System.Int128] @@ -384,4 +384,4 @@ System.Threading.Tasks.Task`1[TResult] FromResult[TResult](TResult) System.Threading.Tasks.Task`1[System.Int128] ``` -which works because we tell the .NET bridge that we want the overload that takes an Int128 as its argument, which makes the type parameter `TResult` *must* be Int128, and therefore there is no need to explicitly apply the type arguments using square brackets. \ No newline at end of file +which works because we tell the .NET bridge that we want the overload that takes an Int128 as its argument, which means the type parameter `TResult` *must* be Int128, and therefore there is no need to explicitly apply the type arguments using square brackets. \ No newline at end of file From 9d6c360129f7bf01f993ddf7159ed5f233694475 Mon Sep 17 00:00:00 2001 From: Peter Mikkelsen Date: Wed, 27 May 2026 12:21:58 +0200 Subject: [PATCH 03/19] Fix "it it" typo --- .../docs/dotnet-classes/advanced-techniques.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md index 4cba76dbb17..2fed5f793c2 100644 --- a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md +++ b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md @@ -159,7 +159,7 @@ For example, if MyClass is a .NET class wit ## Generics -In .NET, a method, interface, or class can be _generic_, which means it it is a template or recipe for a _concrete_ method, interface or class. What makes them generic is that they have a list of type parameters, and the user must apply a matching number of type arguments to get a concrete version. In the case of methods, it is not always necessary to apply type arguments, as the .NET interface can sometimes perform [type inference](#type-inference) to deduce the type arguments from the types of the method arguments. +In .NET, a method, interface, or class can be _generic_, which means it is a template or recipe for a _concrete_ method, interface or class. What makes them generic is that they have a list of type parameters, and the user must apply a matching number of type arguments to get a concrete version. In the case of methods, it is not always necessary to apply type arguments, as the .NET interface can sometimes perform [type inference](#type-inference) to deduce the type arguments from the types of the method arguments. The syntax used to apply type arguments to both methods, classes, and interfaces, is the square brackets, as in `G[T]` where `G` is the generic thing, and `T` is a .NET type, or a vector of .NET types, which themselves might be the result of applying types to a generic .NET class. From 67d46043282806db0bc189aa7c110053abee3c6c Mon Sep 17 00:00:00 2001 From: Peter Mikkelsen Date: Wed, 27 May 2026 13:35:16 +0200 Subject: [PATCH 04/19] Apply suggestions from code review Some suggestions seems to have been created in such a way that they suggest replacing a big chunk of text with an updated version of only the last bit. I will commit those suggestions manually. Co-authored-by: johndaintree --- .../docs/dotnet-classes/advanced-techniques.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md index 2fed5f793c2..30b70c7d39e 100644 --- a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md +++ b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md @@ -161,9 +161,9 @@ For example, if MyClass is a .NET class wit In .NET, a method, interface, or class can be _generic_, which means it is a template or recipe for a _concrete_ method, interface or class. What makes them generic is that they have a list of type parameters, and the user must apply a matching number of type arguments to get a concrete version. In the case of methods, it is not always necessary to apply type arguments, as the .NET interface can sometimes perform [type inference](#type-inference) to deduce the type arguments from the types of the method arguments. -The syntax used to apply type arguments to both methods, classes, and interfaces, is the square brackets, as in `G[T]` where `G` is the generic thing, and `T` is a .NET type, or a vector of .NET types, which themselves might be the result of applying types to a generic .NET class. +The syntax used to apply type arguments to methods, classes, and interfaces, is square brackets, for example `G[T]` where `G` is the generic entity, and `T` is a .NET type, or a vector of .NET types. These types themselves might be the result of applying types to a generic .NET class. -The square bracket syntax means that working with generics in Dyalog APL and C# looks visually similar, except that C# uses angle brackets instead, as illustrated by the example below: +The square bracket syntax means that working with generics in Dyalog APL and C# looks visually similar, except that C# uses angle brackets, as illustrated by the example below: ```C# // Instantiate a concrete version of a generic class in C# @@ -283,7 +283,7 @@ System.Decimal CreateChecked[TOther](TOther) The CreateChecked function has one type parameter, shown in square brackets, and one regular parameter, shown in parentheses. -The generic type argument can be applied using square brackets, and the result is concrete version of the generic method. The method can either be given a name, or evaluated directly. The display form indicates that the type parameters have been replaced by a concrete type. +The generic type argument can be applied using square brackets, and the result is a concrete version of the generic method. The method can either be given a name, or evaluated directly. The display form indicates that the type parameters have been replaced to form a concrete function. ```apl ⎕USING←'System' @@ -371,7 +371,7 @@ System.Threading.Tasks.Task`1[System.Int128] System.Threading.Tasks.Task`1[System.Int128] ``` -Type inference can save the programmer from doing some typing which feels unnecessary, as shown in the last lines of the example above, but it is still allowed to manually apply type arguments. +Type inference can save the programmer from doing unnecessary typing, as shown in the last lines of the example above, but it is still allowed to manually apply type arguments. If the user has [specified an overload](#specifying-overloads), the type information is taken into account, which means another way of doing the above would be: From 51bf80c47a8ddaf353c3d813204328a5ee95c1ec Mon Sep 17 00:00:00 2001 From: Peter Mikkelsen Date: Wed, 27 May 2026 13:43:02 +0200 Subject: [PATCH 05/19] More suggestions from review --- .../docs/dotnet-classes/advanced-techniques.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md index 30b70c7d39e..be9f635726e 100644 --- a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md +++ b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md @@ -159,7 +159,7 @@ For example, if MyClass is a .NET class wit ## Generics -In .NET, a method, interface, or class can be _generic_, which means it is a template or recipe for a _concrete_ method, interface or class. What makes them generic is that they have a list of type parameters, and the user must apply a matching number of type arguments to get a concrete version. In the case of methods, it is not always necessary to apply type arguments, as the .NET interface can sometimes perform [type inference](#type-inference) to deduce the type arguments from the types of the method arguments. +In .NET, a method, interface, or class can be _generic_, which means that it is a template or recipe for a _concrete_ method, interface or class. What makes them generic is that they have a list of type parameters, and the user must apply a matching number of type arguments to create a concrete version. In the case of methods, it is not always necessary to apply type arguments, as the .NET interface can sometimes perform [type inference](#type-inference) to deduce the type arguments from the types of the method arguments. The syntax used to apply type arguments to methods, classes, and interfaces, is square brackets, for example `G[T]` where `G` is the generic entity, and `T` is a .NET type, or a vector of .NET types. These types themselves might be the result of applying types to a generic .NET class. @@ -245,7 +245,7 @@ Applying type arguments to generic interfaces looks just like with generic class ``` ### Multiple Overloads of .NET Classes and Interfaces -Some .NET classes and interfaces have multiple overloads, varying in the number of generic type parameters. The display form of the type makes this clear, and the the .NET interface will automatically use the appropriate overload based on context. +Some .NET classes and interfaces have multiple overloads, varying in the number of generic type parameters. The display form of the type makes this clear, and the .NET interface will automatically use the appropriate overload based on context. ```apl ⎕USING←'System' @@ -345,7 +345,7 @@ However when no type arguments are applied, in some cases [type inference](#type #### Type Inference -If the arguments to a generic method have a concrete .NET type, then their type information might be enough for the .NET bridge to unambiguously select a method overload, and to automatically apply the needed type arguments. But if there is any doubt about the type, such as when the arguments are regular APL arrays, such as the scalar `0`, which can be converted into a number of different .NET types, type inference will not take place. +If the arguments to a generic method have a concrete .NET type, then their type information might be enough for the .NET bridge to unambiguously select a method overload, and to automatically apply the needed type arguments. But if there is any doubt about the type, such as when the arguments are regular APL arrays, for example the scalar `0`, which can be converted into a number of different .NET types, type inference will not take place. ``` ⎕USING←'System' 'System.Threading.Tasks' From 2d6e6293d26cc205d25468049ac0099771395bb5 Mon Sep 17 00:00:00 2001 From: Peter Mikkelsen Date: Wed, 27 May 2026 14:03:48 +0200 Subject: [PATCH 06/19] Change DMX message and update the examples --- .../docs/dotnet-classes/advanced-techniques.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md index be9f635726e..56d16096c8f 100644 --- a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md +++ b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md @@ -214,7 +214,7 @@ Attempting to instantiate a generic class without the expected number of type ar ```apl ⎕USING←'' ⎕NEW System.Collections.Generic.List -LENGTH ERROR: No overload of the type expects the given number of generic type arguments: 0 +LENGTH ERROR: No overload of the type expects the given number (0) of generic type arguments ⎕NEW System.Collections.Generic.List ∧ ``` @@ -224,7 +224,7 @@ Similarly, applying too many type arguments also results in an error: ```apl ⎕USING←'' System.Collections.Generic.List[3⍴System.Int32] -LENGTH ERROR: No overload of the type expects the given number of generic type arguments: 3 +LENGTH ERROR: No overload of the type expects the given number (3) of generic type arguments System.Collections.Generic.List[3⍴System.Int32] ``` ### Creating a Concrete Version of a Generic Interface @@ -331,12 +331,12 @@ Applying an incorrect number of type arguments to a method will generate an erro ```apl ⎕USING←'System' Decimal.CreateChecked 50 -LENGTH ERROR: No overload of the method expects the given number of generic type arguments: 0 +LENGTH ERROR: No overload of the method expects the given number (0) of generic type arguments Decimal.CreateChecked 50 ∧ ValueTuple.Create[10⍴Int32] -LENGTH ERROR: No overload of the method expects the given number of generic type arguments: 10 +LENGTH ERROR: No overload of the method expects the given number (10) of generic type arguments ValueTuple.Create[10⍴Int32] ∧ ``` @@ -353,7 +353,7 @@ If the arguments to a generic method have a concrete .NET type, then their type System.Threading.Tasks.Task`1[TResult] FromResult[TResult](TResult) Task.FromResult 123 -LENGTH ERROR: No overload of the method expects the given number of generic type arguments: 0 +LENGTH ERROR: No overload of the method expects the given number (0) of generic type arguments Task.FromResult 123 ∧ From ac66b93f394fd885f16c2984183b72cd6c74151a Mon Sep 17 00:00:00 2001 From: Peter Mikkelsen Date: Mon, 22 Jun 2026 08:54:51 +0200 Subject: [PATCH 07/19] Update dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md Co-authored-by: FionaDyalog --- .../docs/dotnet-classes/advanced-techniques.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md index 56d16096c8f..2ffa3f5e0fe 100644 --- a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md +++ b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md @@ -159,7 +159,7 @@ For example, if MyClass is a .NET class wit ## Generics -In .NET, a method, interface, or class can be _generic_, which means that it is a template or recipe for a _concrete_ method, interface or class. What makes them generic is that they have a list of type parameters, and the user must apply a matching number of type arguments to create a concrete version. In the case of methods, it is not always necessary to apply type arguments, as the .NET interface can sometimes perform [type inference](#type-inference) to deduce the type arguments from the types of the method arguments. +In .NET, a method, interface, or class can be _generic_, which means that it is a template or recipe for a _concrete_ method, interface, or class. What makes them generic is that they have a list of _type parameters_; the user must apply a matching number of _type arguments_ to create a concrete version. In the case of methods, it is not always necessary to apply type arguments, as the .NET interface can sometimes perform [type inference](#type-inference) to deduce the type arguments from the types of the method arguments. The syntax used to apply type arguments to methods, classes, and interfaces, is square brackets, for example `G[T]` where `G` is the generic entity, and `T` is a .NET type, or a vector of .NET types. These types themselves might be the result of applying types to a generic .NET class. From 6fe1140558bfb63f757033bab2f638abec1f879a Mon Sep 17 00:00:00 2001 From: Peter Mikkelsen Date: Mon, 22 Jun 2026 08:55:39 +0200 Subject: [PATCH 08/19] Update dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md Co-authored-by: FionaDyalog --- .../docs/dotnet-classes/advanced-techniques.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md index 2ffa3f5e0fe..171fec6fc71 100644 --- a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md +++ b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md @@ -161,7 +161,7 @@ For example, if MyClass is a .NET class wit In .NET, a method, interface, or class can be _generic_, which means that it is a template or recipe for a _concrete_ method, interface, or class. What makes them generic is that they have a list of _type parameters_; the user must apply a matching number of _type arguments_ to create a concrete version. In the case of methods, it is not always necessary to apply type arguments, as the .NET interface can sometimes perform [type inference](#type-inference) to deduce the type arguments from the types of the method arguments. -The syntax used to apply type arguments to methods, classes, and interfaces, is square brackets, for example `G[T]` where `G` is the generic entity, and `T` is a .NET type, or a vector of .NET types. These types themselves might be the result of applying types to a generic .NET class. +The syntax used to apply type arguments to methods, classes, and interfaces, is square brackets, for example `G[T]` where `G` is the generic entity, and `T` is a .NET type or a vector of .NET types. The types could be the result of applying types to a generic .NET class. The square bracket syntax means that working with generics in Dyalog APL and C# looks visually similar, except that C# uses angle brackets, as illustrated by the example below: From 6832227ec49938a37b38f2dde075f7f007fcab9d Mon Sep 17 00:00:00 2001 From: Peter Mikkelsen Date: Mon, 22 Jun 2026 08:55:54 +0200 Subject: [PATCH 09/19] Update dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md Co-authored-by: FionaDyalog --- .../docs/dotnet-classes/advanced-techniques.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md index 171fec6fc71..c66422863bb 100644 --- a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md +++ b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md @@ -173,7 +173,7 @@ new System.Collections.Generic.List(); System.Decimal.CreateChecked(5); ``` -And the corresponding APL +The corresponding APL is: ```apl ⍝ Instantiate a concrete version of a generic class in APL ⎕NEW System.Collections.Generic.List[System.Int32] From c13ac491abde5c88dfb08196331783e059db827d Mon Sep 17 00:00:00 2001 From: Peter Mikkelsen Date: Mon, 22 Jun 2026 08:56:52 +0200 Subject: [PATCH 10/19] Update dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md Co-authored-by: FionaDyalog --- .../docs/dotnet-classes/advanced-techniques.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md index c66422863bb..9cedde6ad7d 100644 --- a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md +++ b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md @@ -229,7 +229,7 @@ LENGTH ERROR: No overload of the type expects the given number (3) of generic ty ``` ### Creating a Concrete Version of a Generic Interface -Applying type arguments to generic interfaces looks just like with generic classes. The example below defines a function `IsBoolCollection`, which checks whether a given .NET type implements the concrete version `ICollection[Boolean]` of the generic `ICollection` interface, which is often implemented by data structures that act as collections of elements of a specific type. +Applying type arguments to generic interfaces closely resembles applying type arguments to generic classes. The example below defines a function `IsBoolCollection`. This checks whether a given .NET type implements the concrete version `ICollection[Boolean]` of the generic ICollection interface, which is often implemented by data structures that act as collections of elements of a specific type. ```apl ⎕USING←'System' 'System.Collections.Generic' From 30266123d72964c65f3c0d522d789b6ce3ad5586 Mon Sep 17 00:00:00 2001 From: Peter Mikkelsen Date: Mon, 22 Jun 2026 08:57:13 +0200 Subject: [PATCH 11/19] Update dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md Co-authored-by: FionaDyalog --- .../docs/dotnet-classes/advanced-techniques.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md index 9cedde6ad7d..37f7852158e 100644 --- a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md +++ b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md @@ -261,7 +261,7 @@ Some .NET classes and interfaces have multiple overloads, varying in the number (System.ValueTuple[T1,T2,T3,T4,T5,T6,T7,TRest]) ``` -The ValueTuple class has one non-generic overload, and 8 generic overloads. +The ValueTuple class has one non-generic overload and eight generic overloads. ```apl ⎕USING←'System' From 793b2698c8f05886cb2059db5bd37881bb4c98dc Mon Sep 17 00:00:00 2001 From: Peter Mikkelsen Date: Mon, 22 Jun 2026 09:03:55 +0200 Subject: [PATCH 12/19] Update dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md Co-authored-by: FionaDyalog --- .../docs/dotnet-classes/advanced-techniques.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md index 37f7852158e..c297c808cb9 100644 --- a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md +++ b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md @@ -283,7 +283,7 @@ System.Decimal CreateChecked[TOther](TOther) The CreateChecked function has one type parameter, shown in square brackets, and one regular parameter, shown in parentheses. -The generic type argument can be applied using square brackets, and the result is a concrete version of the generic method. The method can either be given a name, or evaluated directly. The display form indicates that the type parameters have been replaced to form a concrete function. +The generic type argument can be applied using square brackets, and the result is a concrete version of the generic method. The method can either be given a name or evaluated directly. The display form indicates that the type parameters have been replaced to form a concrete function. ```apl ⎕USING←'System' From 8607d542b3792ee790427f6e7c35fa53f18c8752 Mon Sep 17 00:00:00 2001 From: Peter Mikkelsen Date: Mon, 22 Jun 2026 09:05:59 +0200 Subject: [PATCH 13/19] Update dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md Co-authored-by: FionaDyalog --- .../docs/dotnet-classes/advanced-techniques.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md index c297c808cb9..d57d4e84cfc 100644 --- a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md +++ b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md @@ -296,7 +296,7 @@ System.Decimal CreateChecked[Int32](Int32) 50 ``` -If a generic method has overloads with different numbers of type parameters, applying type arguments will narrow down the list of overloads which are applicable, as in the example below where only one overload expects a single type argument. +If a generic method has overloads with different numbers of type parameters, applying type arguments will narrow down the list of overloads that are applicable. For example, when having only one overload means that a single type argument is expected: ```apl ⎕USING←'System' From 689548af4076fb736ad609a63c5653743d70b267 Mon Sep 17 00:00:00 2001 From: Peter Mikkelsen Date: Mon, 22 Jun 2026 09:06:24 +0200 Subject: [PATCH 14/19] Update dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md Co-authored-by: FionaDyalog --- .../docs/dotnet-classes/advanced-techniques.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md index d57d4e84cfc..f88c5d7f100 100644 --- a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md +++ b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md @@ -311,7 +311,7 @@ System.ValueTuple`3[T1,T2,T3] Create[T1,T2,T3](T1, T2, T3) System.ValueTuple`1[System.Boolean] Create[Boolean](Boolean) ``` -.NET methods with only a single overload which expects no arguments, are usually imported into APL as niladic functions, but when they are generic, they are imported as monadic functions so that the type arguments can be applied. +.NET methods with only a single overload that expects no arguments are usually imported into APL as niladic functions. However, when they are generic, they are imported as monadic functions so that the type arguments can be applied. For example: ```apl ⎕USING←'System' From 01430389863a8c640ec4f5e3c576fb0a22d3c0c9 Mon Sep 17 00:00:00 2001 From: Peter Mikkelsen Date: Mon, 22 Jun 2026 09:08:39 +0200 Subject: [PATCH 15/19] Update dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md Co-authored-by: FionaDyalog --- .../docs/dotnet-classes/advanced-techniques.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md index f88c5d7f100..4c0aa0bab5f 100644 --- a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md +++ b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md @@ -345,7 +345,7 @@ However when no type arguments are applied, in some cases [type inference](#type #### Type Inference -If the arguments to a generic method have a concrete .NET type, then their type information might be enough for the .NET bridge to unambiguously select a method overload, and to automatically apply the needed type arguments. But if there is any doubt about the type, such as when the arguments are regular APL arrays, for example the scalar `0`, which can be converted into a number of different .NET types, type inference will not take place. +If the arguments to a generic method have a concrete .NET type, then their type information might be sufficient for the .NET bridge to unambiguously select a method overload and to automatically apply the needed type arguments. If there is any ambiguity about the type, such as when the arguments are regular APL arrays (for example the scalar `0`, which can be converted into a number of different .NET types), type inference will not take place. For example: ``` ⎕USING←'System' 'System.Threading.Tasks' From 28cfcc920d0d7a7bea7527a3117158e8a207bf46 Mon Sep 17 00:00:00 2001 From: Peter Mikkelsen Date: Mon, 22 Jun 2026 09:09:02 +0200 Subject: [PATCH 16/19] Update dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md Co-authored-by: FionaDyalog --- .../docs/dotnet-classes/advanced-techniques.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md index 4c0aa0bab5f..66dd601b18a 100644 --- a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md +++ b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md @@ -371,7 +371,7 @@ System.Threading.Tasks.Task`1[System.Int128] System.Threading.Tasks.Task`1[System.Int128] ``` -Type inference can save the programmer from doing unnecessary typing, as shown in the last lines of the example above, but it is still allowed to manually apply type arguments. +Type inference can remove the need for additional code (as shown in the last lines of the example above), but manually applying type arguments is also permitted. If the user has [specified an overload](#specifying-overloads), the type information is taken into account, which means another way of doing the above would be: From 95d36271eed5eda7a0a685fb289a02a252b8f2f0 Mon Sep 17 00:00:00 2001 From: Peter Mikkelsen Date: Mon, 22 Jun 2026 09:09:22 +0200 Subject: [PATCH 17/19] Update dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md Co-authored-by: FionaDyalog --- .../docs/dotnet-classes/advanced-techniques.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md index 66dd601b18a..f70aec6af6c 100644 --- a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md +++ b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md @@ -373,7 +373,7 @@ System.Threading.Tasks.Task`1[System.Int128] Type inference can remove the need for additional code (as shown in the last lines of the example above), but manually applying type arguments is also permitted. -If the user has [specified an overload](#specifying-overloads), the type information is taken into account, which means another way of doing the above would be: +If the user has [specified an overload](#specifying-overloads), then the type information is taken into account. This means that an alternative way of coding the above would be: ```apl ⎕USING←'System' 'System.Threading.Tasks' From 81a15c4d8d7a698a16cb35e0b8673668fa6b2934 Mon Sep 17 00:00:00 2001 From: Peter Mikkelsen Date: Mon, 22 Jun 2026 09:09:54 +0200 Subject: [PATCH 18/19] Update dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md Co-authored-by: FionaDyalog --- .../docs/dotnet-classes/advanced-techniques.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md index f70aec6af6c..b169962907c 100644 --- a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md +++ b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md @@ -384,4 +384,4 @@ System.Threading.Tasks.Task`1[TResult] FromResult[TResult](TResult) System.Threading.Tasks.Task`1[System.Int128] ``` -which works because we tell the .NET bridge that we want the overload that takes an Int128 as its argument, which means the type parameter `TResult` *must* be Int128, and therefore there is no need to explicitly apply the type arguments using square brackets. \ No newline at end of file +This works because we tell the .NET bridge that we want the overload that takes an Int128 as its argument, which means the type parameter `TResult` *must* be Int128; it is, therefore, not necessary to explicitly apply the type arguments using square brackets. \ No newline at end of file From 6be12d600d1c6fa66e059ff6527aa99bba32bc49 Mon Sep 17 00:00:00 2001 From: Peter Mikkelsen Date: Mon, 22 Jun 2026 09:12:54 +0200 Subject: [PATCH 19/19] Apply suggestions from review --- .../docs/dotnet-classes/advanced-techniques.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md index 37f7852158e..b0d506a906d 100644 --- a/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md +++ b/dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md @@ -200,7 +200,7 @@ A concrete version of the List class can be (System.Collections.Generic.List[System.Int32]) ``` -The shared members of the IntList class can then be accessed, and the class instantiated using `⎕NEW`. +The shared members of the `IntList` class can then be accessed, and the class instantiated using `⎕NEW`. It is not necessary to give the constructed class a name before creating instances of it. Multiple type arguments can also be specified. For example: ```apl @@ -341,8 +341,6 @@ LENGTH ERROR: No overload of the method expects the given number (10) of generic ∧ ``` -However when no type arguments are applied, in some cases [type inference](#type-inference) can take place, and no error will be generated. - #### Type Inference If the arguments to a generic method have a concrete .NET type, then their type information might be enough for the .NET bridge to unambiguously select a method overload, and to automatically apply the needed type arguments. But if there is any doubt about the type, such as when the arguments are regular APL arrays, for example the scalar `0`, which can be converted into a number of different .NET types, type inference will not take place.