source: project/wiki/eggref/4/nrepl @ 34231

Last change on this file since 34231 was 34231, checked in by svnwiki, 11 months ago

Anonymous wiki edit for IP [213.52.73.118]:

File size: 6.3 KB
Line 
1== NREPL
2[[toc:]]
3A networked REPL for Chicken Scheme. Each new incoming connection runs
4in a new {{srfi-18}} thread.
5
6
7=== Requirements
8None except the {{tcp}} and {{srfi-18}} units from CHICKEN core.
9
10
11=== API
12<procedure> (nrepl port [spawn])</procedure>
13
14Listen to TCP port {{port}} and (blockingly) wait for incoming
15connections.  {{(spawn in out)}} is called for each incomming
16connection, where {{in}} is the input port and {{out}} is the output port
17for the new TCP session.
18
19You can use {{spawn}}, for example, for authentication:
20
21<enscript highlight="scheme">    
22(nrepl 1234
23       (lambda (in out)
24         (thread-start! ;; otherwise accept-loop will be blocked
25          (lambda ()
26            (display ";; please enter an accept token: " out)
27            (define token (read-line in))
28            (if (equal? token "abc")
29                (nrepl-loop in out)
30                (begin (display ";; access denied\n" out)
31                       (close-input-port in)
32                       (close-output-port out)))))))
33</enscript>
34
35blockquoteYou can use {{(tcp-addresses in)}} and {{(tcp-port-numbers in)}} to find
36out where the new session is coming from.
37
38{{nrepl}} will loop for accepting incomming connections unless {{spawn}}
39returns {{#f}}.
40
41<procedure> (nrepl-loop in out [eval [read]])</procedure>
42
43Start a standard REPL-loop: print the prompt, read an s-expression
44from {{in}}, evaluate the expression, print the result to {{out}} and
45repeat forever. This can be used inside the optionally supplied
46{{spawn}}-procedure above.
47
48
49=== Practical use
50Any source-code you send down a {{nrepl}} session will not be persisted
51anywhere.  You can reset your program state by restarting your program
52which may be useful sometimes.
53
54
55==== Terminal users
56Editing code directly from {{nc localhost 1234}} isn't
57pleasant. Luckily, [[http://freecode.com/projects/rlwrap|rlwrap]] works along {{nrepl}} to improve this
58experience:
59
60<enscript highlight="bash">    
61 âž€ rlwrap nc localhost 1234
62;; nrepl on (csi -s example.scm)
63#;1> (define (hello) (print "this will be in my history"))
64</enscript>
65
66[[http://freecode.com/projects/rlwrap|rlwrap]] will also save your read-line history for the next invokation
67{{rlwrap nc localhost 1234}} which is handy!
68
69
70==== [[https://www.gnu.org/software/emacs/|Emacs]] users
71{{nrepl}} plays very nicely with [[https://www.gnu.org/software/emacs/|Emacs]]! If you're used to running {{M-x
72run-scheme}} and sending source-code from buffers into your REPL, an
73{{nrepl}} endpoint can be used as a Scheme "interpreter". Specify that
74you want to run {{nc localhost 1234}} for your Scheme interpreter
75instead of the usual {{csi}}, and you get the functionality you're used
76to.
77
78Note that telling [[https://www.gnu.org/software/emacs/|Emacs]] that {{nc localhost 1234}} is your Scheme
79interpreter is tricky because {{C-u M-x run-scheme}} will not let you
80enter spaces. This can be solved by pressing {{C-q}} before pressing
81space.
82
83
84==== Example HTTP-server work-flow
85A real-world use-case for {{nrepl}} might be something like the
86following. Let's make a simple hello-world HTTP server using [[http://api.call-cc.org/doc/spiffy|spiffy]].
87
88<enscript highlight="scheme">    
89(use spiffy nrepl)
90
91(define (app c)
92  (send-response body: "hello world\n"))
93
94(thread-start!
95 (lambda ()
96   (vhost-map `((".*" . ,(lambda (c) (app c)))))
97   (start-server)))
98
99(print "starting nrepl on port 1234")
100(nrepl 1234)
101</enscript>
102
103Now spiffy runs on port {{8080}}:
104
105<enscript highlight="bash">    
106 âž€ curl localhost:8080
107hello world
108</enscript>
109
110What's nice about this is that, since {{app}} is a top-level variable,
111it can be replaced from the REPL:
112
113<enscript highlight="bash">    
114 âž€ nc localhost 1234
115;; nrepl on (csi -s example.scm)
116#;1> (define (app c) (send-response body: "repl hijack!\n"))
117#;1> ^C
118</enscript>
119
120Now {{spiffy}} will use our top-level {{app}} for its proceeding requests:
121
122<enscript highlight="bash">    
123 âž€ curl localhost:8080
124repl hijack!
125</enscript>
126
127Note that {{app}} must be wrapped in a {{lambda}} for this to work,
128because the REPL can only replace top-level variable definitions.
129
130The implications of this can be quite dramatic in terms of
131work-flow. If you write your app in a REPL-friendly way like this, you
132can modify you program behaviour on-the-fly from the REPL and never
133have to restart your process and lose its state.
134
135
136==== Example CPU-intensive main thread
137{{nrepl}} can be used for live-coding interactive application such as
138games. Adding {{(thread-start! (lambda () (nrepl 1234)))}} usually Just
139Works, where you can redefine top-level function and game state
140on-the-fly.
141
142However, if the game-loop is eating up a lot of scheduler-time, you
143may find that your REPL becomes unresponsive. A good way to fix this
144is to wrap both the REPL and the game-loop in a mutex. This has
145another advantage in that it will ensure your REPL will not interfere
146with game-state (or OpenGL state) during game-loop iteration.
147
148<enscript highlight="scheme">    
149;;; wrapping nrepl eval in a mutex for responsiveness
150;;; and game-loop thread-safety. running this and then doing:
151;;;     echo '(thread-sleep! 1)' | rlwrap nc localhost 1234
152;;; should pause the game-loop for 1 second
153(use nrepl)
154
155(define with-main-mutex
156  (let ((main-mutex (make-mutex)))
157    (lambda (proc)
158      (dynamic-wind (lambda () (mutex-lock! main-mutex))
159                    proc
160                    (lambda () (mutex-unlock! main-mutex))))))
161
162(thread-start!
163 (lambda ()
164   (nrepl 1234
165          (lambda (i o)
166            (thread-start!
167             (lambda ()
168               (nrepl-loop
169                i o (lambda (x) (with-main-mutex (lambda () (eval x)))))))))))
170
171(define (game-step)
172  (print* "\r"  (current-milliseconds) "   ")
173  (thread-sleep! 0.05))
174
175(let loop ()
176  (with-main-mutex game-step)
177  (loop))
178</enscript>
179
180
181==== {{nrepl}} in compiled code
182{{nrepl}} also works inside a compiled program. However, sometimes
183[[http://api.call-cc.org/doc/chicken/modules|modules]] disappear due to compiler optimizations.
184
185
186==== {{nrepl}} on Android
187{{nrepl}} has been used successfully on Android target hardware for
188remote interactive development.  Check
189out [[https://github.com/chicken-mobile/chicken-android-template|this]]
190Android example project.
191
192
193=== Source code repository
194You can find the
195source [[https://github.com/Adellica/chicken-nrepl|here]].
196
197
198=== Author
199Kristian Lein-Mathisen at [[https://github.com/Adellica/|Adellica]]
200
201
202=== License
203BSD
204
Note: See TracBrowser for help on using the repository browser.