December 21, 2014

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.