Are static members and interfaces idiomatic for functional programming?

I was watching this video (from the latest F# Weekly post) about functional patterns Functional Patterns in FSharp - YouTube and, at around 22 minutes in, the presenter has code like this:

type ICountryInfo =
    abstract member Capital: string

type Country =
    | USA
    | UK
    static member Create =
        function
        | "USA"
        | "America" -> USA
        | "UK"
        | "England" -> UK
        | _ -> failwith "No such country"

let make country =
    match country with
    | USA ->
        { new ICountryInfo with
            member x.Capital = "Washington" }
    | UK ->
        { new ICountryInfo with
            member x.Capital = "London" }

let uk = make Country.UK

printfn "%s" uk.Capital

That got me wondering why they were making it so ā€˜complicatedā€™.
It seemed to me that the code could be easily simplified to something like this:

type Country =
    | USA
    | UK

let create = function
    | "USA"
    | "America" -> USA
    | "UK"
    | "England" -> UK
    | _ -> failwith "No such country"

let capitalOf = function
    | USA -> "Washington"
    | UK -> "London"

let usa = create "USA"

printfn "%s" (capitalOf usa)

I donā€™t see why interfaces, static members and abstract members have been used in the original ā€“ it seems a bit too ā€˜C#-ishā€™ to me.

(I understand that the example used is somewhat contrived so I might not be getting the bigger picture.)

Am I missing some important reason why the presenter has created the code in the way that they have?

1 Like

Disclaimer: I havenā€™t watched the video.

So this is an opinionated issue, so donā€™t take anything I say as gospel. From the code snippets that you posted, I would much prefer the 2nd, as it seems much simpler. Thereā€™s a few reasons Iā€™ll reach for an interface, and I canā€™t speak to whether or not the author of the video intended any of these reasons:

  1. Interoping with other .net stuff. The .net ecosystem is fond of its interfaces, and a lot of times you need to have your own implementations of an interface just to use some of the existing machinery. This is where Object Expressions can be really handy, because you donā€™t need to clutter your type definitions and itā€™s very low boilerplate
  2. Defining different records that share some fields, like
type Person = 
  abstract member Name : string
  abstract member Email : string

type Employee = { Name:string; Email:string; ID:int; Salary:float } with
  interface Person with
    member this.Name = this.Name
    member this.Email = this.Email

type Customer = { Name:string; Email:string; RelationManager:Employee } with
  interface Person with
    member this.Name = this.Name
    member this.Email = this.Email

and then you can have functions that just take in a Person directly and that can cut back on boilerplate.

  1. Passing around generic functions. This one is a little more advanced. Warning: highly contrived example. Say you want to build a list processing function extract that supports filtering the data in different ways:
let employees : Employee list = [ ... ]
let customers : Customer list = [ ... ]
let extract (filterFn:'a list -> 'a list) = (filterFn employees, filterFn customers)

let top50 xs = List.truncate 50 xs
let top50Percent xs = List.truncate (int (float (List.length xs) / 2.0)) xs
let last50 xs = top50 <| List.rev xs
let bottom50Percent xs = top50Percent <| List.rev xs

extract top50
extract bottom50Percent

Youā€™ll get a compile error because filterFn wonā€™t ā€œstay generic.ā€ As soon as you call extract, it has to figure out what 'a is, and it has to be something concrete like Employee or Customer, but it canā€™t remain as a generic function that could accept either. You can fix this by altering it to use an interface:

type FilterFn =
  abstract member filter : 'a list -> 'a list

let extract (filterFn:FilterFn) = (filterFn.filter employees, filterFn.filter customers)

let top50 = { new FilterFn with member _.filter xs = List.truncate 50 xs }
let top50Percent = { new FilterFn with member _.filter xs = List.truncate (int (float (List.length xs) / 2.0)) xs }
let last50 = { new FilterFn with member _.filter xs = top50 <| List.rev xs }
let bottom50Percent = { new FilterFn with member _.filter xs = top50Percent <| List.rev xs }

f top50
f bottom50Percent

Thereā€™s probably other good reasons to reach for interfaces beyond the three that Iā€™ve given here, but I would tend to agree that unless you have some need that an interface is specifically solving, itā€™s much more common to stick to plain functions.

I suspect a lot of folks come to F# from C# (I know I did). I personally think itā€™s best to try to tear down a lot of what you know and rebuild it with FP principles in mind, but I suspect thereā€™s a lot of folks out there who appreciate some of the FP goodness that F# brings but are still stuck in sort of an OO frame of mind, and F# will happily accommodate those folks.

Thanks for the reply.

I think the video is part of a series for C# programming so itā€™s likely to be ā€˜skewedā€™ towards C# programmers.

I can understand the need for interoperability so I can see where you are coming from in point 1 (even though I donā€™t know what an ā€œobject expressionā€ is ā€“ something else for me to learn).

For point 2 I donā€™t understand why the version with interfaces is ā€˜betterā€™ than this version:

type PersonalDetails = {
    Name : string 
    Email : string }

type Employee = {
    PersonalDetails : PersonalDetails
    ID : int
    Salary : float }

type Customer = {
    PersonalDetails : PersonalDetails 
    RelationManager : Employee }

ā€¦although Iā€™ve not tried to use the interface version so maybe doing so will show me something I donā€™t yet know.

For point 3, I have no idea whatā€™s going on there but thatā€™s purely because of my own shortcomings.

I think Iā€™ll stop watching that video in case itā€™s teaching me stuff I might not want/need to learn just yet.

I think Iā€™m coming slowly towards the opinion that I should be keeping clear of interfaces/members unless I absolutely need to use them because of some factor, be that interoperability or some special case.
As long as I know that interfaces/members are there, and can be used if needed, then thatā€™s probably enough at the moment.

Thanks again.

I also havenā€™t seen the video but just from the code snippetā€¦

Iā€™d say this is trying to demonstrate multiple language features or possibilities rather than a good solution to a specific concrete problem.

The static member ā€˜Country.Createā€™ would often be preferable to just a ā€˜createā€™ function (as in your sample) because there could be many types for which you would like a ā€˜createā€™ function and unless you make it part of a module or a static member of a type then you canā€™t do that.

The ā€˜makeā€™ function in the video sample is actually a good example of how to use object expressions (a splendid feature that C# lacks) and return objects that support an interface together with lambda expressions/closures that capture data for that implementation without ever having to declare a class for it with all the associated boiler plate and potentially multiple variations of it.

The static member on a record / DU is an organization strategy. So you can use Country as a type and also a container for related functions.

// usage as (unnecessary) type annotation
let country : Country = USA
// related Create fn in Country container
let country = Country.Create "USA"

You can express it in F# without static members:

type Country =
    | USA
    | UK

module Country =
    let Create =
        function
        | "USA"
        | "America" -> USA
        | "UK"
        | "England" -> UK
        | _ -> failwith "No such country"

For my code base, I would also fix a few things. Lower-case Create to match F# naming conventions. Use Result or Option or an unknown case instead of throwing an exception for unmatched country, unless it really should crash the program. And not much harm here, but Iā€™d use match over function.

In F#, the module way would have the exact same usage as the static member way. But from C#, the usage is slightly different:

// static member way
Country country = Country.Create("USA");
// module way, F# adds "Module" suffix
Country country = CountryModule.Create("USA");

The difference is because F# compiles types to instance classes and modules to static classes and it wonā€™t merge the two during compilation. When the compiler encounters Country module, it has already used that name for an instance class. So it makes the moduleā€™s static class with the Module suffix to avoid naming conflicts. Then I guess F# compiler specially handles this case to hide that fact from F# code.

Also the interface usage here seems more to demonstrate object expressions, which are pretty cool when you need them. If I were creating ICountryInfo from F# code, I would use a record instead. The only interface Iā€™ve created from F# so far has been a marker interface. I moved away from that (to DUs) later. But Iā€™ve implemented a few interfaces to integrate with C# libraries. Sometimes with object expressions.

realparadyne:
If this were real code I would be making modules, as you say, to contain the types and their related ā€˜createā€™ functions.
(Iā€™m trying to follow Scott Wlaschinā€™s advice in his ā€œDomain Modelling Made Functionalā€ book.)

I need to look to see what ā€œobject expressionsā€ are as Iā€™ve not yet come across them, except in this thread.

kspeakman:
Most, but not all, of what you said has gone way over my head but thatā€™s a result of my lack of experience/knowledge rather than anything else.

One thing I understood, but was curious about, was why you said you would use match over function. Is there a reason for this?

Using function is not a big deal here, but I tend to avoid it, as it is a point-free style. Compare:

// point free Create function
let Create =
    function
    | ā€¦

// normal Create function
let Create countryStr =
    match countryStr with
    | ā€¦

The main benefit of point free is conciseness/DRY. But using (non-point-free) match makes it more readable and extensible. Readability: I can instinctively identify functions by them having arguments. With point-free I have to use heuristics like looking for a verb as a name and examining the signature. Point-free signature is also less helpful to callers since the arguments donā€™t have names to provide hints. Extensibility: If I want to do something with the argument before matching, like normalize it (i.e. trim and lowercase), Iā€™ll have to convert it to a match anyway.

Don Syme also has a presentation about F# code he deems good and less good. IIRC, one of the parts was something like ā€œpoint free is not a virtue.ā€

P.S. Object expressions. To work with OO-based libraries, you would normally have to define a class which inherits an abstract class and/or implements interfaces. Object expressions let you instantiate an abstract class or interface directly. So you donā€™t have to maintain a class definition in your code. Itā€™s a shortcut you can use if it makes sense. For more complicated integrations, you might want to stick with defining classes.

kspeakman:
Thanks for the extra information.

You have confirmed something for me. Iā€™ve been ā€˜toyingā€™ with function for a little while but I agree that making the match explicit makes it easier to read as I donā€™t have to try and work out whatā€™s happening. And there are the other benefits you have stated.

I donā€™t understand ā€œpoint-freeā€ yet (along with many things) but Iā€™ll certainly look at the video you linked to.

I think Iā€™m going to put object expressions in the ā€˜dive into only when necessary binā€™ since Iā€™ve got so much else that I need to learn before adding that extra complication.