Skip to content

Commit f722ecc

Browse files
Document asynchronous APIs
1 parent ce6dc36 commit f722ecc

File tree

4 files changed

+222
-0
lines changed

4 files changed

+222
-0
lines changed

Source/Swift/Channel+Hooks+Boilerplate.swift

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ class LazyHook5<
8585
}
8686

8787
extension Channel {
88+
/// Make a Swift callback out of an Emacs hook's name.
89+
///
90+
/// This allows us to use Emacs hooks as callbacks in Swift APIs.
91+
/// Please, see <doc:AsyncCallbacks> for more details on that.
92+
///
93+
/// - Parameter function: a name of a Lisp hook to turn into callback.
94+
/// - Returns: a callback that if called, will eventually run the hook.
8895
public func hook(_ hook: String)
8996
-> () -> Void
9097
{
@@ -93,6 +100,13 @@ extension Channel {
93100
callback: LazyHook0(hook: hook), args: ())
94101
}
95102
}
103+
/// Make a Swift callback out of an Emacs hook's name.
104+
///
105+
/// This allows us to use Emacs hooks as callbacks in Swift APIs.
106+
/// Please, see <doc:AsyncCallbacks> for more details on that.
107+
///
108+
/// - Parameter function: a name of a Lisp hook to turn into callback.
109+
/// - Returns: a callback that if called, will eventually run the hook.
96110
public func hook<T: EmacsConvertible>(_ hook: String)
97111
-> (T) -> Void
98112
{
@@ -101,6 +115,13 @@ extension Channel {
101115
callback: LazyHook1<T>(hook: hook), args: arg)
102116
}
103117
}
118+
/// Make a Swift callback out of an Emacs hook's name.
119+
///
120+
/// This allows us to use Emacs hooks as callbacks in Swift APIs.
121+
/// Please, see <doc:AsyncCallbacks> for more details on that.
122+
///
123+
/// - Parameter function: a name of a Lisp hook to turn into callback.
124+
/// - Returns: a callback that if called, will eventually run the hook.
104125
public func hook<T1: EmacsConvertible, T2: EmacsConvertible>(
105126
_ hook: String
106127
) -> (T1, T2) -> Void {
@@ -110,6 +131,13 @@ extension Channel {
110131
args: (arg1, arg2))
111132
}
112133
}
134+
/// Make a Swift callback out of an Emacs hook's name.
135+
///
136+
/// This allows us to use Emacs hooks as callbacks in Swift APIs.
137+
/// Please, see <doc:AsyncCallbacks> for more details on that.
138+
///
139+
/// - Parameter function: a name of a Lisp hook to turn into callback.
140+
/// - Returns: a callback that if called, will eventually run the hook.
113141
public func hook<
114142
T1: EmacsConvertible, T2: EmacsConvertible, T3: EmacsConvertible
115143
>(
@@ -121,6 +149,13 @@ extension Channel {
121149
args: (arg1, arg2, arg3))
122150
}
123151
}
152+
/// Make a Swift callback out of an Emacs hook's name.
153+
///
154+
/// This allows us to use Emacs hooks as callbacks in Swift APIs.
155+
/// Please, see <doc:AsyncCallbacks> for more details on that.
156+
///
157+
/// - Parameter function: a name of a Lisp hook to turn into callback.
158+
/// - Returns: a callback that if called, will eventually run the hook.
124159
public func hook<
125160
T1: EmacsConvertible, T2: EmacsConvertible, T3: EmacsConvertible,
126161
T4: EmacsConvertible
@@ -133,6 +168,13 @@ extension Channel {
133168
args: (arg1, arg2, arg3, arg4))
134169
}
135170
}
171+
/// Make a Swift callback out of an Emacs hook's name.
172+
///
173+
/// This allows us to use Emacs hooks as callbacks in Swift APIs.
174+
/// Please, see <doc:AsyncCallbacks> for more details on that.
175+
///
176+
/// - Parameter function: a name of a Lisp hook to turn into callback.
177+
/// - Returns: a callback that if called, will eventually run the hook.
136178
public func hook<
137179
T1: EmacsConvertible, T2: EmacsConvertible, T3: EmacsConvertible,
138180
T4: EmacsConvertible, T5: EmacsConvertible

Source/Swift/Channel+LispCallback+Boilerplate.swift

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,13 @@ class LazyLispCallback5<
7979
}
8080

