1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
|
# Chapter 13: JVM inter-operation
_Where you will cross the great divide._
---
No language is an island, and even _compiled-to-native_ languages need to have some _FFI_ (or _foreign function interface_) to interact with C, Fortran or some other language.
There's a ton of awesome infrastructure out there that was implemented in other technologies, and there is no reason for us not to take a piece of that pie.
The beautiful thing about the inter-operation mechanism offered by Lux is that it can be used to interact with _any_ language running on the JVM (not only Java).
Although, due to its simplicity, it's better suited for interacting with programs originally written in Java, as other languages tend to implement some tricks that make the classes they generate a little bit... _funky_.
_So, what do I **need** to work with the JVM?_
Basically, just 3 things:
1. The means to consume the resources it provides (i.e. classes, methods, fields, and objects).
2. The means to create your own resources (i.e. class definitions).
3. The means to access its special features (such as synchronization).
Let's explore them.
By the way, the only module relevant to this chapter is `library/lux/ffi`.
## Importing classes, methods, and fields
It's all done with the help of the `import:` macro:
```clojure
... Allows importing JVM classes, and using them as types.
... Their methods, fields and enum options can also be imported.
(import: java/lang/Object
["[1]::[0]"
(new [])
(equals [java/lang/Object] boolean)
(wait [int] "io" "try" void)])
... Special options can also be given for the return values.
... "?" means that the values will be returned inside a Maybe type. That way, null becomes .#None.
... "try" means that the computation might throw an exception, and the return value will be wrapped inside the Try type.
... "io" means the computation has side effects, and will be wrapped inside the IO type.
... These options must show up in the following order ["io" "try" "?"] (although, each option can be used independently).
(import: java/lang/String
["[1]::[0]"
(new [[byte]])
("static" valueOf [char] java/lang/String)
("static" valueOf "as" int_valueOf [int] java/lang/String)])
(import: (java/util/List e)
["[1]::[0]"
(size [] int)
(get [int] e)])
(import: (java/util/ArrayList a)
["[1]::[0]"
([T] toArray [[T]] [T])])
... The class-type that is generated is of the fully-qualified name.
... This avoids a clash between the java.util.List type, and Lux's own List type.
... All enum options to be imported must be specified.
(import: java/lang/Character$UnicodeScript
["[1]::[0]"
("enum" ARABIC CYRILLIC LATIN)])
... It should also be noted, the types that show up in method arguments or return values may only be Java classes, arrays, primitives, void or type-vars.
... Lux types, such as Maybe cannot be used (otherwise, they'd be confused for Java classes).
(import: (lux/concurrency/async/JvmAsync A)
["[1]::[0]"
(resolve [A] boolean)
(poll [] A)
(wasResolved [] boolean)
(waitOn [lux/Function] void)
("static" [A] make [A] (lux/concurrency/async/JvmAsync A))])
... Also, the names of the imported members will look like Class::member
(java/lang/Object::new [])
(java/lang/Object::equals [other_object] my_object)
(java/util/List::size [] my_list)
(java/lang/Character$UnicodeScript::LATIN)
```
This will be the tool you use the most when working with the JVM.
As you have noticed, it works by creating functions for all the class members you need.
It also creates Lux type definitions matching the classes you import, so that you may easily refer to them when you write your own types later in regular Lux code.
It must be noted that `import:` requires that you only import methods and fields from their original declaring classes/interfaces.
What that means is that if class `A` declares/defines method `foo`, and class `B` extends `A`; to import `foo`, you must do it by importing it from `A`, instead of `B`.
## Writing classes
Normally, you'd use the `class:` macro:
```clojure
... Allows defining JVM classes in Lux code.
... For example:
(class: "final" (TestClass A) [Runnable]
... Fields
("private" foo boolean)
("private" bar A)
("private" baz java/lang/Object)
... Methods
("public" [] (new [value A]) []
(exec
(:= ::foo #1)
(:= ::bar value)
(:= ::baz "")
[]))
("public" (virtual) java/lang/Object
"")
("public" "static" (static) java/lang/Object
"")
(Runnable [] (run) void
[])
)
... The tuple corresponds to parent interfaces.
... An optional super-class can be specified before the tuple. If not specified, java.lang.Object will be assumed.
... Fields and methods defined in the class can be used with special syntax.
... For example:
... ::resolved, for accessing the 'resolved' field.
... (:= ::resolved #1) for modifying it.
... (::new! []) for calling the class's constructor.
... (::resolve! container [value]) for calling the 'resolve' method.
```
And, for anonymous classes, you'd use `object`:
```clojure
... Allows defining anonymous classes.
... The 1st tuple corresponds to class-level type-variables.
... The 2nd tuple corresponds to parent interfaces.
... The 3rd tuple corresponds to arguments to the super class constructor.
... An optional super-class can be specified before the 1st tuple. If not specified, java.lang.Object will be assumed.
(object [] [Runnable]
[]
(Runnable [] (run self) void
(exec
(do_something some_value)
[])))
```
## Special features
* Accessing class objects.
```clojure
... Loads the class as a java.lang.Class object.
(class_for java/lang/String)
```
* Test instances.
```clojure
... Checks whether an object is an instance of a particular class.
... Caveat emptor: Can't check for polymorphism, so avoid using parameterized classes.
(case (check java/lang/String "YOLO")
{.#Some value_as_string}
{.#None})
```
* Synchronizing threads.
```clojure
... Evaluates body, while holding a lock on a given object.
(synchronized object-to-be-locked
(exec
(do something)
(do something else)
(finish the computation)))
```
Calling multiple methods consecutively
```clojure
... Call a variety of methods on an object. Then, return the object.
(do_to object
(ClassName::method1 arg0 arg1 arg2)
(ClassName::method2 arg3 arg4 arg5))
```
`do_to` is inspired by Clojure's own doto macro.
The difference is that, whereas Clojure's version pipes the object as the first argument to the method, Lux's pipes it at the end (which is where method functions take their object values).
The `library/lux/ffi` module offers much more, but you'll have to discover it yourself by heading over to [the documentation for the Standard Library](https://github.com/LuxLang/lux/tree/master/documentation/library/standard).
---
Host platform inter-operation is a feature whose value can never be understated, for there are many **important** features that could never be implemented without the means provided by it.
We're actually going to explore one such feature in the next chapter, when we abandon our old notions of sequential program execution and explore the curious world of concurrency.
See you in [the next chapter](chapter_14.md)!
|