A "One" Line Echo Server Using "let" in Python
This morning I realized something incredibly stupid. Stupid, because it has no real value except as a curiosity–which is to say, its perfect for this blog, which has featured other, things, of a similar nature.
Anyone familiar with Python and functional programming, will lambast Python for its measly one-line anonymous functions. What if they could do more? Well, they can! And, they can even introduce lexically scoped bindings as one would get if they had just used a better language to begin with, all in a ”somewhat“1 nice, easy, and clean way.
I present to you, a one-line2 echo server that will eventually exhaust all of your memory, and slow to a crawl…
import socket
import itertools
(lambda port=9000, s=socket.socket(socket.AF_INET, socket.SOCK_STREAM):
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) == None and
s.bind(('', port)) == None and
s.listen(5) == None and
list(map(lambda c:
c[0].sendall(c[0].recv(1024)) and
c[0].close(),
(s.accept() for _ in itertools.count(1)))))()
Wait, what? Yeah. Something special, isn’t it? There‘s a lot going on here, so let’s break it down a bit.
lambda port=9000, s=socket.socket(socket.AF_INET, socket.SOCK_STREAM):
This is the best part of the trick, and the reason I threw in ”let“ in the title. In lexically scoped languages, such as Scheme, it‘s common to see the use of the let
form which introduces new bindings that are lexically bound, i.e., local to the body of the let
expression. These are isomorphic to ”block-scoped“ variables one might find in other languages.
(let ((port 9000))
<some body>)
In Scheme, the above let
defines port
only within <some body>
. That construct is isomorphic to the following Scheme (and in fact many implementations just macro expand let
into this):
((lambda (port) <some body>) 9000)
We utilize default arguments to assign names to values in the Python version, and exploit the fact that a lambda
by itself doesn’t bind to a name as def
does3, and doesn‘t commonly appear in the global scope. What this means is that our ”bindings“ are evaluated when the anonymous function is created, which means it’s safe to use within a loop, or some other nested scope.
This lambda
perversion, therefore, is isomorphic to the following Python:
(lambda port: <utilize port somehow>)(9000)
Now, that we‘ve established this baseline, let’s move on.
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) == None and
s.bind(('', port)) == None and
s.listen(5) == None and
Here, we exploit the fact that the and
operator evaluates all of its operands provided they return truthy values. Since these functions return None
, we just check to see if they returned None
and continue to the next statement. This is of course roughly isomorphic to the following:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('', port))
s.listen(5)
Simple enough.
Continuing on, we get to the trickiest part of it all:
list(map(lambda c:
c[0].sendall(c[0].recv(1024)) and
c[0].close(),
(s.accept() for _ in itertools.count(1))))
Ignoring the outer list
for now, we see a simple call to map
, which maps the lambda
over the values produced by the generator expression. The generator expression produces an infinite list connected sockets, but since s.accept()
blocks until a client is connected, this implements a typical listen-accept loop. The roughly isomorphic, plain ole Python looks like so:
while True:
c = s.accept()
c[0].sendall(c[0].recv(1024))
c[0].close()
Why roughly? Because map
is lazily evaluated, and produces a new generator. We need a forcing function in order for each connected socket to be applied to the lambda
we have passed to the map
call. Enter list
which does that beautifully, though ensures that we‘ll eventually completely exhaust memory if we accept a lot of connections. That list being generated will store the value None
for every client that exists.
The only thing left to do is invoke our echo server, by wrapping ()
around the lambda
and tacking on ()
to call it.
Update: trousers suggested that a set
comprehension could avoid the catastrophic unbounded memory problem present with the call to list
. No set
comprehension, however, should be necessary if we just instead change list
to set
.