8181
extension Channel {
82+
/// Make a Swift callback out of an Emacs function.
83+
///
84+
/// This allows us to use Emacs functions as callbacks in Swift APIs.
85+
/// Please, see <doc:AsyncCallbacks> for more details on that.
86+
///
87+
/// - Parameter function: a Lisp function to turn into a callback.
88+
/// - Returns: a callback that if called, will eventually call the given function.
8289
public func callback(_ function: EmacsValue)
8390
-> () -> Void
8491
{
@@ -87,6 +94,13 @@ extension Channel {
8794
callback: LazyLispCallback0(function: function), args: ())
8895
}
8996
}
97+
/// Make a Swift callback out of an Emacs function.
98+
///
99+
/// This allows us to use Emacs functions as callbacks in Swift APIs.
100+
/// Please, see <doc:AsyncCallbacks> for more details on that.
101+
///
102+
/// - Parameter function: a Lisp function to turn into a callback.
103+
/// - Returns: a callback that if called, will eventually call the given function.
90104
public func callback<T: EmacsConvertible>(_ function: EmacsValue)
91105
-> (T) -> Void
92106
{
@@ -95,6 +109,13 @@ extension Channel {
95109
callback: LazyLispCallback1<T>(function: function), args: arg)
96110
}
97111
}
112+
/// Make a Swift callback out of an Emacs function.
113+
///
114+
/// This allows us to use Emacs functions as callbacks in Swift APIs.
115+
/// Please, see <doc:AsyncCallbacks> for more details on that.
116+
///
117+
/// - Parameter function: a Lisp function to turn into a callback.
118+
/// - Returns: a callback that if called, will eventually call the given function.
98119
public func callback<T1: EmacsConvertible, T2: EmacsConvertible>(
99120
_ function: EmacsValue
100121
) -> (T1, T2) -> Void {
@@ -104,6 +125,13 @@ extension Channel {
104125
args: (arg1, arg2))
105126
}
106127
}
128+
/// Make a Swift callback out of an Emacs function.
129+
///
130+
/// This allows us to use Emacs functions as callbacks in Swift APIs.
131+
/// Please, see <doc:AsyncCallbacks> for more details on that.
132+
///
133+
/// - Parameter function: a Lisp function to turn into a callback.
134+
/// - Returns: a callback that if called, will eventually call the given function.
107135
public func callback<
108136
T1: EmacsConvertible, T2: EmacsConvertible, T3: EmacsConvertible
109137
>(
@@ -115,6 +143,13 @@ extension Channel {
115143
args: (arg1, arg2, arg3))
116144
}
117145
}
146+
/// Make a Swift callback out of an Emacs function.
147+
///
148+
/// This allows us to use Emacs functions as callbacks in Swift APIs.
149+
/// Please, see <doc:AsyncCallbacks> for more details on that.
150+
///
151+
/// - Parameter function: a Lisp function to turn into a callback.
152+
/// - Returns: a callback that if called, will eventually call the given function.
118153
public func callback<
119154
T1: EmacsConvertible, T2: EmacsConvertible, T3: EmacsConvertible,
120155
T4: EmacsConvertible
@@ -127,6 +162,13 @@ extension Channel {
127162
args: (arg1, arg2, arg3, arg4))
128163
}
129164
}
165+
/// Make a Swift callback out of an Emacs function.
166+
///
167+
/// This allows us to use Emacs functions as callbacks in Swift APIs.
168+
/// Please, see <doc:AsyncCallbacks> for more details on that.
169+
///
170+
/// - Parameter function: a Lisp function to turn into a callback.
171+
/// - Returns: a callback that if called, will eventually call the given function.
130172
public func callback<
131173
T1: EmacsConvertible, T2: EmacsConvertible, T3: EmacsConvertible,
132174
T4: EmacsConvertible, T5: EmacsConvertible

Source/Swift/Channel+SwiftCallback+Boilerplate.swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ class LazySwiftCallback5<T1, T2, T3, T4, T5>: AnyLazyCallback {
6969
}
7070

