9

Disclaimer:
Although I accept the gospel of immutable state and higher order functions, my real-world experience is still 95% object-oriented. I'd love to change that, but whatchagonnado. So my brain is very much wired to OO.

Question:
I have this situation very frequently: a piece of business functionality implemented as a small "core" plus multiple "plugins", working together to present a seemingly solid surface to the user. I found that this "microkernel" architecture works extremely well in a lot of circumstances. Plus, very conveniently, it nicely combines with a DI container, which can be used for plugin discovery.

So, how do I do this in a functional way?

I do not think that the basic idea in this technique is inherently object-oriented, because I've just described it without using any OO terms or concepts. However, I can't quite wrap my head around the functional way to go about it. Sure, I can represent plugins as functions (or buckets of functions), but the difficult part comes when plugins need to have their own data as part of the big picture, and the shape of data is different form plugin to plugin.

Below is a small F# snippet that is more or less literal translation of C# code that I would write when implementing this pattern from scratch.
Note the weak points: losing type information in CreateData, necessary upcast in PersistData.
I flinch at casts (whether up or down) every time, but I've learned to accept them as a necessary evil in C#. However, my past experience suggests that the functional approach often offers unexpected, yet beatiful and elegant solutions to this kind of problems. It is such solution that I am after.

type IDataFragment = interface end
type PersistedData = string // Some format used to store data in persistent storage
type PluginID = string // Some form of identity for plugins that would survive app restart/rebuild/upgrade

type IPlugin = interface
  abstract member UniqueID: PluginID
  abstract member CreateData: unit -> IDataFragment

  // NOTE: Persistence is conflated with primary function for simplicity. 
  // Regularly, persistence would be handled by a separate component.
  abstract member PersistData: IDataFragment -> PersistedData option
  abstract member LoadData: PersistedData -> IDataFragment
end

type DataFragment = { Provider: IPlugin; Fragment: IDataFragment }
type WholeData = DataFragment list

// persist: WholeData -> PersistedData
let persist wholeData = 
  let persistFragmt { Provider = provider; Fragment = fmt } = 
    Option.map (sprintf "%s: %s" provider.UniqueID) (provider.PersistData fmt)

  let fragments = wholeData |> Seq.map persistFragmt |> Seq.filter Option.isSome |> Seq.map Option.get
  String.concat "\n" fragments // Not a real serialization format, simplified for example

// load: PersistedData -> WholeData
let load persistedData = // Discover plugins and parse the above format, omitted

// Reference implementation of a plugin
module OnePlugin =
  type private MyData( d: string ) = 
    interface IDataFragment
    member x.ActualData = d

  let create() = 
    {new IPlugin with
      member x.UniqueID = "one plugin"
      member x.CreateData() = MyData( "whatever" ) :> _
      member x.LoadData d = MyData( d ) :> _

      member x.PersistData d = 
        match d with
        | :? MyData as typedD -> Some typedD.ActualData
        | _ -> None
    }




Some updates and clarifications

  • I do not need to be educated in functional programming "in general" (or at least that's what I like to think :-). I do realize how interfaces are related to functions, I do know what higher-order functions are, and how function composition works. I even understand monads warm fluffy things (as well as some other mumbo-jumbo from category theory).
  • I realize that I don't need to use interfaces in F#, because functions are generally better. But both interfaces in my example are actually justified: IPlugin serves to bind together UniqueID and CreateData; if not interface, I would use a record of similar shape. And IDataFragment serves to limit the types of data fragments, otherwise I would have to use obj for them, which would give me even less type safety. (and I can't even imagine how I would go about it in Haskell, short of using Dynamic)
