If you aren’t aware of the existence of Lisp Flavoured Erlang then you really need to get on that. It is a thing of beauty, combining the true deliciousness of two amazing languages - Lisp & Erlang. This post assumes you’re aware of LFE in some capacity and I will continue without regard for your level of interest.
Primarily because this is a “My Crazy Idea Post(TM)”. You’ve been warned.
Specs and Types
Currently in LFE there is no method (that I know of) for defining type specs for the Dialyzer system to interrogate. So whilst in Erlang you can incrementally provide greater information about your functions like so:
-spec(add_one(number()) -> number()).
add_one(N) ->
N+1.
This is something that, because of a slight obsession with Haskell and Idris, is something I’m quite interested in making happen. Additionally taking inspiration from the brilliant “Typed Clojure”. It seems like this is something that LFE could draw some real power from.
I am partial to static types and I am the sort of person that considers them to be a real boon to nearly any application. That said I am able to understand the benefit that comes from ‘incrementally typing’ an application. Providing a base level of surety around certain functionality and expanding it or reducing it as required by emergent circumstances.
My Cray-Cray-Type-Type Idea
LFE is of course a Lisp, and so many of you might be wondering why I don’t just write some macros and call it a day. First, I don’t think macros in LFE can do this because there is no way to generate the spec information in the first place. There may be scan/parse steps involved to generate the attribute information. I don’t think that it’s a huge job but it’s not done yet so I would have to take that into account.
The more I read about Dialyzer the more it seems that simply including support for it within LFE would provide some seriously amazing benefits. If you’re the sort of loon that likes these shenanigans. Pimping it out with a dash of extra functionality could prove useful too!
Such as the so
operator from Idris in this highly contrived example:
;; A required persistent truth.
(defun targetInZone ['float 'float -> 'boolean]
((lat long)
(and (and (> lat 0)
(<= lat (doge-arena-limit 'latitude)))
(and (> long 0)
(<= long (doge-arena-limit 'longtitude)))))
((0 0) 'false))
(defun deployDoge [(float x y) -> (so (targetInZone x y))]
((lat long)
(let ((doge (untasked-doge))
(retask-doge doge lat long 'such-investigate)))))
Could be kinda cool.
Interestingly the more straight forward application of types to an LFE application could support the match-lambda
functionality in weird and wonderful algebraic ways.
(defun foo [[a] 'int -> [a]] ;; Provide a generic function signature here
((a b) ['list-float 'int -> 'list-float] ;; More specific constraints
(: lists map (lambda (x) (wow x b)) a))
((a b) ['list-int 'int -> 'list-int] ;; are applied to each branch.
(: lists map (lambda (x) (such x b)) a)))
I’ve not spent enough time with these systems to gain a sufficient understanding of how things work together. From just my initial reading the type system available courtesy of Dialyzer is really really powerful. I am quite literally just thinking out loud about things I think could be fun/cool/useful and not necessarily in that order.
Because this sort of thing in Haskell is sweet.
foo :: [a] -> (a -> b -> a) -> [a]
Dialyzer supports something similar, although you notice the lack of support for the type of the second argument being different to the first.
-spec foo([any()], fun((any(), any()) -> any()) -> [any()]
As some of you may have noticed though, the type specification above includes the deliciousness of polymorphic types. Which in ultra-simplistic terms means types that can take other types as arguments. In the example above, [any()]
is an example of a polymorphic type, list(integer())
would be another. This is a tremendously sexy feature and can be used to great effect when documenting, err, I mean typing, your application.
Similar to Haskell (I said similar, go rage somewhere else) you are also able to specify your own types in a module and construct your own type classes, in a manner of speaking! Jumping the gun here as I’ve not finished my initial readings on the intracacies of type classes but here is an example from Learn You Some Erlang on Dialyzer.
-type red_panda() :: bamboo | birds | eggs | berries
-type squid() :: sperm_whale
%% The type for Food is determined in the specification later on
-type food(A) :: fun(() -> A)
In our LFE system we could have something like
(deftypes red_panda_food '('bamboo 'birds 'eggs 'berries)
squid_food '('sperm_whale)
(food a) '((fun '() -> a)))
In Erlang the above types are used in specifications like so:
-spec feeder(red_panda) -> food(red_panda());
(squid) -> food(squid()).
-spec feed_red_panda(food(red_panda())) -> red_panda().
-spec feed_squid(food(squid())) -> squid().
For our delicious LFE version it’d be neat to leverage this awesomeness:
(defun feeder
#| Match lambdas could support more generic type signatures to enable a quick
understanding at a high level, with more precise return types being available |#
['atom -> a]
((red_panda) [(food red_panda_food)]
...)
((squid) [(food squid_food)]
...))
(defun feed_red_panda [(food red_panda_food) -> red_panda_food]
(...))
(defun feed_squid [(food squid_food) -> squid_food]
(...))
There are obviously nicer ways to handle it and I am going to play around with a few different options to see if anything sticks. I’d like to be able to replicate a Haskell type signature style, predominantly because I believe it to be clear and flexible without adding too much noise.
But that’s not even a concern at the moment since (in case you hadn’t noticed) I still have so much to learn regarding the functionality that is offered by Dialyzer and type systems in general. The growth rate of my reading list shows no signs of abating and like everyone else I must content with ‘real world’ things, I’ve no idea of how much I will be able to commit to this. :(
This post is mostly me just airing my thoughts on this, so don’t expect a link to a repo with a PR to try out or papers in progress under my name (on this topic). I think LFE is one of the most awesome languages around at the moment, even from being neck deep in Haskell and Idris experiments. Type systems have so much to offer when they are built to function as a system for stability, surety, and documentation.
So I think LFE could, COULD, really benefit from having access to this sort of functionality and I sincerely hope I will be able to provide a meaningful contribution. All the same, I think it’d be hell cool to be able to whip out super powerful BODOL style types within your already magnificent distributed Lisp app!
Hopefully more to come ! :)
Manky