7171
extension Channel {
72+
/// Make a callback that doesn't require the environment from a closure that does.
73+
///
74+
/// This allows us to contact Emacs as part of asynchronous callbacks from Swift APIs.
75+
/// Please, see <doc:AsyncCallbacks> for more details on that.
76+
///
77+
/// - Parameter function: a function to turn into a callback.
78+
/// - Returns: a callback that if called, will eventually call the given function.
7279
public func callback(function: @escaping (Environment) throws -> Void)
7380
-> () -> Void
7481
{
@@ -77,6 +84,13 @@ extension Channel {
7784
callback: LazySwiftCallback0(function: function), args: ())
7885
}
7986
}
87+
/// Make a callback that doesn't require the environment from a closure that does.
88+
///
89+
/// This allows us to contact Emacs as part of asynchronous callbacks from Swift APIs.
90+
/// Please, see <doc:AsyncCallbacks> for more details on that.
91+
///
92+
/// - Parameter function: a function to turn into a callback.
93+
/// - Returns: a callback that if called, will eventually call the given function.
8094
public func callback<T>(function: @escaping (Environment, T) throws -> Void)
8195
-> (T) -> Void
8296
{
@@ -85,6 +99,13 @@ extension Channel {
8599
callback: LazySwiftCallback1(function: function), args: arg)
86100
}
87101
}
102+
/// Make a callback that doesn't require the environment from a closure that does.
103+
///
104+
/// This allows us to contact Emacs as part of asynchronous callbacks from Swift APIs.
105+
/// Please, see <doc:AsyncCallbacks> for more details on that.
106+
///
107+
/// - Parameter function: a function to turn into a callback.
108+
/// - Returns: a callback that if called, will eventually call the given function.
88109
public func callback<T1, T2>(
89110
function: @escaping (Environment, T1, T2) throws -> Void
90111
) -> (T1, T2) -> Void {
@@ -93,6 +114,13 @@ extension Channel {
93114
callback: LazySwiftCallback2(function: function), args: (arg1, arg2))
94115
}
95116
}
117+
/// Make a callback that doesn't require the environment from a closure that does.
118+
///
119+
/// This allows us to contact Emacs as part of asynchronous callbacks from Swift APIs.
120+
/// Please, see <doc:AsyncCallbacks> for more details on that.
121+
///
122+
/// - Parameter function: a function to turn into a callback.
123+
/// - Returns: a callback that if called, will eventually call the given function.
96124
public func callback<T1, T2, T3>(
97125
function: @escaping (Environment, T1, T2, T3) throws -> Void
98126
) -> (T1, T2, T3) -> Void {
@@ -102,6 +130,13 @@ extension Channel {
102130
args: (arg1, arg2, arg3))
103131
}
104132
}
133+
/// Make a callback that doesn't require the environment from a closure that does.
134+
///
135+
/// This allows us to contact Emacs as part of asynchronous callbacks from Swift APIs.
136+
/// Please, see <doc:AsyncCallbacks> for more details on that.
137+
///
138+
/// - Parameter function: a function to turn into a callback.
139+
/// - Returns: a callback that if called, will eventually call the given function.
105140
public func callback<T1, T2, T3, T4>(
106141
function: @escaping (Environment, T1, T2, T3, T4) throws -> Void
107142
) -> (T1, T2, T3, T4) -> Void {
@@ -111,6 +146,13 @@ extension Channel {
111146
args: (arg1, arg2, arg3, arg4))
112147
}
113148
}
149+
/// Make a callback that doesn't require the environment from a closure that does.
150+
///
151+
/// This allows us to contact Emacs as part of asynchronous callbacks from Swift APIs.
152+
/// Please, see <doc:AsyncCallbacks> for more details on that.
153+
///
154+
/// - Parameter function: a function to turn into a callback.
155+
/// - Returns: a callback that if called, will eventually call the given function.
114156
public func callback<T1, T2, T3, T4, T5>(
115157
function: @escaping (Environment, T1, T2, T3, T4, T5) throws -> Void
116158
) -> (T1, T2, T3, T4, T5) -> Void {
@@ -121,6 +163,13 @@ extension Channel {
121163
}
122164
}
123165

