Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When to use interfaces, and when to use higher order functions?

Given a ASP.NET MVC application with the following layers:

  • UI (Views, CSS, Javascript, etc.)
  • Controllers
  • Services (Contains business logic, and data access)

The reason for no separate data access layer, is that I'm using SQL type provider.

(The following code may not be working, as it's only a raw draft). Now imagine a service named UserService defined like:

module UserService =
    let getAll memoize f =
        memoize(fun _ -> f)

    let tryGetByID id f memoize =
        memoize(fun _ -> f id)

    let add evict f name keyToEvict  =
        let result = f name
        evict keyToEvict
        result

And then in my Controllers layer, I'll have another module named UserImpl or it could just as well be named UserMemCache:

module UserImpl =   
    let keyFor = MemCache.keyFor
    let inline memoize args = 
        MemCache.keyForCurrent args 
        |> CacheHelpers.memoize0 MemCache.tryGet MemCache.store

    let getAll = memoize [] |> UserService.getAll
    let tryGetByID id = memoize [id] |> UserService.tryGetByID id
    let add = 
        keyFor <@ getAll @> [id]  
        |> UserService.add MemCache.evict 

The usage of this would be like:

type UserController() =
    inherit Controller()

    let ctx = dbSchema.GetDataContext()

    member x.GetAll() = UserImpl.getAll ctx.Users
    member x.UserNumberOne = UserImpl.tryGetByID ctx.Users 1
    member x.UserNumberTwo = UserImpl.tryGetByID ctx.Users 2
    member x.Add(name) = UserImpl.add ctx.Users name

Using interfaces, we would have the following implementation:

type UserService(ICacheProvider cacheProvider, ITable<User> db) =
    member x.GetAll() = 
        cacheProvider.memoize(fun _ -> db |> List.ofSeq)

    member x.TryGetByID id = 
        cacheProvider.memoize(fun _ -> db |> Query.tryFirst <@ fun z -> z.ID = ID @>)

    member x.Add name = 
        let result = db.Add name
        cacheProvider.evict <@ x.GetAll() @> []
        result

And the usage would be something like:

type UserController(ICacheProvider cacheProvider) =
    inherit Controller()

    let ctx = dbSchema.GetDataContext()
    let userService = new UserService(cacheProvider, ctx.Users)

    member x.GetAll() = userService.GetAll()
    member x.UserNumberOne = userService.TryGetByID 1
    member x.UserNumberTwo = userService.TryGetByID 2

Obviously the interface implementation has much less code, but it doesn't really feel like functional code anymore. If I start using interfaces throughout my web app, when do I know when to use higher order functions instead? - else I'll just end up with a plain old OOP solution.

So in short: When should interfaces be used, and when to use higher order functions? - some line has to be drawn, or it will all be types and interfaces, whereof the beauty of FP disappears.

like image 607
ebb Avatar asked Aug 27 '13 06:08

ebb


1 Answers

On interfaces. First of all, I think you can see interfaces as just named pairs of functions. If you have:

type ICacheProvider =
  abstract Get : string -> option<obj>
  abstract Set : string * obj -> unit

then this is pretty much equivalent to having a pair (or a record) of functions:

type CacheProvider = (string -> option<obj>) * (string * obj -> unit)

The benefit of using interfaces is that you give the type a name (you would get that with records too) and you are more clearly expressing your intention (other components can implement the interface).

I think using an interface is a good idea if you have more than 2 functions that are often passed to some other function together - this way, you avoid having too many parameters.

Module or class. The real difference in your code is whether to use module with higher-order functions or a class that takes the interface as constructor argument. F# is a multi-paradigm language that combines functional and OO style, so I think using classes in this way is perfectly fine. (You can still benefit from the functional style when defining data types to represent the domain etc.)

One thing to keep in mind is that functional programming is all about composition. This might not be as useful in this case, but I always prefer writing code that I can compose to add more functionality rather than code that requires me to provide something when I want to use it.

Perhaps you could write it so that your database access code does not do caching directly (this would include all the database queries and pre-processing logic):

module UserService =
    let getAll () = (...)
    let tryGetByID id = (...)
    let add name = (...)

...and then define a type that wraps this and adds caching (and this would then be used by the main type of the web application - it is quite similar to the type you defined in your example, but now we are separating the database access and memorization using a cache provider):

type UserService(cacheProvider:ICacheProvider) =
    member x.GetAll() = cacheProvider.memoize UserSerivce.getAll ()
    member x.TryGetByID id = cacheProvider.memoize UserService.tryGetByID id
    member x.Add name = cacheProvider.memoize UserService.add name

Summary. But - I think your approach using a class that takes ICacheProvider is perfectly fine - F# is pretty good in mixing functional and object oriented style. The example I posted is really just a possible extension that might be useful in bigger projects (if you wanted to use functional aspects and clearly separate different aspects of the functionality)

like image 94
Tomas Petricek Avatar answered Oct 10 '22 03:10

Tomas Petricek