Academic Integrity: tutoring, explanations, and feedback — we don’t complete graded work or submit on a student’s behalf.

I\'ve just recently started familiarising myself with functional programming, mo

ID: 645803 • Letter: I

Question

I've just recently started familiarising myself with functional programming, mostly via F#, and there's one particular functional idiom that I'm not fully understanding the benefits of. I've seen it described in a few places, but I'll refer to the version in this article.

The article describes a shopping cart with several requirements, which essentially put it in one of three states: Empty, Active (occupied by one or more items but not yet paid for), or PaidFor. It's argued that by using F#, it's easy to ensure correctness because the compiler will prevent you from performing illegal operations on cart states. For example:

let addItemToCart cart item =
   match cart with
   | Empty state -> state.Add item
   | Active state -> state.Add item
   | PaidFor state ->
       printfn "ERROR: The cart is paid for"
       cart
Only the Empty and Active states have Add methods attached, so if we tried to instead write:

| PaidFor state -> state.Add item
or similar, we'd get a compiler error.

By comparison, an OO approach might (in C#) look like:

interface ICart
{
    ICart AddItem(CartItem item);
}

class EmptyCart : ICart
{
    public ICart AddItem(CartItem item)
    {
        return new ActiveCart(item);
    }
}

class ActiveCart : ICart
{
    public ICart AddItem(CartItem item)
    {
        return new ActiveCart(_items.Add(item));
    }
}

class PaidForCart : ICart
{
    public ICart AddItem(CartItem item)
    {
        throw new InvalidOperationException("Who cares about Liskov anyway?");
    }
}
What I'm trying to understand is what benefit a client gets from having addItemToCart rather than ICart.AddItem. Either way, there's no compile-time check stopping the client from using that function with a paid for cart. And either way, the client has no control over what happens in the error case.

The functional version could be modified to give some control to the client over what happens in the error case (a callback, or some wrapper type over the result), but the object-oriented could be modified to do so just as easily (maybe with a TryAddItem method, which would be a bit more OO-idiomatic).

So what am I missing?

Explanation / Answer

There is no benefit from from having addItemToCart rather than ICart.AddItem. They are essentially identical. Both of those necessarily have a runtime check, because you want to be able to have a variable with any kind of Cart in it, and you won't know at compile time which one will get passed into the function.

Where the benefit comes is when you first implement addItemToCart, or later when you add another operation at the same level of abstraction, say oneClickAddAndPay. The union type will give you a compile error if you try to Add an item to a PaidFor cart, or if you completely forget to account for PaidFor carts at all. The ICart interface can't catch that kind of programming error at compile time.

In other words, the union type can't move all kinds of errors to compile time, but it does move some. If you don't end up adding a lot of functions like oneClickAddAndPay that reuse the existing Add functionality, it won't buy you much.

Hire Me For All Your Tutoring Needs
Integrity-first tutoring: clear explanations, guidance, and feedback.
Drop an Email at
drjack9650@gmail.com
Chat Now And Get Quote