Interfacing Frege and Ring
Because Clojure is too mainstream for you, you filthy hipster
Frege is a strongly-typed functional language for the JVM. Its goal is to mirror Haskell as closely as is possible on the platform, and as far as I can tell it does a pretty decent job. It seems performant enough, and more importantly grants access to a Haskell-esque type system. This makes it a pretty good complement to Clojure for those problems where a strong typing system is important.
The downside to Frege is that, even though documentation exists (and is actually quite expansive given the language’s limited adoption), it’s still hard to find straightforward how-tos by googling. So here’s some much-needed frege-related blogspam to fill out those results.
I spent a few hours experimenting with calling Frege code from Clojure in the context of Ring, since I can’t seem to stop thinking in terms of web services. Here’s how that went.
Starting with the Types
My goal was to write some routing and glue code in Clojure, but do the intermediary in Frege so that I could benefit from type-safety where it was easiest – in the most functionally-pure parts of the code. I came up with the following types to outline a basic Request:
type StatusCode = Int
type Uri = String
type ResponseBody = String
type RequestParams = HashMap String String
type Headers = HashMap String String
data Request = Request {
uri :: Uri,
params :: RequestParams
}
data Response = Response {
body :: ResponseBody,
headers :: Headers,
status :: StatusCode
}
It’s not much, but it’s a start. The Request
could be extended with more fields for headers and whatnot as necessary, but
the Response
is actually sufficient to completely fulfill Ring’s response interface.
Our intention is to convert from and to regular Ring request/response maps at the edges, and
pass around these typed records in between.
Unfortunately, there’s no HashMap
implemented in Frege, at least not one that was handy. So to
accomplish this, we need to do one of the following:
- Implement/import a hash-map in frege
- Use a Java hash-map via Frege’s Java FFI
- Use a Clojure hash-map that we can interop somehow
The last choice seemed like the best one. Since Clojure’s persistent collections
are pure and well-behaved, we won’t need to mark everything as mutable and/or use
a state monad to work with it; in fact, we can just annotate assoc
and valAt
and more or less work with that.
Reconciling Clojure with Frege
Major news: turns out that, with some nudging by Frege’s creator Ingo Wechsung,
I was able to correctly annotate IPersistentMap
and dispense with all the nonsense
about re-implementing an ImmutableHashMap
as a delegate. It has been removed.
We’ll need to tell Frege about how Clojure maps work. This way, we’ll have a map that we
can pass in from Clojure, and as a bonus one we can use from Frege is well. Since Clojure’s
maps are persistent, we won’t need to bother with a state monad to work with them – we just
annotate assoc
and valAt
as pure and we can call them just like Frege functions.
Here’s how it looks:
data PersistentMap k v = native clojure.lang.IPersistentMap where
pure native empty clojure.lang.PersistentHashMap.EMPTY :: PersistentMap k v
pure native assoc :: PersistentMap k v -> k -> v -> PersistentMap k v
pure native valAt :: PersistentMap k v -> k -> v -> v
fromKVList :: [(k, v)] -> PersistentMap k v
fromKVList xs = loop xs newMap where
newMap = PersistentMap.empty
loop ((k,v):xs) map = loop xs $ PersistentMap.assoc map k v
loop [] map = map
We simply told Frege about the assoc
and valAt
methods, as well as the
static field EMPTY
from clojure.lang.PersistentHashMap which contains
a sample empty map. Apparently this works just fine.
The point is, we’ve got a working hash map. Let’s actually pretend we’re going to do something with it. We’ll stub out two functions for this purpose:
make_request uri params = Request uri params
index :: Request -> Response
index req = Response body headers status where
body = ("Hello " ++ name ++ " from " ++ req.uri)
name = PersistentMap.valAt req.params "name" "World"
headers = PersistentMap.fromKVList [("Content-Type", "text/html")]
status = 200
The make_request
is just a wrapper for the Request
constructor to make it
easier on interop for reasons that will become clear later. The index
function
is our erstwhile “view”.
index
has the signature that we want from our handlers:
it accepts a Request
and returns a Response
. If we were writing something
for general-purpose real-world use we’d probably want to use an IO
monad
here so we could access a database or a filesystem or something, but for
now let’s keep it simple.
Here, we just echo whatever was passed in as the "name"
to prove that we
can. Exciting stuff!
Compiling
I used lein-fregec from the
prolific Sean Corfield. You can find the sample
project here: https://github.com/adambard/fregeweb – have a look in the project.clj
for the lein configuration.
Compiling involves running lein fregec
. You may or may not need to run
lein javac
manually first. If it actually compiles, it should work just fine,
as is the habit of Haskell-like languages.
Calling it from Clojure
Now that we’ve got all our figurative ducks in a row in Frege, it’s time to hook that all up in Clojure. First, we have the imports:
(ns fregeweb.core
(:require [compojure.core :refer [defroutes GET POST]]
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]
[org.httpkit.server :refer [run-server]])
(:import [fregeweb Handlers
Handlers$TResponse ]))
Note that hairy guy with the dollar sign – we’ll be hearing about him later.
Our first task is to generate a Request
for use with our “view”. We’ll accept
an incoming Ring request map and pluck out the relevant bits.
(defn map-to-request [req]
(Handlers/make_request
(:uri req)
(:params req)))
Here, we call Handlers/make_request
in
a pretty straightforward way, taking care that our args are the right type
(well, not too much care – there’s nothing stopping a nil uri getting in there,
but that’s something Clojure deals with all the time anyhow). We’re pretty much just trusting on faith that :params
contains
only strings.
We can use the REPL to make sure that works:
(def req {:uri "/test" :params {"name" "Frank"}})
(map-to-request req) ; a TRequest object
(Handlers/index (map-to-request req)) ; a TResponse object
Great! Now we need to turn that response back into something that
Ring will understand – that’s there Handlers$TResponse
comes in.
Handlers$TResponse
is what the Response
datatype we defined
in Frege compiled down to. Turns out all the fields are represented
by static methods, so we just have to call those. The hardest part
was finding the class.
(defn response-to-map [resp]
{:body (Handlers$TResponse/body resp)
:status (Handlers$TResponse/status resp)
:headers (Handlers$TResponse/headers resp)})
There we go – a nice map with :body
, :status
, and :headers
just like
ring expects. There’s one more task to do: we’ll need to make sure that we always pack and unpack
our Request and Response objects for Frege-based views. We can do that by simply
wrapping the handler
(defn frege-view [view-fn]
(fn [req] (response-to-map (view-fn (map-to-request req)))))
((frege-view Handlers/index) req) ; CompilerException!
Whoops! That throws a CompilerException
because Handlers/index
is a static
field. That’s ok though, we can just use a macro instead:
(defmacro frege-view [sym]
`(fn [req#] (response-to-map (~sym (map-to-request req#)))))
((frege-view Handlers/index) req)
; {:body "Hello Frank from /" :status 200 :headers {"Content-Type" "text/html"}}
Now, we can plonk whatever Frege-based views we like straight into our compojure route definition (or whatever) and enjoy (some of) the benefits of Frege’s awesome type system. We can write whatever works best in Clojure in Clojure, and whatever benefits from strict type-checking and mathy syntax in Frege.
(defroutes myapp
(GET "/" [] (frege-view Handlers/index)))
(myapp {:uri "/" :request-method :get :params {"name" ""}})
; {:body "Hello from /" :status 200 :headers {"Content-Type" "text/html"}}
Well, that was a huge pain, but now we’ve done some of the hard bits in terms of interoping the two. I’m not ready to write anything with a database yet, although I have no doubt that it’s perfectly doable, but I’m pleased with the progress so far. Once again, the code for this working example is available at https://github.com/adambard/fregeweb
Final Thoughts
There were some things about this that worked really well, and some that didn’t. Frege and Clojure get along very well on the immutability front, but interop between a dynamic language like Clojure and a (really) strong static one like Frege caused some tension, especially around the handling of nulls. But, thanks to Frege’s ability to partially annotate classes for use in Frege, we were able to just skip anything that could involve a null, which made things a lot easier.
I really noticed that Frege would rarely let me compile a bad program. Even in instances where I mis-annotated some native methods, there would be a java compile error during that phase (Frege compiles to Java source, and then to bytecode from there). Overall this worked quite well. I’m not terribly experienced in Haskell, but it wasn’t too hard to pick up. However, it should be noted that Frege is only compile-time safe, which means that Clojure, dynamic as it is, will happily pass incorrect values to Frege functions and can cause all sorts of nasty errors.
It’s not very difficult to deal with Frege types from Clojure, but it’s not
super straightforward either, so you’ll mostly want to use Frege to deal with Frege
and expose functions rather than types. Perhaps it would even have been appropriate
to return an ImmutableHashMap
from Frege to save ourselves from Handlers$TResponse
–
then we’d just need to keyword-ize the keys and we’d be done.
Overall, the hardest part was working out exactly how to interop between Frege and java. Examples certainly exist, but none covered i.e. static methods, or how to work with a more complex but still pure class/implementation. So, I hope these examples help with that.