Skip to content

Commit 408e6c7

Browse files
committed
Finish draft on QME
1 parent 205ae95 commit 408e6c7

File tree

1 file changed

+51
-71
lines changed

1 file changed

+51
-71
lines changed

_drafts/2024-08-05-qualified-methods-for-ClojureCLR.md

Lines changed: 51 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
---
22
layout: post
3-
title: Qualfied methods -- for ClojureCLR
4-
date: 2024-08-05 00:00:00 -0500
3+
title: Qualified methods -- for ClojureCLR
4+
date: 2024-09-05 00:00:00 -0500
55
categories: general
66
---
77

8-
Clojure has introduced a new _qualified methods_ feature allows for Java methods to be passed to higher-order functions. This feature also provides alternative ways to invoke methods and constructors, and new ways to specify type hints. We need to enhance this mechanism for ClojureCLR in the same way we enhanced 'classic' interop.
8+
Clojure has introduced a new _qualified methods_ feature allows for Java methods to be passed to higher-order functions. This feature also provides alternative ways to invoke methods and constructors, and new ways to specify type hints. We need to enhance this mechanism for ClojureCLR in the same ways we enhanced 'classic' interop.
99

1010
I'll start with a quick review of 'classic' interop, including the additions made for interop with the CLR.
11-
I'll introduce the new qualified methods mechanism.
11+
I'll (quickly) survey the new qualified methods mechanism.
1212
I'll conclude with looking at how the CLR extras will be incorporated in the new mechanism.
1313

1414
# 'Classic' interop
1515

16-
I'm going to assume basic familiarity with interoperabiltiy with the JVM/CLR, at least prior to the introduction of qualified methods. If you need a refresher, you can look at the [Java interop section](https://clojure.org/reference/java_interop) of the Clojure reference.
16+
I'm going to assume basic familiarity with interoperabiltiy with the JVM/CLR (prior to the introduction of qualified methods). If you need a refresher, you can look at the [Java interop section](https://clojure.org/reference/java_interop) of the Clojure reference.
1717

1818
There are several pages on the ClojureCLR wiki that talk about the additional features of CLR interop:
1919

@@ -35,7 +35,24 @@ Symbols that represent the names of types are resolved to `Type` objects.
3535

3636
Classes can be `import`ed into a Clojure namespece so that the namespace of the type can be omitted, as with `String` above. (There is a default set of imports that are always available. See the note at the end for how that set is computed.)
3737

