Function Overloading Five Ways in F#

Ever want to overload a function in F# like you do in other languages? So you have a function “foo” that sometimes might take a string, sometimes an int? You write two different functions with the same name that take different parameters, then you just call it. The compiler figures out which one to use.

Yeah, that’s not going to work in F#

I’ll explain why, and how to fix it, but my real purpose in today’s post is to talk about programmer workflow. When you’re learning F# and you get hung up, what should you do?

Yesterday I decided to start writing every now and then about F#. For my first post, I included a few little code snippets I’ve picked up over the years. I also wanted to do function overloading. People say you can’t do it, but you can! I just knew it!

I was wrong. It didn’t work like I thought it would. In fact, I couldn’t get it to work easily at all. So I had to find out why.

First thing I did was find the easiest cut-and-paste answer I could, like any good programmer. It uses a type and static methods. Turns out you can override methods inside a type.

// Function overloading five ways
// First way. Overload a type member with static methods
type FooType =
    static member foo(x:int) = printfn "The int is %d" x
    static member foo(x:string) = printfn "The string is %s" x
FooType.foo 9
FooType.foo "weasels"

That kinda-sorta worked, but it sucked having to always stick that type name on the front of all of the calls. While I’d use it in a pinch, it just didn’t feel right. So I looked around some more.

As it turns out, you can use the “inline” operator to point to a static method on a type. Then the compiler figures out which of those methods to call. This gets you pretty close to something that’s function overloading.

// Second way. Overload a type member and use inline to resolve to it
type FooInt = { thing: int } with 
    static member foo (x:FooInt) = printfn "The int is %d" x.thing
type FooString = { label: string } with
    static member foo (x:FooString) = printfn "The string is %s" x.label
let inline foo (x:^t) =
    (^t: (static member foo: ^t -> unit) (x))
foo { thing = 98 } 
{ label = "Car" } |> foo 

Well heck. Now we don’t have to worry about the carrying the type name around everywhere. but we have to instantiate some stupid dummy-type first just to get the inline magic working. I still don’t like that. Isn’t there some way to use the inline function to do the overloading, but then have the type and such constructed behind the scenes? Let’s try this one.

// Third way. Ugly! Figure it out at runtime
// The problem is that not only will it take
// our parameters, it'll take _any_ parameters.
// Ouch!
let Bar (x:obj) =
    match x with
        | :? string as x->FooType.foo x
        | :? int as x->FooType.foo x
        |_->failwith "We don't do that"
Bar 98
Bar "cats"

The usage looks perfect. Just type in the function name and throw a parameter at it, just like you’d expect. But heck, you can throw anything at that function. There’s no safety at all. The only way to fix that is to return an option. And I don’t want to return options from functions unless I’m being held hostage at gunpoint. It’s a sign of my not-solving a problem and just foisting it on somebody else. That’s a code smell — and I’m not releasing anything into production that has that smell if I can help it.

Ok. Can I check for the type in the inline function, perhaps in this case only taking strings and ints, the types I know to work?

Looking around, I figured out how to check for a subtype. I couldn’t out how to test for one of multiple subtypes. We’re venturing here into type programming, where you’re writing code that controls which types you’ll let do which things. You can look at it as an extremely advanced form of polymorphism.

What we need, I think, is called a “typeclass”, but F# doesn’t have those. We could check and see if a method is present, like we did in our second example.

// Bonus fourth way. Vastly more ugly!
// Flag the type that it's okay, then check during compile
// and reference using inline. Uglier, but safer
type FooBar = {o:obj} with
    static member bar (x:FooBar) =
        match x.o with
            | :? string as x->FooType.foo x
            | :? int as x->FooType.foo x
            |_->failwith "We should never, ever get here"
type System.String with static member DoesFoo()=true
type System.Int32 with static member DoesFoo()=true
let inline barfoo (x:'t when 'a:(static member DoesFoo:unit->bool)) =
    {o=x} |> FooBar.bar 
barfoo 6
barfoo "elephants"

Well that’s a big old hunk-o-fun. You know you’re having fun coding when you start changing the way the system types work. I would get out a flamethrower, but I don’t have one handy.

Way ugly. But look! Now we have type safety and a usage pattern that looks like it should. I guess this is probably the best compromise. Overload all you want inside a nested type, then use an inline call to handle the dispatch and mark your handled types for function safety. After all, this is meant for custom types. You shouldn’t be using with string and int as shown here.

Is there no way to get rid of this nested type/static nonsense? How about Active Patterns? I do some googling around and play around a bit.

// Play with some ideas
let Fun1(x)=printfn "s"
let Fun2(x)=printfn "q"
let Pick1 (|Fun1|Fun2|) (x:int)() = (if x<10 then Fun1 else Fun2)()
let Pick2 (x:int) = ((Pick1 x) x)()
Pick2 4 //outputs "s"
Pick2 15 // outputs "q"

That’s pretty cool. Still ugly as crap, but no nested types. Instead Active Patterns handle the switching. I can use them instead of inlining, but I’m back to the dynamic type problem: to make overloading work, I’m forced to take an obj type and then figure out what to do with it. I could fix that with my type annotation trick from example four.