166+
/// Execute the given closure with Emacs environment.
167+
///
168+
/// This function allows us to asynchronously use environment
169+
/// to execute code on the Emacs side whenever we have any
170+
/// updates.
171+
///
172+
/// - Parameter function: a callback to execute with Emacs environment
124173
public func withEnvironment(_ function: @escaping (Environment) throws -> Void) {
125174
register(callback: LazySwiftCallback0(function: function), args: ())
126175
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,92 @@
11
# Asynchronous Callbacks
22

33
Calling Lisp functions without an active Environment.
4+
5+
## Overview
6+
7+
As it was mentioned in <doc:Lifetimes>, ``Environment`` instances could not be stored and captured. Additionally, dynamic modules are not allowed to use given environments from a different thread they were given it on. This can be a problem if we want to use one of the rich Swift APIs to do some useful work for us. We do want to tell the Emacs side that the work was done, and pass in some results. ``Channel`` mechanism was designed specifically with this goal in mind and it provides multiple ways of passing data back into Lisp.
8+
9+
## Creating a Channel
10+
11+
In order to create a ``Channel``, you do need to have an active environment. Simply call ``Environment/openChannel(name:)`` with a name for that channel, and that's pretty much it. You are allowed to create multiple channels and use them simultaneously. Channel callbacks are serialized to be called in exactly the same order their Swift counter-parts got called in the first place. Because of this reason, it might be a good idea to have different channels for less frequent important callbacks and more frequent, but less important callbacks.
12+
13+
## withEnvironment
14+
15+
The easiest way of interacting with ``Environment`` using an open ``Channel`` is via ``Channel/withEnvironment(_:)``. This function allows you to execute some code with Emacs environment whenever we'll get it from Emacs.
16+
17+
```swift
18+
// do some work
19+
channel.withEnvironment {
20+
env throws in try env.funcall("message", with: "The work is done!")
21+
}
22+
// keep doing something else
23+
```
24+
25+
It should be noted that the code from `// keep doing something else` will most likely get executed before before our message will appear in Emacs. For this reason, `withEnvironment` (and other callbacks) don't have return values.
26+
27+
## callback
28+
29+
In many cases, we want to adapt some asynchronous APIs to Lisp. Let's say we have a UI form that represents a text-field or something similar. When the user submits the form, a callback is called with the user-written text. We probably don't want to do anything with this text on the Swift side, but our module can pass it to the Lisp side. Of course, we can implement it using ``Channel/withEnvironment(_:)``:
30+
31+
```swift
32+
form.onSubmit {
33+
(text: String) in
34+
channel.withEnvironment {
35+
env throws in try env.funcall("lisp-on-submit", with: text)
36+
}
37+
}
38+
```
39+
40+
Instead of having to closures, we can use one of the `callback` methods.
41+
42+
```swift
43+
form.onSubmit(channel.callback {
44+
(env: Environment, text: String) throws in
45+
try env.funcall("lisp-on-submit", with: text)
46+
})
47+
```
48+
49+
This family of methods turns closures that have ``Environment`` as its first parameter into closures that don't. In our example, it's `((Environment, String) -> Void) -> (String) -> Void`.
50+
51+
This way, we can write the code that would've been very similar to the code that doesn't need to communicate with Emacs in the first place. However, sometimes we don't even need to do anything else except for communicating with Lisp.
52+
53+
Let's extend our previous example:
54+
55+
```swift
56+
try env.defun("create-form") {
57+
(onSubmit: PersistentEmacsValue) in
58+
let form = new Form()
59+
form.onSubmit(channel.callback {
60+
(env: Environment, text: String) throws in
61+
try env.funcall(onSubmit, with: text)
62+
})
63+
return form
64+
}
65+
```
66+
67+
Here, we expose form creation to Lisp in its entirety, and allow users to create it when they want it and have a callback completely on the Lisp side. This code works, but feels a bit wordy. For this reason, ``Channel`` provides a family of methods also named `callback` that work with ``EmacsValue`` functions directly. Let's look at how we can rewrite our code first.
68+
69+
```swift
70+
try env.defun("create-form") {
71+
(onSubmit: PersistentEmacsValue) in
72+
let form = new Form()
73+
form.onSubmit(channel.callback(onSubmit))
74+
return form
75+
}
76+
```
77+
78+
That's it, we turned `onSubmit` Lisp function into a Swift closure: `(String) -> Void`. You can use this formula with all kinds of closure types, ``Channel`` will create a Swift closure exactly of type that's expected by the API. Of course, since we call into a Lisp function, it means that all arguments should be ``EmacsConvertible`` (see <doc:TypeConversions>).
79+
80+
## hooks
81+
82+
Emacs Lisp has a different way of notifying some code of system-wide events, - hooks. In some cases, it would be the most appropriate tool to use for designing our module's API. ``Channel`` also provides a family of `hook` methods that you can use this way:
83+
84+
```swift
85+
try env.defun("create-form") {
86+
let form = new Form()
87+
form.onSubmit(channel.hook("form-submit-hooks"))
88+
return form
89+
}
90+
```
91+
92+
Essentially, it does the same thing as the `callback` method in the earlier snippet, but it runs a hook instead.

0 commit comments

Comments
 (0)