Fyodor Soikin
  • 78,590
  • 9
  • 125
  • 172
  • 1
    Related: http://stackoverflow.com/q/22263779/126014 – Mark Seemann Mar 16 '15 at 06:34
  • Yep, I've read that, a long time ago. That question is talking about something quite different. – Fyodor Soikin Mar 16 '15 at 15:02
  • As a clarification, what is expected to change more, the number of types of data plugins work with, or the number of functions that work with the data? (i.e. 'which one should be easier to do?') – Christopher Stevenson Mar 16 '15 at 20:46
  • @ChristopherStevenson - definitely the number of types. However, I must note that the solution where either the types or the functions are fixed (e.g. using ADT for types) would be unacceptable. The core must remain open and reusable. – Fyodor Soikin Mar 16 '15 at 22:58
  • That requirement absolutely prevents a Discriminated Union representation of your Types. Your remaining options are to use an interface to tag Type functionality, or to abstract away the types for each plugin (making some kind of messaging system if the plugins need to communicate with each other), and making your plugins fit into a workflow/monad. That approach is not what you're discussing, however. – Christopher Stevenson Mar 17 '15 at 14:50

3 Answers3

5

I can only sympathize with your statements. While functional programming in the small has been talked to death, there is precious little advice on how to do functional programming in the large. I think for F# in particular most solutions will gravitate towards more object-oriented (or at least, interface-oriented) style as your system grows. I don't think it's necessarily bad - but if there is a convincing FP solution, I would like to see it as well.

One pattern that I have seen used in a similar scenario was to have a pair of interfaces, a typed and an untyped one, and a reflection based mechanism to go between them. So in your scenario you'd have something like this:

type IPlugin =
    abstract member UniqueID: PluginID
    abstract member DataType: System.Type
    abstract member CreateData: unit -> IDataFragment

type IPlugin<'data> = 
    inherit IPlugin

    abstract member CreateData: unit -> 'data
    abstract member PersistData: 'data -> PersistedData option
    abstract member LoadData: PersistedData -> 'data

and an implementation would look like this:

let create() = 
    let createData () = MyData( "whatever" )
    {
        new IPlugin with
            member x.UniqueID = "one plugin"
            member x.DataType = typeof<MyData>                
            member x.CreateData() = upcast createData()
        interface IPlugin<MyData> with
            member x.LoadData d = MyData( d )
            member x.PersistData (d:MyData) = Some d.ActualData
            member x.CreateData() = createData()            
    }

Note that CreateData is part of both interfaces - it's just there to illustrate that there's a balance to strike between how much is duplicated between the typed and untyped interface and how often you need to jump through the hoops to convert between them. Ideally CreateData shouldn't be there in IPlugin, but if it saves you time, I wouldn't look back twice.

For going from IPlugin to IPlugin<'a> you'd need a reflection-based helper function, but at least you explicitly know the type argument since it's part of IPlugin interface. And while it's not pretty, at least the type conversion code is contained in a single part of the code, rather than being sprinkled across all the plugins.

scrwtp
  • 13,437
  • 2
  • 26
  • 30
  • I usually go a slightly different way: have a factory function that takes several functions/values (one for each method/property) and returns an instance of the interface. Now these functions/values can be as strongly typed as they want, and the factory function just inserts all the casting code around them. Same idea though: keep the casting in one place. But still, I was kinda hoping that the functional community would have a better way. I can't be the only person to try building real apps in F#, can I? :-) – Fyodor Soikin Mar 17 '15 at 01:42
3

You don't have to define interfaces in order to make an architecture pluggable in F#. Functions are already composable.

You can write your system Outside-In, starting with the desired, overall behaviour of your system. For example, here's a function I recently wrote that transitions a Polling Consumer from a state where no message was received, into a new state:

let idle shouldSleep sleep (nm : NoMessageData) : PollingConsumer =
    if shouldSleep nm
    then sleep () |> Untimed.withResult nm.Result |> ReadyState
    else StoppedState ()

This is a higher order function. While I was writing it, I discovered that it depended on the auxiliary functions shouldSleep and sleep, so I added these to the argument list. The compiler then automatically infers that e.g. shouldSleep must have the type NoMessageData -> bool. That function is a Dependency. The same goes for the sleep function.

As a second step, it turns out that a reasonable implementation of a shouldSleep function ends up looking like this:

let shouldSleep idleTime stopBefore (nm : NoMessageData) =
    nm.Stopped + idleTime < stopBefore

