Question

I'm new to f#. I'm wanting to return a list of search results to the front end. However, there could be several types of search results (e.g. BlogResult,MovieResult, etc) each with their own properties (e.g. MovieResult.PosterIcon). The UI will be aware of these types and will display each type accordingly. The core search API will not be aware of these types and is only providing a framework that a "provider" needs to implement including the type that it will return. For example, a BlogProvider would implement the necessary functions and it's corresponding search result type. The application will register all of these providers together into the core framework such that all results should be returned in ranked order as a single list of items. It is highly likely that these providers will each exist in their own assembly, separate from both the application and the core search API.

My OO brain wants to have a SearchResult class or interface that all the other result types inherit from. Is there a more functional way to handle this?

Update The application is an ASP.NET MVC application. Each result is represented by a partial view. The site itself does not need to be modified when a new search result type is added. There is only the new partial view. This does not violate the Open/Closed principle. Technically I could have the provider create the template for the partial view. However the ceremony code is not worth the effort.

Was it helpful?

Solution

It may be my limited imagination and poor design skills, but whenever I've been presented with requirements like the above, I've been forced to make some sort of compromise. Here's why it's difficult also in OOP:

The UI will be aware of these types and will display each type accordingly.

This is the problematic part. As far as I can tell, there's only two ways to address this requirement:

  • Treat all 'sub-types' as a special case. If you do it with OOD, this means that the client (the UI) must know about all available sub-types. It can either attempt downcasts in an ad-hoc manner, or it can leverage the Visitor pattern, but in either case, it violates the Open/Closed Principle, because you can't just add a new 'provider' to the system without modifying the UI code.
  • Let all 'sub-types' implement a common interface or inherit from a common base class. This interface (or base class) would then have one (or more) methods that the UI can invoke to render the result. Such a method could be called Render, and it would be technology-specific:
    • For desktop applications, it might look like this void Render(Canvas) (simplified) - that is, it receives some sort of canvas object, upon which it's then asked to render.
    • For web applications, it might be a function returning an HTML fragment: string Render() (again, simplified).

While you can come up with more elaborate schemes, they'd tend to be a combination of the above two alternatives.

Each alternative can be modelled in F# without relying on inheritance.

Special cases

Instead of treating each 'sub-type' as a special case, you can define a Discriminated Union of all the various cases:

open System

type BlogResult = {
    Title : string
    Summary : string }

type MovieResult = {
    Title : string
    PosterIcon : Uri }

type SearchResult =
    | BlogResult of BlogResult
    | MovieResult of MovieResult

This has the same disadvantage as the OOD approach to special casing: you'll need to modify (recompile) the UI if you want to introduce a new sub-type.

On the other hand, Discriminated Unions are built-in to F#, are easy to work with, and even give you compile-time checking (which you could also get with the Visitor pattern).

Common interface

As an alternative to using a common interface, you can use a function. Functions, or rather, closures are equivalent to objects.

So, instead of letting the UI consume an interface that defines a string Render() method, you can let the UI consume a function with this signature: unit -> string.

Any 'provider' you want to plug into the system simply needs to return a function with this signature for each search result. That function may very well be a closure.

OTHER TIPS

Disclaimer: this answer isn't applicable in F#. Mark's answer is the best F# offers. In particular the discriminated union approach there is something for which C# has no direct equivalent.

But you ask about "functional", and some other functional languages have a better option. Other functional languages like Scala and Haskell offer a feature called typeclasses, which is essentially being able to make an extension method implement an interface, and then bolt on that interface after-the fact.

So how is this relevant? Well with F# you have to either A) pre-determine all your cases in a Discriminated Union, or B) put rendering functionality in your data library. Neither is ideal.

With typeclasses, you could define your record types independently:

// Blog.fs
type Blog {...}
[<SomeProviderAttribute>]let getBlogs() = ... // returns Blog seq

// Movie.fs
type Movie = {...}
[<SomeProviderAttribute>]let getMovies() = ... // returns Movie seq

Then you could bolt on interfaces at a higher level: (pseudocode follows)

// UiBase.fs
class Renderable where render :: 'T -> string

// BlogUi.fs
instance Renderable Blog where render blog = blog.header + blog.text

// MovieUi.fs
instance Renderable Movie where render movie = movie.title + movie.description

Finally from the main UI region it'd be easy to collect the rendered strings. If you open the modules that define the typeclasses, you can use them interchangeably:

// MainUi.fs
open Blog; open Movie; open UiBase; open BlogUi; open MovieUi
// Just like you'd have to call "using" in C# to get the extention methods,
// you have to open these to get the type classes

let dataProviders = ... // Some assembly search and reflection here; 
                    // say it returns [getBlogs; getMovies]
                    // (and note without the common typeclass,
                    //    those two would be incompatible)

let renderedStrings = 
  seq { for provider in dataProviders do
          for renderable in provider() do
            yield render renderable }

So it essentially lets you use the (otherwise completely unrelated) Blog and Movie classes and providers interchangeably at the higher level, because you were able to "bolt on" the interface at the mid level. Very very cool and pertinent exactly to the common problem you're facing here.

...Unfortunately F# itself does not have this feature.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top