Why OO Matters (in F#)

F# is a functional-first programming language that comes with a substantial object-oriented feature set. It is so feature-complete in fact, that almost any C# class can be ported over to F# code with little substantial alteration.

However significant, this subset of the language is seeing limited appreciation from the community, which I suspect is partly fuelled by the known criticisms of OOP and partly by a desire to be different than C#. After all, this is a functional-first language so we can just replace all our classes with functions. There is also the opinion that OOP in F# merely serves as a compatibility layer for .NET, so it’s really only there to cover those unfortunate scenarios of having to use a library that accepts interfaces.

Enabling Abstraction

One of the most important aspects of maintaining a nontrivial codebase is controlling complexity. Complexity can be contained by partitioning code into logically standalone components whose implementation details are hidden behind appropriately designed abstractions. In his excellent Solid and Functional article, Vesa Karvonen argues that selecting the correct abstraction is a hard problem, and that functional programming is no silver bullet in dealing with that. This resonates a lot with me, and I strongly encourage everyone to read the full article.

That said, Vesa is framing the article in Standard ML which supports a full-blown module system. Modules can be abstracted using signatures or they can be parameterized by other modules using functors. Modules are the predominant means of abstraction in the ML ecosystem. In Haskell, it is type classes and higher-kinded types. In F#, modules are intentionally stripped of any complicated features, effectively functioning as a mere namespacing construct.

My claim is that there are inherent limits to what can be expressed using just F# modules and functions, in terms of enabling good abstraction. Luckily, we can always make use of the next best thing, which is F# OO. The thesis of this article is that strategically admitting elements of OO in an F# codebase significantly improves quality and maintainability. While I cannot conclusively prove this within the confines of a single blog post, I will try to provide hints as to why this is.

Classes as Value Parametric Modules

It is often the case that an API exposed through a module must be context aware. Typically F# developers address this by adding extra parameters in every function:

module MyApi =
    let function1 dep1 dep2 dep3 arg1 = doStuffWith dep1 dep2 dep3 arg1
    let function2 dep1 dep2 dep3 arg2 = doStuffWith' dep1 dep2 dep3 arg2

While this does work well in simple cases, it does not scale nicely as dependencies increase. It would typically prompt the developer to group arguments in context records:

type MyApiContext = { Dep1 : Dep1 ; Dep2 : Dep2 ; Dep3 : Dep3 }

module MyApi =
    let function1 (ctx : MyApiContext) arg1 = doStuffWith ctx.Dep1 ctx.Dep2 ctx.Dep3 arg1
    let function2 (ctx : MyApiContext) arg2 = doStuffWith' ctx.Dep1 ctx.Dep2 ctx.Dep3 arg2

This complicates the implementation even more both in the definition site and in the consumption site. In practice, you either end up with one context type per component or one God context for the entire application. Even more importantly, this approach often violates encapsulation concerns, pushing the burden of gathering dependencies to the consumers of the API, every single time they do consume the API. Partial application also does little to address any of these concerns in nontrivial contexts.

Less experienced developers might be prompted to do something even worse: lift dependencies to module values.

module MyApi =
    let dep1 = File.ReadAllText "/Users/eirik/connectionstring.txt"
    let dep2 = Environment.GetEnvironmentVariable "DEP_2"
    let dep3 = Random().Next()

    let function1 arg = doStuffWith dep1 dep2 dep3 arg
    let function2 arg = doSutffWith dep1 dep2 dep3 arg

This is bad for many reasons: it makes the API reliant on global state, introduces unneeded side-effects, and pushes app configuration concerns deep inside the guts of our codebase. What’s more, module value initialization compiles to a static constructor for the entire compilation unit so the exact moment of execution is largely unpredictable. Initialization errors manifest as TypeInitializationExceptions which are difficult to debug.

Contrast the situation above with the elegance afforded by a plain old class:

type MyParametricApi(dep1, dep2, dep3) =
    member __.Function1 arg1 = doStuffWith dep1 dep2 dep3 arg1
    member __.Function2 arg2 = doStuffWith' dep1 dep2 dep3 arg2

An API object could be created once at application initialization, or as many times required depending on context. It’s also more amenable to testing. I should add that this approach is essentially just as “functional” as the approaches above, since it’s merely composing dependencies to expose a context-sensitive API. Importantly, it achieves this in a much simpler way both in the definition site and consumption site, which pays great dividends if realized in big codebases.

Expressive APIs

An important attribute of method-based APIs is that they allow for greater expressive power, in two important ways:

  1. Named/Optional parameters: unlike OCaml, whose functions support out-of-order named argument passing and omitted optional arguments, F# functions support neither. Luckily, we can do this using F# methods. I find this to be an immensely powerful tool when exposing non-trivially parameterizable functionality. A function that explicitly accepts 10 optional parameters is not acceptable; a method that accepts 10 optional arguments works like a charm.
  2. Method overloading: because function names like connect' and bind2 are simply not good enough when exposed in a public API.

More Powerful Types

The type system afforded by .NET is strictly more powerful than what can be expressed using modules and functions. For example, the interface

type Scheduler =
    abstract Run<'T> : Async<'T> -> 'T

encodes a kind of function that cannot be expressed in terms of proper F# lambdas. When combined with subtyping, it is possible to effectively encode existential types and Rank-N polymorphism. Even GADTs are possible, with minimal augmentations of the type system.

In practice, it is possible to leverage that additional power very effectively. In fact, F# makes it easy to define generic function literals using object expressions. This is also how the TypeShape library has been made possible.

Abstracting Modules

Functions are the unit of abstraction in F#, but that unit is often insufficient when abstracting APIs. This prompts developers to adopt an approach where abstract APIs are surfaced as either records or tuples of functions:

type Serializer =
    {
        Serialize : bool -> obj -> string
        Deserialize : bool -> string -> obj
    }

According to the F# design guidelines, use of records for building APIs is discouraged and recommends using regular interfaces instead.

I strongly agree with this recommendation for a multitude of reasons: interfaces are more powerful since they support generic methods, named arguments and optional arguments. An interface is less likely to be defined in terms of closures, making it easier to reason about when viewing from a debugger.

So the example above could be rendered as an interface like so:

type Serializer =
    abstract Serialize<'T> : preserveRefEq:bool -> value:'T -> string
    abstract Deserialize<'T> : preserveRefEq:bool -> pickle:string -> 'T

The most important aspect of this approach is readability. It is easier for a consumer of this interface to anticipate what the purpose of each argument is, in the same way that it is easier to understand a record of functions over a tuple of functions.

Representing Illegal State

A lot of proverbial ink has been spent describing how we should strive to make illegal states unrepresentable. However, I do fail to see how this could be fully realized given that the functional core of F# only consists of algebraic data types. Take for example an oft-quoted email type:

type Email = Email of string

The following values are valid instaces of type Email:

Email null
Email "John Smith"
Email "eirik@foo.bar'; DROP TABLE dbo.Users"

If we truly care about illegal states, the obvious alteration to the type above ought to be the following

type Email = private | Email of string
with
    member this.Address = let (Email addr) = this in addr
    static member TryParse(address : string) =
        if isValidEmail address then Some(Email address)
        else None

But really, this is a just a class encoded by a union. The implementation below is simpler:

type Email private (address : string) =
    member __.Address = address
    static member TryParse(address : string) =
        if isValidEmail address then Some(Email address)
        else None

NB the previous implementation might in fact be warranted in cases where free structural equality or comparison are needed. But for all intents and purposes, both approaches effectively subscribe to OO-style encapsulation.

OO And Purity

The relationship between OO and purity can be a frequent avenue for misconception. Occasionally someone will claim that by admitting objects we are ipso facto forsaking purity. On the contrary, I do claim that these really are orthogonal concerns. Just as a lambda is capable of producing side-effects, objects can be designed for purity. Good examples of this are Map and Set in the core library. The lambda is really just an abstract class with a single virtual method and lots of syntactic sugar. There is nothing fundamentally setting it apart from objects once you exclude the syntactic aspect.

Conclusions

So, is this a call to go full-blown OO in F# projects? Should we be digging up our old GoF copies? Are design patterns up there in the functional curriculum together with profunctor optics? Are inheritance and class hierarchies sexy again? No!

I am in fact proposing that there is a third way, where functional and OO components coexist, with one paradigm complementing the other. This is hardly a new idea. Quoting from the F# design guidelines:

F# is commonly called a functional-first language: object, functional and imperative paradigms are all well supported, but functional programming tends to be the first technique used. Many of the defaults of F# are set up to encourage functional programming, but programming in the other paradigms is effective and efficient, and a combination is often best of all. It is a common misconception that the functional and object programming methodologies are competing. In fact, they are generally orthogonal and largely complementary. Often, functional programming plays a stronger role “in the small” (e.g. at the implementation level of functions/method and the code contained therein) and object programming playe a bigger role “in the large” (e.g. at the structural level of classes, interfaces, and namespaces, and the organization of APIs for frameworks).

In my 6 years of working with F#, my style has gradually shifted towards embracing this approach. A few examples:

  • I typically write the implementation of a large component in the functional style behind a private module, then expose its public API as part of a standalone class. I find that method-based APIs are friendlier to consumers unfamiliar with the implementation.
  • I use records and unions for encoding internal representations and classes for encapsulating publicly visible instances. A very good example of this is the F# map implementation.
  • I rarely expose records and unions as part of a public API unless it is abundantly evident that all possible instances for the given types are valid in their context of use. This does not happen often in my experience.
  • If a module is exposed as part of a public API, care must be taken so that the number of arguments is small and behaviour can be predicted by reading the type signature of the function alone. The core List and Array modules are a good example. Avoid using modules to expose complex functionality like the Async API.

I remember reading a few years back Simon Cousins’ NOOO manifesto, which stands for Not Only Object-Oriented development. In retrospect I find this to be an excellent name for a manifesto, if only because “Not Only OO” is not the same thing as “No OO”. So here’s a proposal to revive that manifesto, perhaps with the understanding that “Not Only OO” also implies “Not Only FP” in the context of F#.

Advertisements
Why OO Matters (in F#)

56 thoughts on “Why OO Matters (in F#)

  1. Interesting to see your logic. I have gone the other way on your key decisions and hence come out with the opposite conclusion:

    1) Function parameter count problem:
    For me a function that required a large number of parameters is big code smell. It means the function is doing too much and/or there is not enough type structure. If a function takes ten parameters it should really be a network of multiple functions that each take 2-5 parameters. For me your MyApiContext should be found to have meaning in the domain and the structure of these types worked out.

    2) Records vs Interfaces for API:
    I know its recommended practice to use interfaces for a set of functions. I find this limiting and produces more boilerplate and harder to refactor. I’ve just built a hand coded serializer based on haskell Data.Serialize (https://hackage.haskell.org/package/cereal-0.5.4.0/docs/Data-Serialize.html) using records and am super pleased with it. It composes easily and gives me backward compatibility without any reflection or code gen.

    3) Representing illegal state:
    I disagree that your class is simpler when you take into account using it and functionality such as comparison, immutability and serialization.

    Anyway interesting and each to their own.

    1. eirik says:

      1) The important aspect in this point is encapsulation for me. Few things a context record can achieve in that respect. I use that pattern of course, just never in the large.

      2) Serialize uses type classes. Function records are hardly that.

      3) Definitions of “simple” vary. I wonder though how a single-case union makes serialization more amenable in this case.

      1. 2) When I say based on I just mean using the API of a Get and Put monad. No funky type class workarounds.

        3) How should I serialize your class? Which private member needs to be saved? This Email type is possibly going to become more complicated that just a string. Just look at the illegal state blog posts.

      2. eirik says:

        How should I serialize your class? Which private member needs to be saved? This Email type is possibly going to become more complicated that just a string. Just look at the illegal state blog posts.

        Surely, this question is only valid in the context of auto serializer derivation. When using picklers, like your library does, you just manually specify what needs be serialized.

      3. eirik says:

        What’s private in this case? There’s a pretty public property exposing the value and a constructor to match. The DU approach is hardly simpler. Perhaps if you look at both compiled types through ILSpy might convince you.

      4. eirik says:

        There is no way in which classes can effectively replace union types, and I never claimed the opposite. The example is merely referring to the single case DU wrapper anti-pattern. It could have been an F# record really, I just picked the more popular example.

  2. Forgot to add to 1):

    If a function takes 10 independent parameters just consider the huge permutations for testing that are required. When you think of it this way you start to see that they are not really independent parameters and the function needs to be broken up.

  3. Hey,
    I find your article pretty fascinating – the reason being, that I agree and simultaniously disagree.
    I agree with it in sofar that this is the way it is and also in sofar that many members of the F# community regard what you have written as idiomatic F# code.
    I start to disagree about the dichtonomy of records of functions vs interfaces (althou I see their uses some times) and I disagree when you start to encode business types as (mutable) classes.
    If you go that route then my question is indeed: why F# at all as you loose so much of FPs benefits – then C# with its modes mix of newly added FP features might be a better way.
    Nevertheless I understand your highlevel point which I’d like to condense (hopefully correctly) as: Use the tools that are offered to you and in the case F# those are predominantly OO-based (for programming in the large).
    Fine!
    But what is missing (and where we might disagree greatly or not) is the question: Is that a preferable state? Is it good that F# lacks so many higher level abstractions that makes FP much easier in other languages?
    Sure you somehow can encode HKTs as classes and also GADTs too, not to mention modules. All of it with significant boilerplate code minus the type safety you get when those concepts would be implemented natively.
    The more I think about it the more it becomes clear to me that F# certainly is NOT a FP “first” language. Its a FP & OO lang. Which certainly is OK and makes many people welcome.

    1. eirik says:

      I disagree when you start to encode business types as (mutable) classes.

      None of the examples I provided admit any form of mutation.

      The more I think about it the more it becomes clear to me that F# certainly is NOT a FP “first” language. Its a FP & OO lang. Which certainly is OK and makes many people welcome.

      F# is indeed a functional-first language because it has an important feature that C# will never have: it’s expression-based. It’s the precise reason why C# will never be a good scripting language. That combined with pattern matching/active patterns, HM type inference are what give F# the competitive edge over C#. So it’s a functional-first language that fully leverages the .NET type system when it comes to abstraction.

      Bottom line: people need to embrace the fact that F# is a hybrid language, and that the primary motivation for using F# is the .NET platform. People interested in FP but not in .NET should just try SML or Haskell. Claiming that F# is “up there” with Haskell or SML is false advertising and ultimately deals unpleasant surprises to people coming either from .NET or Haskell backgrounds.

  4. Hmm, this is interesting stuff. As someone still getting their bearings wrt proper F# app and lib design, I can say that a lot of the issues you bring up resonate with me, particularly context awareness. I admit to trying all of the module/function based workarounds: extra params, context type (global and local), lifting deps, and sometimes, back to objects. Your points do help to clarify the use of objects in F# for me, but don’t you think that they may also highlight a problem with the lang itself? I am beginning to feel like I did when I began using JavaScript in earnest and read “JavaScript: The Good Parts”. Alot of the issues you describe are not things that have to do with just good style, rather, they require nuanced knowledge about how F# works, like module value initialization, or nuanced understanding of language features that in simple cases seem interchangeable but develop significant tradeoffs for advanced use, like polymorphism in modules and functions as opposed to classes/interfaces/objects/methods/etc. Any language requires a certain level of sophistication from the dev to produce good designs, but these things leave me feeling a bit more in flux than even Scala or Haskell with all their complexity (almost precisely because they are supposed to be so simple in F# yet end up not).

    1. eirik says:

      I think you make a very good point. From an architectural perspective it absolutely makes sense to understand the nuances and trade-offs, but they’re certainly not something that should concern the beginner necessarily.

      1. I guess to add a bit more to my point, in particular what is giving me the same feeling as with “JavaScript: The Good Parts” is that this nuanced use of the lang actually IS kinda important for everyday scenarios. It is not necessary for a beginner to know them to accomplish things with the lang, but nonetheless they crop up even if you are just writing a Todo app or Blog app and you are trying to figure out how to structure your logging and persistence in a way that doesn’t feel simplistic or cumbersome. The fact that the application of obvious or natural language features in many cases does not yield what might be considered a good solution exposes the developer to a level of risk and responsibility that can counteract the other benefits that the language provides. I admit, however, that I am at a loss to describe how exactly things should be instead. I suspect from what you and others have written that a lot of this has to do with how object code (which does not come as obviously or naturally as function code) can be parametrized to a degree that function code cannot? Thus people begin with function code but when they reach a point where they would like to generalize their code further they find that objects provide more capabilities for supporting their scenario and thus look to rewrite to object code which would mean giving up automatic use of things like pattern matching and partial application and other things which in turn means even more code to write, etc.? So as others seem to be suggesting, equivalent support for parametrization of object and module/function code?

      2. eirik says:

        I don’t necessarily agree that adopting OO features entails forsaking the good stuff in F#, like unions and pattern matching. I would say that my style of writing F# is still 90% functional in module/function sense, with 10% being classes that act as wrappers for the actual implementation.

  5. You reference an article on SOLID and as a counterpoint I want to reference a recent talk by Dan North proclaiming “every element of SOLID is wrong”. The point to the talk is that simple is generally better when writing and more importantly maintaining software. I get the limits of the f# module system, but I would ask if the OO solutions to these limits are really worth the hassle, or whether they simply break Dan North’s anti-SOLID mantra of “write simple code!”.

    1. eirik says:

      I’m aware of that talk, and in a way this article is in part a reaction to it. I find the statement “just write simple code” to be a platitude devoid of any real information content. Everybody starts out wanting to write “simple code”, but the road to hell is always paved with good intentions. The main fallacy perpetrated in that talk is that “simple code” is a well-defined concept, which anybody can grasp regardless of experience. In fact I claim that a simple solution (in the Hickeyan sense) can only be defined within the boundaries of a stated problem, the tooling available to solve it, a good grasp of basic principles (like SOLID!) and an acute awareness of trade-offs and other incidental complexities that may arise in what may seem like a simple solution.

  6. ankos says:

    I’m learning F# right now and can not fully wrap my head around where you would instantiate your API class. I also looked at the Map implementation and it does make sense. Also instantiating a new map withing a function seems to be ok. But lets say I have an AddressService and a CustomerService the logic itself is implemented in a functional way as you described it.
    Now an other component (in my head that is a function) wants to use these two services to relocate a customer. Where do I instantiate the services? In the component it self? That would lead to tight coupling right? Or do I hand it to the component as a dependency? Which would still not address the problem.
    In C# or Java I usually have an IoC container which takes care of providing dependencies. So, do I have to build my own composition root where I instantiate and cast the classes to an interface which I then use to do partial function application or pass it to another class? And do you use your Api classes to compose your applications or just functions? Maybe you can point me to a good example which does use your approach. Thank you.

    1. eirik says:

      The example I provided is pretty much artificial, but the point it tries to make is that class-based APIs are an excellent means for abstraction/information hiding, which requires significant boilerplate to encode using the functional approach.

      The proposal I’m making is thus this: if information hiding is appropriate, the quality of your API may benefit by utilizing objects.

      If I can give an example, here’s the public-facing API of Argu:
      https://github.com/fsprojects/Argu/blob/master/src/Argu/ArgumentParser.fs#L14

  7. In addition I prefer tupled parameters rather than curried except for the core libraries. Tupled usually works better with intellisense, is generally less typing (x:int) (y:int) vs (x:int, y:int), is more explicit, and too much currying can make for obtuse code. Terser, yes, but less intuitive. And it relieves you of having to worry about “how do I best order these parameters for optimal currying and piping”, which is orthogonal to getting your work done. My rule is that if any one of the params needs a type hint, then tuple them.

    1. eirik says:

      You make good points, although I still think that there is a place for curried functions. The fact that they still compile to to normal methods make them a good value proposition for interop too. I personally dislike mixing of styles in a single signature, i.e. curried arguments grouped in tuples.

  8. Thank you Eirik for giving insight into this. I truly believe we need more posts talking about what is idiomatic and what is not for writing software in F#.

    I have not followed this exact style. But, I’ve been migrating to using objects when it comes to exposing APIs. I have tried throwing everything into a module and exposing it as an API. What ends up happening, when I start to use that API, my code can get bloated a bit and starts to feel weird, especially when they *have* to cause side-effects. A module function that returns “unit” has always felt weird to me. I actually feel that kind of thing should be an object method instead.

    Anyway, whatever my reasons are for side-effects, I have to have them. Exactly how you write a physics engine or renderer with a functional style API, I don’t know. Some things are practical in certain paradigms, and some aren’t. Now, the internals of those could have functional bits which can be valuable.

  9. Thanks for the article. I think it makes some valid points and some not-so-optimal ones.

    I didn’t agree with the encapsulation example. It seems to encourage sweeping complexity under the rug instead of refactoring a function with too many responsibilities. When I go to reach for a type (or a class) to encapsulate function parameters, then I take that as a sign that the design needs to be given a serious look.

    I also didn’t agree with the illegal state example. The implementation seems a bit contrived. Recognizing that Email probably doesn’t exist alone, I probably would model its containing entity as a type with a string Email property and a module with a `create` function that takes care of ensuring only valid data is produced. This is not a lot different from one of the common ways to model it in C#. Except in F# I can return an Option or Result (and chain that) instead of returning null or throwing an exception.

    Otherwise, there were some good points that seemed quite applicable for the edges of APIs and in a few other cases.

    1. I’m confused/intrigued by the “you can refactor a function with too many responsibilities”. Once you’ve refactored that into a few smaller functions, you still need a top-level function that calls all those functions in sequence, and *that* function still needs all the dependencies/parameters your original one did, so how does that help?

      For example the following function requires four “IO” params. To add a child node to an existing node in a tree in DB, you need to getNode to get the existing one, insertNode to insert the new one, updateNode to update the parent’s metadata (cached child count etc), and logAction to log it.

      How would it be possible to refactor this such that no function requires those four params? I could obviously pull the first line out and have the fn accept `node` instead of `nodeId`, but then the calling function would still need all four params. And logAction is the sore thumb here, but again I don’t see any way of pulling it out that doesn’t still require a top-level function to take all four params. If there’s a pattern to accomplish this kind of thing I’d love to know it, because I’m definitely leaning toward an OO model.

      1. Thanks for the question.

        Chances are the only reason getNode, updateNode, and addNode are function parameters in the first place is because a connection or context parameter has been partially applied to each one. I’d be curious to see the code which sets up these functions.

        So how would this code be affected if the context was a transaction and you needed to rollback after a step failed? (I ran into this.) Rollback would be on the connection/context that you’ve hidden from this function in partial applications. Since this method is orchestrating several low level operations, I wouldn’t try to hide the context from it. And I wouldn’t pass the operations as parameters. Instead, I would put them as functions on a module where the function takes the context as a parameter.

        I’ll try to expand on it further with refactor in a comment on your gist.

      2. By “context” do you mean e.g. the .Net SqlClient or whatever? If so, yes that’s what I’m doing: partially applying that to those four functions. But if I don’t, and just pass the context around everywhere, then there’s no way of unit testing. Everything will be explicitly dependent on that SqlClient object. And in fact even in the production code there are use cases where I want to run this function with an “in memory ‘database'”, so an explicit dependency on SqlClient isn’t an option.

        So the options that come to mind are either 1) what I did; use functions and partially apply the context, or 2) wrap the context in an interface, but that’s essentially the OO we’re trying to avoid. The third option is to have the context be a DU of `type DbContext = | SqlContext of SqlClient | MockContext of InMemoryTree`, or something to that effect. But that’s pretty ugly IMO–offers no additional abstraction, and forces you to do a bunch of match blocks scattered throughout your code. Maybe that’s my OO background speaking though. Would it be a fairly typical FP solution? Which of those three options is most idiomatic, or is there another one I’m not thinking of?

      3. I finished posting my gist comment before seeing your comment above. In the case where you want to dynamically swap out the database implementation at run time, I think a traditional interface could work and is a perfectly valid strategy. Option 3 could also work, and you can write a helper function to avoid gratuitous matches.

        One thing you have to bear in mind is that IO is kind of a shady place to be strictly “functional”. Although you can get benefit from some of the functional patterns, the main benefit of functional programming (to me) is more in focusing on pure functions where you can, especially core business logic. Pure functions give you maintainability/refactoring benefits that are hard to get in traditional OO. However, your functions can’t be pure when doing IO, as that’s non-deterministic by definition. So some functional patterns may be helpful at the edges, but at the end of the day you are still doing mutation. And OO has mutation as a core principle, and most IO libraries are OO-like. So, it’s perfectly sensible to use traditional patterns there IMO.

        With that said, I lean more toward a functional strategy for IO where possible (isn’t always). But testing against multiple DBs is not on our radar. And I don’t need to mock DB access to test business logic, because that logic has no side effects. (No DB reads/writes interspersed.)

      4. Cool, I feel better about myself now. Especially given that this was relatively functional for a snippet in my domain. Doing photobooths, so between comm with camera, 3rd party image processing, social sharing, gui, I’m lucky if I can extract five whole lines with only one IO dependency. “Pure” is another world. So I feel pretty good about the design I’ve chosen now. Interfaces were realistically the best approach.

      5. The series I linked above is definitely worth the read. A big part of the challenge of modeling a given problem in pure functions is figuring out how to push IO out of it. Such that, decisions are wholly separated from IO. Coming from OO, it’s a brain-bend. But it can be quite fun. For instance, I recently worked through how to get a cryptographically random shuffle using pure functions. (I needed to shuffle test questions and answers.) Typically, pure function solutions only use pseudorandom deterministic algorithms, which have poor randomization. I have a few more optimizations to make, like using bytes instead of floats.

        http://kasey-jo.blogspot.com/2017/03/shuffling-without-side-effects.html

      6. eirik says:

        Interesting post, but making probabilistic algorithms pure using that approach comes at cost. First of all, it relies on the assumption that the size of required random inputs is known ahead of time, which in some cases does not hold. Secondly, the correctness of many probabilistic algorithms is predicated on uniformity in the RNG. How can you test when your randomizer is deterministic?

      7. Yes, my implementation does have trade-offs, particularly memory in this case. That makes it not a general solution for all cases, just for my need to shuffle questions and answers of known quantity. My chosen randomizer (RNGCryptoServiceProvider) is absolutely not deterministic. I’m pretty sure mutation is a core principle that makes it work. Instead, my shuffle algorithm is deterministic, which means I can test that shuffled questions/answers are graded properly. At run time, the “seed values” or “picks” for shuffling are generated from the randomizer before calling the business logic. But in unit testing, I choose some known seed values which should produce expected results. IOW, I’m not seeding the randomizer as is normal for PRNGs, but the shuffle.

    2. eirik says:

      @Kasey I would contend that single responsibility and information hiding are orthogonal concerns. If information hiding is a requirement for an API, then objects are really the only game in town. Algebraic data types by definition do not admit information hiding nor any form of validation. Which brings me to your second point: yes, the example is contrived however it’s only the only to achieve type safety. Consider this example of a DateTimeOffset implementation:

      type DateTimeOffset = { Ticks : int64 ; UtcOffsetTicks : int64  }
      

      This is faithfully mirroring the internal representation of System.DateTimeOffset, however this implementation is fundamentally flawed precisly because it’s encoded on top of algebraic data types. Not all instances are valid representations of time and structural equality is invalid here. This can really only be achieved using the contrived approach.

      1. @Eirik re type safety. I’m not sure that we disagree. Types alone can’t guarantee legal states… a border check is required. Single type unions are not a fix for this. I agree with all that. But your two implementations are different only in window dressing, and very minimally at that. It’s not a convincing argument that OO is better at making illegal states unrepresentable. If it’s worth making an explicit concept with types and methods around it, I’d probably do something like this (sans exceptions):

        https://github.com/fsharp/fslang-suggestions/issues/553#issuecomment-288935840

        It’s still fundamentally no different from either of the implementations you show. And going beyond the simple, union types give you additional modeling tools to represent states aside from just validity.

      2. eirik says:

        I agree with what you’re saying, I guess my point is that as long as you’re resorting to techniques like the example you posted then you’re employing OO-style information hiding in the broader sense. Which if fine, I often do it like this myself. There are limits to this approach however, particularly when structural equality semantics are not appropriate (as in the DateTimeOffset example given above), in which case a plain old class does offer the simpler solution.

      3. Forgot to mention. Instead of modeling it as static methods on the type, I would take the approach you often find in functional libraries of having a module of operations for the type. Again, this has to be an important enough concept to my code that it is worth creating all that. An email string is not something I would typically put that much labor into.

        I do agree though, that sometimes an object is the right container. For instance, when I work with MailboxProcessor, it is basically a stateful object. I’ve tried in the past to use the “type and module of operations” approach on it. But that ends up being a lot more work than just creating an object. Because it’s inherently a stateful/mutable thing. But my core business logic, I would try to model as much with pure functions and types as possible, not with objects.

    3. eirik says:

      Also, I would check out this series on functional dependencies.

      The reasoning of this series is founded on the premise that “functional” is synonymous with “pure”. This is not a widely accepted definition, as anybody who’s worked with MLs or Lisps will tell you. While there is great value writing your business logic using pure functions (especially wrt testability), I find the author’s promotion of monadically-introduced effects to be problematic for many reasons: 1) it’s promoted in a type system that doesn’t support it, 2) there are well-documented problems in languages that do in fact support it and 3) PL research has gone on to propose better ways to reason about effects.

      A take-away from the article is what I mentioned earlier: partial application is the same as object construction. In that sense both can either be pure or impure. And regardless of that attribute either are a means for effecting encapsulation. Encapsulation is useful independently of purity.

      1. eirik says:

        You can mark records as not equatable/comparable when such comparisons are inappropriate.

        Sure, but why is this simpler than using a plain old class? What is gained by using a record in that context?

      2. Agreed. I had already gone through that series and wasn’t sure the point of it. (Note my comments at the end of https://fsharpforfunandprofit.com/posts/dependency-injection-1/). I think OO in F# needs to be promoted more heavily. Not in the OOP patterns (inheritance hierarchies, rich domain models, get/set, etc) sense, just in the IoC sense. The constant promotion of “obscure” IoC techniques and sneering at classes/interfaces just keeps relegating F# to a niche market. For me OO F# is the most productive way to think about an application, and if that could be better marketed, I’d love it if it was an impetus to get rid of all the inheritance hierarchy crap architecture that most large OO projects suffer from.

      3. I came to a completely different conclusion with that series of articles. (Perhaps I had been prepped already by some of the author’s previous articles.) My takeaway was not in how to do IO, but in how to avoid doing IO in your critical business code that you desire to be pure. After all, the last article in series culminates in how to remove dependencies from said sections. The first articles are setting up for that including how partially applying dependencies is the same as injecting them. I assumed how the author chose to do IO was incidental.

      4. > Sure, but why is this simpler than using a plain old class? What is gained by using a record in that context?

        A class tends to lend itself to mutable state (semantically and mentally). If your whole team is diligent and discipline about it, you can avoid that, but that’s asking a lot. But a record is built with immutability by default. So if I’m building core business logic that I want to be referentially transparent, I will usually (not always) choose a record to make sure people don’t unintentionally make it non-deterministic. You can actually make a record mutable or it’s members mutable, but this will be very plain to see in the declaration of the record. Whereas, you will have to dig to find that out in a class.

      5. eirik says:

        An F# class is immutable by default unless it has fields that carry the `mutable` keyword. Pretty similar to records in that sense.

      6. After our discussions (which I have enjoyed), I think I can agree with nearly everything in your article (and even the one on exceptions) if I view it through the lens of IO and/or public API borders. Obviously my default perspective is different, hence the comments. But I thank you for bearing with me.

  10. The one big downside of classes and interfaces is `null`. I still think records of functions are worth considering because they can’t be null.

    Do I really think that? No, not really. I can’t say unexpectedly-null service references have caused much real-world frustration for me. You initialize them and pass them around, and if one is unexpectedly null it’s usually obvious what you did wrong.

    But still … kinda.

    1. scrwtp says:

      Classes defined in F# don’t support null as a value unless explicitly marked with AllowNullLiteral attribute. In that sense they’re no different from records.

      1. Weird I never noticed this. I said it’s not a thing I’ve had much real-world trouble with, but I guess that was an understatement. Apparently it’s a thing I never once had real-world trouble with even at dev time. But, still good to know that F# protects you even then.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s