Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# how to pass equivalent of interface

Tags:

f#

I know I will laugh when I see this answer, but for some reason I don't see it.

For some reason it is eluding me how to pass multiple func in one parameter (for lack of better words.)

For instance, lets say I have IDoSomething that has 3 methods:

1.)  DoIt() 2.)  DoItMore() 3.)  DoItMost() 

in OO, I would do this:

type MyController(something:IDoSomething) =    let a = something.DoIt()    let b = something.DoItMore()    let c = something.DoItMost() 

So, for F# I would have a module with the 3 functions mentioned above. But how would I pass that into my controller? Would I have to pass each as a separate function instead? I kinda feel like I want to pass the whole module hehe :-)

like image 416
schmoopy Avatar asked Dec 01 '15 03:12

schmoopy


1 Answers

This question seems to come up again and again, and somehow the accepted answer often turns out to be 'a record of functions'. There's no reasonable motivation for doing this. Records are for data. They have structural equality, which is totally destroyed by putting functions into them, since functions don't have structural equality.

Interfaces

So, what's the alternative to interfaces in F#?

Well, if you absolutely must group functions together, F# enables you to define interfaces. Yes: interfaces:

type IDoSomething =     abstract DoIt : unit -> unit     abstract DoItMore : unit -> unit     abstract DoItMost : unit -> unit 

This language feature exists, so if you need an interface, there's no reason to come up with some weird replacement for it.

But, it's not Functional

Right, it isn't Functional, but neither is creating a record of functions.

The question is whether there's a single, ubiquitous Functional way of grouping related functions together. Haskell has type classes and Clojure has protocols (which, to me, look a bit like type classes, but then, I'm hardly a Clojure expert).

F# has neither type classes nor protocols; the closest you get in the language is, again, interfaces.

Functions

All that said, the fundamental building block of Functional Programming is: functions. Sometimes, functions are composed from other functions, or return other functions. We call these higher-order functions.

Idiomatic Functional code is often expressed through higher-order functions. Instead of passing in an interface to a function, pass other functions:

let run foo bar baz = List.map foo >> bar >> List.groupBy baz 

Because of type inference, even such a nonsense example as above compiles. It has the type ('a -> 'b) -> ('b list -> 'c list) -> ('c -> 'd) -> ('a list -> ('d * 'c list) list). I have no idea what it does (I just made it up), but the point is that foo, bar, and baz are functions. As an example, foo is a function of the type 'a -> 'b.

Even with such a ridiculous function as run, you can apply it, and it may actual make sense:

type Parity = Even | Odd let parity i =     match i % 2 with     | 0 -> Even     | _ -> Odd  open System  let tryParse s =     match Int32.TryParse s with     | true, i -> Some i     | _ -> None let runP = run tryParse (List.choose id) parity 

The runP function has the type string list -> (Parity * int list) list. What does it do? It takes a list of strings, discards those that aren't integers, and groups them by parity (even/odd):

> runP ["Foo"; "1"; "42"; "Bar"; "Baz"; "1337"];; val it : (Parity * int list) list = [(Odd, [1; 1337]); (Even, [42])] 

So, it turned out to be (sort of) useful after all!

From OOP to FP

In the beginning of this rant, I wrote: "if you absolutely must group functions together". There's a reason I wrote if. Even in OOD, from the Interface Segregation Principle, we know that we shouldn't force a client to depend on functions it doesn't need. Passing a group of functions to a client can easily violate that principle. The more members an interface defines, the bigger the risk of violation.

In addition to that, from the Dependency Inversion Principle follows that "clients [...] own the abstract interfaces" (APPP, chapter 11). In other words, the client states what it needs, and the interface must conform to that; it's not the implementation that defines the interface.

Once you start following these, and the rest of the SOLID principles, you should begin to realise that the more granular you define your interfaces, the better. The logical conclusion is to define all interfaces with only a single method. If a client needs more than one member, you can always pass two interfaces as two arguments, but you can never remove a member from an interface if it's already defined.

That's extensibility in a nutshell: you can extend, but you can't diminish.

In OOD, interfaces should ideally define only a single member, but in Functional Programming, we have a more natural candidate for such polymorphism: a function.

Thus, pass functions as arguments. It's the Functional way to do it.

Edit: See also my other answer (on free monads)

like image 128
Mark Seemann Avatar answered Sep 21 '22 10:09

Mark Seemann