Pondering Axum + F#

It’s been a while since I’ve posted about Axum as I’ve been posting about other asynchronous and parallel programming models.  After two releases of the Axum standalone language and lots of good user feedback, it’s time to ponder what could be with Axum.  Overall, the goals of Axum to provide an agent-based concurrency oriented system is important as we consider the emerging hardware trends.  Many of these ideas would in fact benefit most languages, whether mainstream or not.  With all the success that it has had, there have also been some issues as well, which leads me to wonder, what else could we do here?  Let step through some of those issues today and see where we could go.

Should It Be Its Own Language?

One of the highlighted concerns was the need to have yet another language.  Creating and maintaining languages is a large effort, not to be undertaken lightly at any organization.  To base your language on C# while adding additional constructs was a great way to get started and create some buzz and buy-in, but it also brought its own set of problems. 

One problem is that it is an enormous effort to keep on par with the C# compiler and add these constructs on a continuing basis.  The Spec# project, also based upon C# realized that it wasn’t feasible to keep up with the rapid pace of the language and instead went as a language neutral approach.  Not only keeping up with the language, but to enforce many functional programming constructs that Axum embraces such as immutability and purity are harder to enforce given a language which is built upon object-oriented and imperative approaches, an approach which encourage the encapsulation and manipulation of state.  So, instead of creating a separate language, why not fold these constructs into an existing language instead?

The Case For F#

Given the impedance mismatch between the C# language and the overall goals of Axum for a safe, isolated, agent-based system that is great for concurrency, why not consider another language such as F#?  Now that F# is a first-class citizen within Visual Studio going forward, there can be a strong case made.  Given some of the features of F#, which we will discuss, could potentially be a happy marriage with Axum.  The F# language and its associated libraries have many of the constructs that are essential to an agent-based messaging system, including immutability (albeit shallow), encouraging side effect free programming, and library functions such as the mailbox processor messaging classes.

In Praise Of Immutability

In the world of concurrency we find that shared mutable state is evil.  Often we have to protect certain parts by using low level constructs such as locks, semaphores, mutexes.  Because of this, we find that most people tend to avoid concurrency programming as it tends to be too hard to get just right.  Instead, communication through message passing in languages such as Axum and Erlang, immutability is the standard.  This way, we can freely reference the values without any worry of whether they will change underneath us.  Providing first-class support for immutability goes a long way towards making safe concurrency programming easier for the masses.  With the F# language, we get that approach by default and with rich types such as records, discriminated unions, lists, tuples and more. 

Of particular note, Discriminated Unions are quite useful in messaging systems to define the types of messages your system accepts.  For example, we could model our auction messages quite easily using discriminated unions to define which interactions are legal, such as the following code:

type AuctionMessage =
  | Offer of int * MailboxProcessor<AuctionReply>
  | Inquire of MailboxProcessor<AuctionReply>
and  AuctionReply =
  | Status of int * System.DateTime
  | BestOffer
  | BeatenOffer of int
  | AuctionConcluded of 
      MailboxProcessor<AuctionReply> * MailboxProcessor<AuctionReply>
  | AuctionFailed
  | AuctionOver

In this case, we have succinctly defined what messages can be processed.  Adding to this, Axum, through the use of data-flow networks and channel protocols could be a nice compliment to determine how these messages are processed.   Where else could they match up nicely?

Built For Concurrency

To talk about a potential marriage between F# and Axum, we need to talk about the language and its libraries and how they support concurrency.  From first class events, to asynchronous workflows, to mailbox processors, F# has a wide array of asynchronous and parallel programming libraries available out of the box.  In particular, the asynchronous workflows and the mailbox processor could match well with the Axum programming model.  Interestingly, Luca Bolognese has built upon the mailbox processor for his LAgent framework to show some more interesting scenarios where agent based programming could work.  This includes such things as advanced error handling, hot swapping, implementing MapReduce and so on.

With little effort, we can model such things as the standard ping-pong example using F# mailboxes:

let (<--) (m:MailboxProcessor<_>) msg = m.Post(msg)
type PingMessage = Ping of MailboxProcessor<PongMessage> | Stop
and  PongMessage = Pong

let ping iters (pong : MailboxProcessor<PingMessage>) =
  new MailboxProcessor<PongMessage>(fun inbox -> 
    pong <-- Ping inbox
    let rec loop pingsLeft = async { 
      let! msg = inbox.Receive()
      match msg with
      | Pong -> 
          if pingsLeft % 1000 = 0 then
            printfn "Ping: pong"
          if pingsLeft > 0 then
            pong <-- Ping inbox
            return! loop(pingsLeft - 1)
          else
            printfn "Ping: stop"
            pong <-- Stop
            return () }
    loop (iters - 1))
            
