summaryrefslogtreecommitdiff
path: root/src/pages/missionary.clj
blob: 2bb06dea5960f3b29467496855073a54777b10a4 (plain)
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
(ns pages.missionary
  (:require [components :refer [page]]
            [borkdude.html :refer [html]]
            [highlight :refer [highlight-clj]]))

(defn missionary []
  (page
   (html
    [:<>
     [:h1 "FRP in Clojure with Missionary"]
     [:h3 "August 2025"]
     [:p "I've been working in Clojure on and off for nearly a decade now. In
that time, I've spent thousands of hours working in other languages
professionally: Python, Ruby, TypeScript / Flow / JavaScript, Kotlin, and Java.
I've experimented with many more, to varying degrees: Zig, Janet, Prolog, and
Factor, to name a few. At the end of the day, though, I always find myself
coming back to Clojure."]
     [:p "Though it's hard to pin down exactly why I find myself so drawn by
the language, it seems to have something to do with the relatively high incidence
of exposure to new, mind-expanding ideas that I've experienced while working
in Clojure and its ecosystem."]
     [:p "For one reason or another, Clojure seems to attract a high density of
smart people who are experimenting with fairly radical ways of solving hard
problems. Something about the combination of the language's simple macro system,
data orientation, reasonable performance, and practical approach to
functional programming make it a great foundation for building more complicated
experimental language constructs."]
     [:p "It is via Clojure that I first came to be exposed to datalog, for example,
which helped me begin to be able to articulate why I struggled with SQL's
relativel lack of expressivity. From there, I discovered prolog and the world of
\"search programming\", which gave me an entirely different way to frame
certain problems."]
     [:p "Similarly, I first learned of Datomic through Clojure, which blew my
mind the first time I encountered it. The idea of treating your entire database
as a single, immutable value would never have occurred to me on my own;
once I encountered it, however, the alternative seemed patently absurd."]
     [:p "The Clojure library " [:span.inline-code "meander"] " showed me that term rewriting systems
could be used for data transformation. (Did you know that term rewriting systems
have the full power of turing machines?) The folks over at Hyperfiddle
have proven that you can effectively abstract away the network connection that
sits between a client and a server, and they have credited Clojure for that
accomplishment. Similarly, my experience so far with Rama, from Red Planet Labs,
have made it hard for me to go back to \"traditional backend programming\".
(I could write a whole series of posts on that topic. Reach out if you'd find
that interesting.) They, too, credit Clojure for their progress."]
     [:p "In this post, I want to use the FRP library " [:span.inline-code "missionary"] " to give an
example of what it means to embed an entirely new concept within an existing
programming language. On the one hand, missionary is simply a library. On the
other hand, it differs significantly from the kind of library that you might
find in other programming languages because it introduces its own syntax and
semantics that integrate seamlessly with the host language, Clojure. (This is
the old \"promise of the DSL\" that lisps are famous for.)"]
     [:p "First, though: what is missionary? At a high level, missionary
seeks to make it easier to write correct asynchronous (and concurrent) programs
correctly, efficently (meaning that it makes effective use of available CPU
resources), and in a manner that will feel comfortable to functional programmers.
To accomplish that, missionary draws inspiration from a number of interesting
programming ideas and diciplines:"]
     [:ul
      [:li "Functional Reactive Programming, or FRP"]
      [:li "Process supervision"]
      [:li "Structured concurrency"]
      [:li "Ambiguous programming"]
      [:li "TKTK"]]
     [:p "To start our journey, I want to show you the equivelant of a \"hello, world!\"
program, written with missionary. This program captures the current time, in milliseconds;
it sleeps for one second; and then it captures the current time again."]
     (highlight-clj
      (require '[missionary.core :as m])

      (def run
        (m/ap (tap> [:a (System/currentTimeMillis)])
              (m/? (m/sleep 1000))
              (tap> [:b (System/currentTimeMillis)])))

      (m/? (m/reduce {} nil run)))
     [:p "(In these examples, you'll note my use of the " [:span.inline-code "(tap> ...)"] " function. This
function appends the value of its argument to a list. After the program runs,
I print the contents of that list to the \"tap contents:\" box below the code.)"]

     [:p "TKTK Note, however, that " [:span.inline-code "run"] " doesn't actually execute until we feed it
to the " [:span.inline-code "reduce"] " function. It's too early to explain how " [:span.inline-code "reduce"] " has anything
to do with running a program – we'll have to build some other intuition first.
For now, when you see " [:span.inline-code "reduce"] ", just think \"run a sequence of operations.\""]

     [:p "You might be thinking: "
      [:em "I could write this code just as easily without missionary.
What's the point? "]
      "Great question. We should go ahead and do that. Let's write this program once
without missionary, and then again with missionary but with a bit of additional
debugging thrown in:"]

     (highlight-clj
      (defn log
        "Emit `v` to `tap>`, capturing the current time and active thread along the way."
        [v]
        (tap> [v (System/currentTimeMillis) (.getName (Thread/currentThread))]))
      (defn run-native []
        (log :native-a)
        (Thread/sleep 1000)
        (log :native-b))

      (tap> (-> (run-native)
                (time)
                (with-out-str)))

      (def run-missionary
        (m/ap (log :missionary-a)
              (m/? (m/sleep 1000))
              (log :missionary-b)))

      (tap> (-> (m/reduce {} nil run-missionary)
                (m/?)
                (time)
                (with-out-str))))
     ;; TODO: Fix bug where second log doesn't
     ;; always print!

     [:p "A few things might jump out immediately."]
     [:p "First, both "
      [:span.inline-code "run-native"] " and "
      [:span.inline-code "run-missionary"]
      " block during their evaluation.  Notice that the runtime
     for each is approximately 1000ms."]
     [:p "Second, "
      [:span.inline-code "run-native"]
      " evaluates the entire expression on the repl's main thread.i By contrast, "
      [:span.inline-code "run-missionary"]
      " seems to move the
     form after the sleep expression to some kind of background thread. What's
     that about?"]  [:p "Oh, by the way. At the beginning of this post, I made
     it clear that Clojure has had a major impact on the way I think about
     programming, in large part because the language has facilities that make it
     particularly attractive for experimentation. While I believe that to be
     true, I know that Clojure is hardly alone in its expressiveness. If you are
     a Haskeller, an OCameler, a Common Lisper, an APLer, or even a C
     programmer, and you're thinking to yourself \"Clojure isn't special in this
     regard\", well, I'm sure you're right! Shoot me an email with your
     thoughts, I'd love to learn more about why your language of choice is
     unique in this regard. I'll update this post with your contribution."]
     [:h2 "References"]
     [:ul
      [:li "TODO flow spec"]
      [:li "TODO task spec"]
      [:li "TODO process supervision"]
      [:li
       [:a {:href "https://github.com/leonoel/missionary"} "Missionary"]]
      [:li [:a {:href "https://github.com/hyperfiddle/electric"} "Electric Clojure"]]
      [:li [:a {:href "https://github.com/noprompt/meander"} "Meander"]]
      [:li [:a {:href "https://jimmyhmiller.com/meander-rewriting"}
            "Introduction to Term Rewriting with Meander"]]]])))