Meh. Here’s what I have so far:

// Fifth way
// Combine what we have so far:
// Overload all you want, just add type in at end of func name
let FooFooString(x) = printfn "The string is %s" x
let FooFooInt(x) = printfn "The int is %d" x
// then make an active pattern to manage it that takes an obj
let FooFooAP (|FooFooString|FooFooInt|) (x:obj)() =
        match x with
            | :? string as x->FooFooString(x)
            | :? int as x->FooFooInt(x)
            |_->failwith "Back to dynamic typing problem, but without inlines"
// finally wrap it. I must be doing something wrong to have to use
// so much syntactic junk
let FooFoo (x:obj) = ((FooFooAP x) x)()
FooFoo 42
FooFoo "dasdf"

Wow, that’s some ugly syntax, isn’t it? I had to write a second function just to wrap the first one. In fact, I probably missed some things here. There has to be a way to clean that up.

And here’s a good place to stop. After researching and playing around with the code, I now have a better understanding of what’s going on. F# has type inference, which means that it’s always trying to figure out the appropriate types for you, instead of your having to put types on everything like you do in C or C#.

Type inference does not play well with function overloading. As soon as you type the first “foo” into the code, the compiler figures out what types it has to take. It’s locked in. If you type another “foo” in with a different signature, it doesn’t match the first. The type inference system blows up. You can scope this out to a static member inside a nested type in your code — probably because F# keeps track of more stuff when you explicitly and statically add a method to your own type.

Because F# tries to figure out all the type stuff behind the scenes for you, you’re never going to do function overloading like you would in other languages. That’s why the answer will always involve “moving up” and horsing around with the type system itself. Now you know.

It used to be that the Program.fs file was a module, kind of a hidden module. I screwed up thinking it was easy because in my mind I thought I could add static methods there. But it doesn’t work like that. Instead, after a couple of hours of poking around, I now have several options if I want to overload. And that’s plenty.

Who’s got time to chase down this stuff? I’ve got code to get out the door! I hear you. This is the kind of thing you do to “sharpen the saw”, make yourself better understand how the tool you’re using works. When I’m in a production environment, it’s the kind of thing I might do once or twice a week: take 2-3 hours and dig in to figure out how things work. I wouldn’t consider myself a professional otherwise.

Note the difference here. There are programmers who want to be uber-nerds. They’ll learn everything possible about DoodleSquat 7.1 and they’ll be able to tell you the difference between 7.0 and 7.1. Then here are “hack and slash” programmers (we’ve all been there), that are just looking to google something quickly, copy-and-paste, and move on.

Here we’re picking our battles, making sure every week there is at least one battle to fight, and then diving deep enough to understand the issues and be able to make several reasonable suggestions for solutions, then moving on. It’s not that I’m nerdy or not nerdy, I’m just nerdy enough.

And remember: F# is a mixed-mode language. If you come from an OO world and get stuck, fall back to your OO skills, just translate it into F#. Then schedule a “clean up” where you go pure functional, for later in the week, after you get the work done. Because it’s mixed-mode, F# teaches you good functional programming, it doesn’t mandate it.

I love writing in F# because it teaches me to be a better programmer. If you don’t let it teach you, you should probably just stick with whatever you’re doing now.

Thanks for hanging out! If you’re interested in watching Uncle Bob (Clean Code), James Grenning (Embedded TDD), and myself horse around with learning F#, check out these videos.


Follow the author on Twitter

July 6, 2018

6 responses to Function Overloading Five Ways in F#

  1. I’m very interested in the reasoning behind your strong aversion to writing option-returning functions. Sure, if you have a function that can fail, then an exception might be better (or a Result, depending on your use case). But there are lots of situations where you need a strongly typed way to handle a value that might or might not exist. Missing values should be handled correctly at each level, and often the correct way is to just pass it up to the caller, and it’s a higher-level function that actually needs to do something different if a value doesn’t exist.

    • admin said:

      This is an interesting question because it crosses the line between what I consider to be good programming and good architecture. I plan on writing several essays about it. Basically if you’re doing something with the external world, yes, you certainly might not have an answer to whatever the caller’s question is. So that’s fair game. The problem is when option types start propagating throughout the rest of the code. Then I’m four levels down and I’m having to make all of these decisions that should have been made somewhere else.

      So yeah, I’ll generate an option if it reflects the natural state of the problem domain. But heck if I’ll pass one to the rest of my code. The minute I get one I have to ask myself “For this particular application, what happens when it’s null? Do I provide a default? Is it an error?” and so forth.

      It’s related to the onion architecture and total programming. (I learned this the hard way over many years, including a lot of pain with NULLs in relational databases)

  2. Thorsten said:

    You can modify your first example a little bit to get rid of the type name:

    type FooHelper = FooHelper with
    static member ($) (f: FooHelper, x: string) = “String”
    static member ($) (f: FooHelper, x: int) = “Int”

    let inline foo x = FooHelper $ x

    foo “some string”;;
    foo 5;;

    (I’ve got it from http://nut-cracker.azurewebsites.net/blog/2011/11/15/typeclasses-for-fsharp/)

Leave a Reply

Your email address will not be published. Required fields are marked *