38-
There are types in the CLR that can not be named by symbols. (I guess Java does not yet have this problem.) See the note at the end for a few comments about this.
38+
## Interlude: Specifying type names
39+
There are types in the CLR that can not be named by symbols, or at least symbols that the Lisp reader can parse. (I guess Java does not yet have this problem.) See the note at the end for a few comments about this. The
40+
[specifying types](https://github.com/clojure/clojure-clr/wiki/Specifying-types) page explains how we get around this. It is just ugly. The mechanism ties directly into the CLR's type naming and resolution machinery, thus:
41+
42+
```clojure
43+
|System.Collections.Generic.IList`1[System.Int32]|
44+
```
45+
46+
You have to include fully-qualified type names, generics include a backtick and the number of type arguments, and the type arguments are enclosed in square brackets.
47+
48+
> I plan to introduce a new syntax for this in ClojureCLR.Next that would take advantage of imports and be otherwise nice.
49+
When I designed the `|...|` syntax (stolen from CommonLisp), Clojure did not yet have _tagged literals_. Now we might be able to do something like
50+
>
51+
```
52+
#type "IList<int>"
53+
```
54+
>
55+
> (If you are interested in helping to design this, please let me know.)
3956
4057
## Member access
4158

@@ -58,59 +75,53 @@ For CLR interop, we had to add some additional functionality, primarily for call
5875
If you are familiar with C#, you have seen `ref`, `in`, and `out` used in method signatures. There is no distinction of these at the CLR level. C# adds `in` and `out` for additional compile-time analysis. Given that we don't have uninitialized variables in Clojure and that CLR doesn't distinguish, ClojureCLR only provide a `by-ref` mechanism. The example given on the wiki page looks at a class defined by:
5976

6077
```C#
61-
public class C1
78+
public class AlmostEverything
6279
{
63-
public int m3(int x) { return x; }
64-
public int m3(ref int x) { x = x + 1; return x+20; }
65-
public string m5(string x, ref int y) { y = y + 10; return x + y.ToString(); }
66-
public int m5(int x, ref int y) { y = y + 100; return x+y; }
80+
public int Out(int x) { return x + 10; }
81+
public int Out(ref int x) { x += 1; return x + 20; }
82+
83+
public string Ambig(string x, ref int y) { y += 10; return x + y.ToString(); }
84+
public int Ambig(int x, ref int y) { y
6785
}
6886
```
6987

70-
To call `m3` with a `ref` argument, you would use:
88+
To call `Out` with a `ref` argument, you would use:
7189

7290
```clojure
73-
(let [n (int n) ]
74-
(.m3 c (by-ref n)))
91+
(let [m (int n) ]
92+
(.out c (by-ref m)))
7593
```
7694

7795
The type hint provided by the `(int n)` is required -- otherwise the it will try to match a `ref object` parameter.
7896

79-
The `by-ref` is a syntactic form that can only be used at the top-level of interop calls, as shown here. It can only wrap a local variable. (`by-ref` can also be used in `definterface`, `deftype`, and the like.) And yes, the value of the local variable `n` is updated by the call -- yep, that binding is not immutable. You do not want to know how this is done.
97+
The `by-ref` is a syntactic form that can only be used at the top-level of interop calls, as shown here. It can only wrap a local variable. (`by-ref` can also be used in `definterface`, `deftype`, and the like.) And yes, the value of the local variable `n` is updated by the call -- that binding is not immutable. You do not want to know how this is done.
8098

8199
For `params`, consider the class:
82100

83101
```C#
84-
namespace dm.interop
102+
public class ParamsTest
85103
{
86-
public class C6
87-
{
88-
public static int sm1(int x, params object[] ys)
104+
public static int StaticParams(int x, params object[] ys)
89105
{
90106
return x + ys.Length;
91107
}
92-
public static int sm1(int x, params string[] ys)
108+
public static int StaticParams(int x, params string[] ys)
93109
{
94110
int count = x;
95111
foreach (String y in ys)
96112
count += y.Length;
97113
return count;
98114
}
99-
public static int m2(ref int x, params object[] ys)
100-
{
101-
x += ys.Length;
102-
return ys.Length;
103-
}
104-
}
115+
105116
}
106117
```
107118

108-
Method `sm1` is overloaded on the `params` argument.
119+
Method `StaticParams` is overloaded on the `params` argument.
109120
You can access the first overload with either of these:
110121

111122
```clojure
112-
(dm.interop.C6/sm1 12 #^objects (into-array Object [1 2 3] ))
113-
(dm.interop.C6/sm1 12 #^"System.Object[]" (into-array Object [1 2 3]))
123+
(ParamsTest/StaticParams 12 #^objects (into-array Object [1 2 3] ))
124+
(ParamsTest/StaticParams 12 #^"System.Object[]" (into-array Object [1 2 3]))
114125
```
115126

116127
The second overload is accessed with:
@@ -119,19 +130,9 @@ The second overload is accessed with:
119130
(dm.interop.C6/sm1 12 #^"System.String[]" (into-array String ["abc" "de" "f"]))
120131
(dm.interop.C6/sm1 12 #^"System.String[]" (into-array ["abc" "de" "f"]))
121132
```
122-
123-
Make me one with everything.
124-
125-
```clojure
126-
(defn c6m2 [x]
127-
(let [n (int x)
128-
v (dm.interop.C6/m2 (by-ref n) #^objects (into-array Object [1 2 3 4]))]
129-
[n v]))
130-
```
131-
132133
## Generic methods
133134

134-
We are talking here about methods with type paremeters, not methods that are part of a generic type.
135+
We are talking here about methods with type parameters, not methods that are part of a generic type.
135136
Often you don't need to do anything special:
136137

137138
```clojure
@@ -194,8 +195,7 @@ By invocation we mean appearing as the first element in a `(func args)` form. T
194195
```
195196

196197
For static methods, this is our original syntax. For instance methods, this would compare to `(.ToUpper ^String s)`.
197-
The third example is equivalent to `(String. \a 12)`. We gain the ability to directly specify the type on the instance method.
198-
(Though see `:param-tags` below for a bonus.)
198+
The third example is equivalent to `(String. \a 12)`. We gain the ability to directly specify the type via the namespace of the symbol.
199199

200200
Using qualified methods as values is the more interesting case. Rather than needing to wrap a method in a function, as in
201201

@@ -214,8 +214,7 @@ Note that we no longer need the type hint on the parameter to avoid reflection.
214214

215215
Using qualified methods gives us the benefit of a type hint on the instance variable for instance method invocation. In fact, the type that the qualified method gives (`String` in the case of `String/.ToUpper`) overrides any type hint on the instance argument.
216216

217-
For invocations, further disambiguation of method signature can be made by providing type hints on the other arguments.
218-
However, for use in value positions, we cannot type hint in this way.
217+
For invocations, further disambiguation of method signature can be made by providing type hints on the other arguments.
219218

220219
Consider trying to map `Math/Abs` over a sequence. In the old `IFn`-wrapper style
221220

@@ -235,7 +234,7 @@ Now consider using a qualified method:
235234
(map Math/Abs [1.23 -3.14])
236235
```
237236

238-
You will get a reflection warning. And there is no place to put a traditional type hint.
237+
You still will get a reflection warning. And there is no place to put a traditional type hint.
239238

240239
Enter `:param-tags`. You can add `:param-tags` metadata to the qualified method to provide type hints. The easiest way to The easiest way is to use new `^[...]` metadata reader syntax.
241240

@@ -271,13 +270,13 @@ For one argument, not a big deal. But with, say, 3 arguments, it might help to
271270

272271
At least you have a choice.
273272

274-
## the add-ons
273+
## The CLR add-ons
275274

276-
We need to allow adding `(type-args ...)` and `(by-ref ...)` to our `:param-tags`.
275+
We need to allow adding `(type-args ...)` to our `:param-tags`.
277276
Simply, we just put them into position.
278277

279278
```clojure
280-
#[(type-args T1 T2) T3 T4 (by-ref T5)] T/.m
279+
#[(type-args T1 T2) T3 T4 |T5&|] T/.m
281280
```
282281

283282
would specify the instance method
@@ -289,8 +288,9 @@ class T
289288
}
290289
```
291290

292-
I don't know if there is any utility for `by-ref` in things like `map` calls. You would have no way to get any changed value in a `by-ref` parameter.
293-
A fallback to the classic interop using a function wrapper seems best for this situation.
291+
Note also the use of the reference type `T5&` for the by-ref parameter.
292+
293+
I don't know if there is any utility for `by-ref` in things like `map` calls. You would have no way to get any changed value in a `by-ref` parameter. A fallback to the classic interop using a function wrapper seems best for this situation.
294294

295295

296296
## Availability
@@ -309,24 +309,4 @@ The new qualified methods feature is available in ClojureCLR 1.12-alpha10 and la
309309
- the type is not a generic type definition (meaning an uintantiated generic type)
310310
- the type's name does not start with "_" or "<".
311311

312-
In addition, strictly for my own convenience for dealing with 'core.clj' and other startup files, I add `System.Text.StringBuilder`, `clojure.lang.BigInteger` and `clojure.lang.BigDecimal`. (I'll change `clojure.lang.BigInteger` to `System.Numerics.BigInteger` in the ClojureCLR.Next.)
313-
314-
315-
## Note: Specifying type names
316-
317-
The
318-
[specifying types](https://github.com/clojure/clojure-clr/wiki/Specifying-types) page explains how we get around this. It is just ugly. The mechanism ties directly into the CLR's type naming and resolution machinery, thus:
319-
320-
```clojure
321-
|System.Collections.Generic.IList`1[System.Int32]|
322-
```
323-
324-
You have to include fully-qualified type names, generics include a backtick and the number of type arguments, and the type arguments are enclosed in square brackets.
325-
326-
I plan to introduce a new syntax for this in ClojureCLR.Next, that would take advantage of imports and be otherwise nice.
327-
When I designed the `|...|` syntax (stolen from CommonLisp), Clojure did not yet have _tagged literals_. Now we might be able to do something like
328-
329-
#type "IList<int>"
330-
331-
(If you are interested in helping to design this, please let me know.)
332-
312+
In addition, strictly for my own convenience for dealing with 'core.clj' and other startup files, I add `System.Text.StringBuilder`, `clojure.lang.BigInteger` and `clojure.lang.BigDecimal`. (I'll change `clojure.lang.BigInteger` to `System.Numerics.BigInteger` in ClojureCLR.Next.)

0 commit comments

Comments
 (0)