Never mind if you don't know what it all does. It's the composition of functions that matter here. In this case, we've learned that this particular shouldSleep function has the type TimeSpan -> DateTimeOffset -> NoMessageData -> bool, which isn't quite the same as NoMessageData -> bool.

It's pretty close, though, and you can use partial function application to go the rest of the distance:

let now' = DateTimeOffset.Now
let stopBefore' = now' + TimeSpan.FromSeconds 20.
let idleTime' = TimeSpan.FromSeconds 5.
let shouldSleep' = shouldSleep idleTime' stopBefore'

The shouldSleep' function is a partial application of the shouldSleep function, and has the desired type NoMessageData -> bool. You can compose this function into the idle function, together with an implementation of its sleep dependency.

Since the lower-order function has the correct type (the correct function signature), it just clicks into place; no casting is necessary in order to achieve this.

The idle, shouldSleep and shouldSleep' functions can be defined in different modules, in different libraries, and you can pull them all together using a process similar to Pure DI.

If you want to see a more comprehensive example of composing an entire application from individual functions, I provide an example in my Functional Architecture with F# Pluralsight course.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • 1
    I have updated the question with some clarifications. My question wasn't about interfaces vs. functions or about function composition or about higher-order functions or about any functional stuff in general. My question was about this one specific technique - you can call it "plugins", "microkernel", or "faceted composition", doesn't matter. And your example doesn't have anything to do with that, because it misses a central feature: multiple component that share same interface and are uniformly treated by the core. – Fyodor Soikin Mar 16 '15 at 14:07
  • 3
    That doesn't make your question clearer to me. Why do you need a Marker Interface (`IDataFragment`)? That's not even good OO... What do you mean by *microkernel* or *faceted composition*? Are you looking for something like the Managed Extensibility Framework, but for functions instead of interfaces? If so, I don't think such a library exists (yet). – Mark Seemann Mar 16 '15 at 14:26
  • I was sure I explained the idea pretty well. Did you read the text, or did you just look at the code? I want to implement a certain piece of functionality as a small "core" plus multiple plugins of uniform shape, which work together to present a seemingly solid interface to the user. That works fine as long as plugins are pure functions (data in/data out): the core can just call them either in sequence or dispatch to one of them, whatever. But this breaks when plugins need to store and/or pass around some "state" as part of the big picture. – Fyodor Soikin Mar 16 '15 at 14:41
  • The empty interface is not "marker". Marker interfaces are used as a way to "tag" objects, which is not the case here. In my code, the interface is used to provide some semi-type safety: now I can still pass data from one plugin to another plugin by mistake, but at least I'm protected from passing some random object. – Fyodor Soikin Mar 16 '15 at 14:44
  • I am *not* looking for a framework like MEF or similar. The mechanism for discovering the plugins, or for wiring up the whole setup, is out of scope for this question. At this point, I am only trying to figure out a way to design the interfaces between parts of the system. – Fyodor Soikin Mar 16 '15 at 14:48
  • I think it's good to have interfaces or something, so if your plugins are loaded dynamically you need to look for some entry point using reflection... Pluggable system IMO is something more dynamic (functionality you can plug and unplug) – Konrad Dec 04 '18 at 21:30
  • I think it doesn't answer OP's question – Konrad Dec 04 '18 at 21:32
  • In C# you'd create some kind of interface/abstract class named `IModule` or `IPlugin` and then all plugins would implement that and your main application would load those plugin assemblies and call appropriate functions – Konrad Dec 04 '18 at 21:35
0

Functional programming is all about the explicit composition of functions. No magic. If you need plugins, compose functions by some configuration. That's all.

OK, that's not all. You were asking about handling the state.

In FP programming, you have only two options.

1) Manual partial application as Mark described.

2) Automatic partial application by Reader, State, or some another monad.

Via monad, core functions can be pure, while dangerous state is handled by monad itself.

Does it make sense now?

Daniel Steigerwald
  • 1,075
  • 1
  • 9
  • 19