let pong () =
  new MailboxProcessor<PingMessage>(fun inbox -> 
    let rec loop pongCount = async { 
      let! msg = inbox.Receive()
      match msg with
      | Ping sender -> 
          if pongCount % 1000 = 0 then
            printfn "Pong: ping %d" pongCount
          sender <-- Pong
          return! loop (pongCount + 1) 
      | Stop -> printfn "Pong: stop"; return () }
    loop 0)

let ponger = pong()
let pinger = ping 100000 ponger
pinger.Start()
ponger.Start()

And you’ll notice from all of this, there are no mutable values anywhere nor shared global state and all communication happens through message passing.  We have certain limitations here such as we cannot remote these calls, so all interaction happens inside the single application.  Not only that, but how do we enforce no shared state?

What Would It Look Like?

So, now that we looked at some of the reasons why F# and Axum might be a good marriage, what might it actually look like?  That’s a great question!  Some of the concepts from Axum could map perfectly to F# whereas some might need a little more coaxing.  Let’s go back to the basic building blocks of Axum.  They consist of the following:

  • Agents and domains
  • Channels
  • Schema
  • Networks

The first three items are concerned mostly with state isolation and the exchange of information between our isolated regions of our application, while the last item deals more with the how messages are passed. 

The domain is the most basic isolation concept where all data is encapsulated and only constructors are exposed outside of the domain definition.  Agents are defined to run within the isolated space of a domain with each agent on a separate thread of control.  These agents are exposed to one another through passing messages back and forth via channels which define ports through which our data flows.  The data that passes between our domains can be defined in a schema, similar in nature of XML Schemas, to define structure and rules of our data.  Now to think about message passing, F# already has asynchronous behavior already built in to the mailbox processor, so we could take advantage here and build on top of it and our networks could guide us as to what order messages are passed.

Let’s look at some possible code examples of what things might look like.  I’ll choose just a couple as they might work nicely out of the box.  Let’s first look at how we might define a port which accepts a given message. 

type ProductInput = Multiply of int * int | Done
type ProductOutput = Product of int | AckDone

This way, we could define what our payload type for both incoming and outgoing.  Another area would be to look how schemas might be done.  In our earlier example, we used simple data types such as integers, but what about more complex types?  Using F#, we could think of using records here to simulate the not only the data, but what is and is not required:

type PersonSchema = 
  { Name     : string
    Employer : string
    Age      : int option }

let matt =
  { Name = "Matthew Podwysocki"
    Employer = "Microsoft Corporation"
    Age = None }

By marking the fields we require as standard fields and those which are optional as option types works out nicely.  Using F#, we’re able to have concise syntax with a language that is already defined and quite flexible.

Conclusion

As you can see from this post, a potential marriage between Axum and F# would certainly be a great fit.  Instead of focusing on Axum as a separate language, focusing the effort on top of an existing language which matches many of the design goals can bear a lot of fruit.  I believe Axum is important for the .NET programming stack, giving us more options on how to deal with concurrency and changing hardware architectures.  What do you think?

3 Comments

  • No, I think that is a terrible idea. F# is already getting too big, fine for a research language like say Haskell but for a production language, maybe not so so good. There are already a lot of parallel,asynchronous, concurrent, whatever constructs available for use with the language. Why Axum? Don't make people's choice for them. No point retrofitting both Axum and F# with features that may compromise both for the handful of people who would use or be interested in it.

    I say instead release it as a library. Then people get the choice to use it or not while you get the benefit of full control of direction and not back and forthing with the F# design team. In addition you add to .net and not just F#. There are languages on .NET other than F# where this would make a good fit as well. You reach more people so instead of a subset of F# you reach subsets of F#, C# Vb.net etc who are brave/wise enough to use it. Adding more to the community. Do not underestimate the creativity with which a community can drive your product in ways you never imagined.

  • @D

    I of course disagree with you on a number of fronts.

    First of all, F# is a large language, I'll grant you that. As a research language, it had that luxury of going in any number of directions, which can be both good and bad. I feel that adding Axum to the current asynchronous and parallel computing power would only add, and not detract from the language. After all, many of the things Axum needs to thrive (immutability, side effect free functions, etc) are already status quo among developers using the language.

    Axum itself was a small language to begin with with very pointed uses which the average developer may never use, such as agent based concurrency. This already is a fringe topic to many developers, so the added burden of F# (as you seem to indicate it may be), is negligible. Those who understand functional programming and its intents will get Axum's concepts quite well. That's not to say that C#, VB or other languages can't pick them up as well.

    I'll have to disagree with releasing it as a library. Many of the things it needs in order to be successful, such as immutability (deep, not shallow), side effect free functions, are harder to enforce on other languages. There was a reason that this was to be its own language because of this.

    Matt

  • N2dI9R I loved your blog article. Really Great.

Comments have been disabled for this content.