Introducing joinads

Joinads is a general-purpose extension of the F# computation expression syntax (also called monadic syntax) in F# and is mainly useful for concurrent, parallal and reactive programming. The extension adds a new piece of notation, written match! that can be used to compose computations using non-deterministic choice, parallel composition and aliasing.

For example, when programming with futures (the Task<T> type), you may want to implement logical "or" operator for tasks that returns true immediately when the first task completes returning true. When programming with events (the IObservable<T> type in .NET), we'd like to wait for the event that happens first. Finally, when programming using agents, we sometimes need to wait only for certain types of messages. All of these problems can be solved, but require the use of (sometimes fairly complicated) functions.

Joinads make it possible to solve them directly using the match! syntax. For example, the following snippet shows the "or" operator for tasks:

open System.Threading.Tasks
open FSharp.Extensions.Joinads

/// Returns a Task that produces the given
/// value after the specified time
let after (time:int) res = (...)

/// Short-circuiting implementation 
/// of the 'or' operator for tasks. 
let taskOr t1 t2 = future {
  match! t1, t2 with
  | true, ? -> return true
  | ?, true -> return true
  | a, b    -> return a || b }

// Apply 'or' to a task that returns true after 100ms
// and a task that returns false after 2 sec.
let res = taskOr (after 100 true) (after 2000 true)
printfn "\nCompleted with result: %b" res.Result

The match! notation intentionally resembles pattern matching. However, instead of pattern matching on actual values, we are pattern matching on computations of type Task<'T>. The patterns used in the clauses of match! can be either normal F# patterns or a special pattern ? which specifies that the clause can run even if the value of the corresponding computation is not available.

In the above example, the last clause specifies that both of the computations have to complete, producing bool values a and b. When the clause matches, the result is calculated as a || b. The first two clauses are more interesting. For example, the pattern true, ? specifies that the first computation should produce true, but the second does not have to finish for the clause to match. As a result, when one of the computations returns true, the match! construct does not wait for the other computation and immediately returns true.

If you run the above code (run the last two lines separately to get a readable output), then you can see that the task created by taskOr completes after 100ms, even though the second argument of taskOr is still running. If you change the result of the first argument to false, the computation needs to wait for the second value, and so it take 2 seconds to complete.

Applicative functors

Aside from the support for joinads, the F# interactive console on this site also supports syntax for programming with applicative functors (also called idioms). Applicative functors are an abstract notion of computations that is weaker (and thus more common) than monads - every monad is an applicative functor, but not all applicative functors are monads. For more information about the extensions for applicative functors, see a blog post that discusses them.

Joinads in Haskell

Joinads are an abstract concept that describes what operations are required to implement pattern matching on monadic computations. This means that the general idea can be used in any programming language.

In addition, languages that already have some syntactic support for writing monadic computations can be extended with a special syntax for joinads. This tutorial shows numerous examples of the match! syntax in F#, but there is also an implementation for Haskell.

The Haskell extension is currently available as a patch on GitHub and you can find more information in a our papers on joinads or in the GHC Trac description. Akin to the do notation and case construct, the patch adds docase notation, which allows pattern matching on monadic computations that implement three additional operation. The previous F# example could be written in Haskell as follows:

taskOr t1 t2 = 
  docase t1, t2 of
    True, ? -> return True
    ?, True -> return True
    a, b    -> return $ a || b

The syntax is quite similar to the F# version, but there are several differences. Most notably, thanks to type-classes, the above code is polymorphic over the actual joinad - any type that implements a couple of involved type-classes can be used with this function.

namespace System
namespace System.Threading
namespace System.Threading.Tasks
namespace FSharp
namespace FSharp.Extensions
namespace FSharp.Extensions.Joinads
val after : int -> 'a -> Task<'a>

Full name: TryJoinads.after


 Returns a Task that produces the given
 value after the specified time
val time : int
  type: int
  inherits: System.ValueType
Multiple items
val int : 'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
  type: int<'Measure>
  inherits: System.ValueType


--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int
  type: int
  inherits: System.ValueType
val res : 'a
future { System.Threading.Thread.Sleep(time)
           return res }
val taskOr : Task<bool> -> Task<bool> -> Task<bool>

Full name: TryJoinads.taskOr


 Short-circuiting implementation
 of the 'or' operator for tasks.
val t1 : Task<bool>
  type: Task<bool>
  inherits: Task
val t2 : Task<bool>
  type: Task<bool>
  inherits: Task
val future : FutureBuilder

Full name: FSharp.Extensions.Joinads.TopLevelValues.future
val a : bool
  type: bool
  inherits: System.ValueType
val b : bool
  type: bool
  inherits: System.ValueType
val res : Task<bool>

Full name: TryJoinads.res
  type: Task<bool>
  inherits: Task
val printfn : Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
property Task.Result: bool