F# – inline functions and member constraints

Gener­ic type para­me­ters were intro­duced in C# 2.0, and they gave us the abil­i­ty to write code that works against any type that match­es a set of con­straints and remove the need to cre­ate type-spe­cif­ic over­loads, e.g.:

image

A few years passed, and dynam­ic types was intro­duced in C# 4, which allows us to bypass com­pile-time type check­ing (type check­ing still hap­pens at run­time) when we use the dynam­ic key­word, e.g.:

image

this code works so long as the ‘+’ oper­a­tor is defined for the run­time types of obj1 and obj2, oth­er­wise, you’ll get a Run­time­BinderEx­cep­tion when this code is exe­cut­ed. This kind of type check­ing is often referred to as Duck typ­ing.

Duck typ­ing is a very pow­er­ful lan­guage fea­ture but it also makes your code less safe, as you now face the pos­si­bil­i­ty of hav­ing run­time type errors that would have oth­er­wise been caught by the com­pil­er.

It’s a damn shame that there’s no way for you to com­bine the pow­ers of gener­ics and dynam­ic typ­ing.. because if you could, you’d be able to write DoSome­thin­gElse like this instead:

pub­lic void DoSomethingElse<T, U, V>(T obj1, U obj2) where T + U => V

{

    var obj3 = obj1 + obj2;

    Console.WriteLine(obj3);

}

which stops you from mis­tak­en­ly try­ing to invoke the method with an int and a List<int> at com­pile time, and saves you 10 mins of debug­ging and bug fix­ing time.

Well, would it blow your mind if I tell you that such fea­ture already exists in the .Net frame­work today? No, I kid you not, mem­ber con­straints in F# lets you do just that!

Member Constraints

In F#, you can spec­i­fy a sta­t­i­cal­ly resolved type para­me­ter by pre­fix­ing it with the caret sym­bol ( ^ ), e.g. ^T. These type para­me­ters can be used with mem­ber con­straints which allows you to spec­i­fy that a type argu­ment must have a par­tic­u­lar mem­ber or mem­bers in order to be used.

Take the fol­low­ing F# code for instance:

image

It’s equiv­a­lent to the ear­li­er C# ver­sion of DoSome­thin­gElse, but this func­tion has the fol­low­ing sig­na­ture:

image

which says that the types ^a and ^b used in the func­tion must have a sta­t­ic mem­ber ‘+’ defined on either one of them that can be used on instances of these two types.

You can explic­it­ly state mem­ber con­straints too, for instance:

image

Mem­ber con­straints are very use­ful in those tight spots where you find your­self wrestling with the .Net type sys­tem, it gives you the flex­i­bil­i­ty of duck typ­ing but also the safe­ty of sta­t­ic typ­ing, so real­ly it gives you the best of both worlds!

Restric­tions

How­ev­er, use­ful as it may be, there are some restric­tions to when you can use mem­ber con­straints.

First, they can­not be used with gener­ic type para­me­ters, they can only be used with sta­t­i­cal­ly resolved type para­me­ters.

Sec­ond­ly, they can only be used with func­tions or meth­ods that are inline.

Inline functions

Which brings us to the top­ic of inline func­tions, which as the name sug­gests, cre­ates the func­tions ‘inline’.

But what does that mean? It means that, at every call site to an inlined func­tion or method, the con­crete types for the sta­t­i­cal­ly resolved type para­me­ters (e.g. ^T, ^U) are resolved and the spe­cif­ic code for those types are insert­ed at the call site.

This has the obvi­ous impli­ca­tion that the com­pil­er will gen­er­ate many dupli­cat­ed IL code for inline func­tions and it increas­es the size of your assem­bly.

By con­trast, a nor­mal func­tion or method will gen­er­ate only one instance of that func­tion or method and every call site makes a ‘jump’ to the loca­tion of that func­tion or method in mem­o­ry to exe­cute it. The inlin­ing behav­iour also has some per­for­mance impli­ca­tions which we will go through short­ly.

Restric­tions

The one major restric­tion on inline func­tions is that an inlined func­tion can­not be over­loaded.

When to use inline func­tions

As with most pow­er­ful fea­tures, such as duck typ­ing and inline func­tions, they are often open to abuse.. Per­son­al­ly, I think it’s impor­tant for one to think care­ful­ly about what it is that they’re try­ing to do before decid­ing to use the lat­est and great­est lan­guage fea­tures just for the sake of it.

Take the last code snip­pet for exam­ple, there’s no rea­son why I can’t solve this prob­lem using inter­faces and gener­ic type para­me­ters – define an ISpeak­er inter­face with a Speak mem­ber and requir­ing the para­me­ter x to be an instance of ISpeak­er.

Doing so will achieve the same end goal, there will be a stronger con­tract (a well under­stood, shared inter­face) and the code will be clean­er and eas­i­er to read:

image

As far as I’m aware, there are real­ly only two main rea­sons for using inline func­tions:

Gener­ic Arith­metic

This is the prob­lem inline func­tions were designed to solve, and a prob­lem that can’t be solved by using inter­faces and gener­ics because there’s no way to spe­cif­ic con­straints on oper­a­tors with­out using mem­ber con­straints (and there­fore inline func­tions).

Take this sim­ple add func­tion for exam­ple:

image

with­out mak­ing it inline the para­me­ters a and b will be inferred to be of type int, and there’s no way to make this func­tion gener­ic and usable with every numer­ic type (int32, int64, uint32, uint64, float, etc.). This is a prime exam­ple of when you should inline a func­tion, doing so solves the afore­men­tioned prob­lem:

image

Per­for­mance Opti­miza­tion

In some cas­es, it’s pos­si­ble to get a per­for­mance improve­ment by mak­ing a func­tion inline as it removes the jumps from call sites to the function’s loca­tion in mem­o­ry.

Jon Har­rop gave this exam­ple of how by mak­ing the fol­low­ing fold func­tion inline it can run 5x faster:

image

image

As you can see, in my test, the inline ver­sion man­aged to run a whop­ping 8.5x faster!

This might just be too much temp­ta­tion to those of us con­stant­ly seek­ing bet­ter per­for­mance from our apps, but it’s impor­tant to keep a cou­ple of things in mind too:

  • mak­ing a func­tion or method inline does not always guar­an­tee a per­for­mance improve­ment
  • overuse can add sig­nif­i­cant bloat and makes your assem­bly big­ger and takes longer to load, hence poten­tial­ly imped­ing over­all per­for­mance
  • always measure/profile your app first, don’t pre­ma­ture­ly opti­mize

Further readings

MSDN – Sta­t­i­cal­ly resolved type para­me­ters (pre­ced­ed by a caret ^ sym­bol)

MSDN – Inline func­tions

Sta­t­i­cal­ly typed duck typ­ing in F#