Thursday, July 8, 2010

Arity and Unit Arguments in F# 2.0

While working on an internal domain-specific language, I encountered a case where passing a unit into a function made the syntax somewhat more readable. The completely artificial example below illustrates the concept as the function xfer1. I also tried a two-unit version, which is illustrated as xfer2. (Don't get too stuck on the example; I abstracted these artificial illustrations to avoid clutter; it makes more sense in the actual code.)

(As always, this code is presented "as-is" and without warranty or implied fitness of any kind; use at your own risk.)

type X () =
  let mutable a = [1;2;3;4;]
  let mutable b = [];
  let rec xfer1 () =
    match a with 
    | [] -> ()
    | h::t -> 
      xfer1 (a<-t;b<-h::b)  
  let rec xfer2 () () =
    match a with 
    | [] -> ()
    | h::t -> xfer2 (a<-t) (b<-h::b)
  member this.Xfer1 () = 
  member this.Xfer2 () = 
let x1 = (X()).Xfer1()
let x2 = (X()).Xfer2()
printfn "Done."

However, I was concerned about what the generated code actually contained, so I decided to have a look at the IL. As it turns out, the two cases, xfer1 vs. xfer2, generate very different code. xfer1 results in a function of no arguments. xfer2, on the other hand, results in a function with two unit arguments.

// Signatures:
  internal void xfer1()
  internal void xfer2(Unit unitVar0, Unit unitVar1)
// As called:
  this.xfer2(null, null);

xfer2, I’m sure, represents the canonical case and xfer1 the exception. This exception is very convenient, and it results in more efficient code. But I’m wondering why it wasn’t extended to include functions of any arity as long as all the arguments are unit? I’m sure this behavior is spelled out in the F# spec, and if I find the section, I’ll update this entry.


No comments: