F# how to pass equivalent of interface

2019-01-13 18:36发布

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 :-)

标签: f#
4条回答
Juvenile、少年°
2楼-- · 2019-01-13 19:00

Most of what I wrote in my other answer, I still consider proper and idiomatic for F#. Later, however, I've learned that the use of functions or partial application, while fine, readable, understandable, simple, and idiomatic in a multiparadigmatic language like F#, is, in fact, not strictly functional.

In a nutshell, the problem is that dependencies tend to be impure, and if you 'inject' them into client code, then that client code also becomes impure, because pure code can't call impure code.

In Haskell, you have several options for addressing this problem, but not all of them translate well to F#. One of these alternative do translate, at least to some degree: free monads.

I don't want this answer to come across in a way that free monads is the idiomatic replacement for interfaces in F#, but for the sake of completeness, I'm adding this answer as well:

Using the F# free monad recipe, the OP interface becomes:

type DoSomethingInstruction<'a> =
| DoIt of 'a
| DoItMore of 'a
| DoItMost of 'a

let private mapI f = function
    | DoIt next     -> DoIt     (next |> f)
    | DoItMore next -> DoItMore (next |> f)
    | DoItMost next -> DoItMost (next |> f)

type DoSomethingProgram<'a> =
| Free of DoSomethingInstruction<DoSomethingProgram<'a>>
| Pure of 'a

let rec bind f = function
    | Free x -> x |> mapI (bind f) |> Free
    | Pure x -> f x

let doIt = Free (DoIt (Pure ()))

let doItMore = Free (DoItMore (Pure ()))

let doItMost = Free (DoItMost (Pure ()))

type DoSomethingBuilder () =
    member this.Bind (x, f) = bind f x
    member this.Return x = Pure x
    member this.ReturnFrom x = x
    member this.Zero () = Pure ()

let doDomething = DoSomethingBuilder ()

You can write a small sample program using the doSomething computation expression:

let p = doDomething {
    do! doIt
    do! doItMore
    do! doItMost }

Furthermore, you can 'implement' the 'interface' by writing an interpreter:

let rec interpret = function
    | Pure x -> x
    | Free (DoIt next)     -> printfn "Doing it.";      next |> interpret
    | Free (DoItMore next) -> printfn "Doing it more!"; next |> interpret
    | Free (DoItMost next) -> printfn "DOING IT MOST!"; next |> interpret

You can now run the program:

> interpret p;;
Doing it.
Doing it more!
DOING IT MOST!
val it : unit = ()

This clearly requires more boilerplate code, but all code apart from the interpreter is pure.

查看更多
放荡不羁爱自由
3楼-- · 2019-01-13 19:12

Technically, there are at least five different ways to pass multiple functions:

  • as arguments, curried
  • as arguments, tupled
  • as field values of a record type instance
  • as field values (val ...) or properties (concrete or abstract member ...) of a concrete or abstract class type instance
  • as properties (abstract members) of an interface type instance

If and when coupling multiple functions together makes any sense depends on your specific problem domain, and on how much you weigh certain advantages and disadvantages of each approach... not to mention conventions, culture, style and taste. So-called best practices are never universally valid; they are just hints. Pragmatism is the best best practice.

Here is what Expert F# 3.0 says (page 579): "Recommendation: Use Object Interface Types Instead of Tuples or Records of Functions. In Chapter 5, you saw various ways to represent a dictionary of operations explicitly, such as using tuples of functions or records of functions. In general, we recommend that you use object interface types for this purpose, because the syntax associated with implementing them is generally more convenient."

查看更多
forever°为你锁心
4楼-- · 2019-01-13 19:20

Use a Record Type:

type MyFunctions = { 
    DoIt: (unit -> unit); 
    DoItMore: (unit -> unit);
    DoItMost: (unit -> unit);
}

Then you can

type MyController(functions: MyFunctions) =
    let a = functions.DoIt()
    let b = functions.DoItMore()
    let c = functions.DoItMost()

and you can instantiate the record type with references to a module's functions:

module MyModule =
    let doIt() =
        Console.WriteLine("Do It!")

    let doItMore() =
        Console.WriteLine("Do It More!")

    let doItMost() = 
        Console.WriteLine("Do It Most!")

    let myRecord = { 
        DoIt = doIt; 
        DoItMore = doItMore; 
        DoItMost = doItMost; 
    }

    let controller = 
        MyController(myRecord)

Alternatively you might want to just

type MyController(doIt: (unit -> unit), doItMore: (unit -> unit), doItMost: (unit -> unit))
// ... Etc

Side note: a lot of unit in F# is usually an indication of bad design.

Edit: Mark Seemann's answer is the correct answer to this question.

查看更多
神经病院院长
5楼-- · 2019-01-13 19:24

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)

查看更多
登录 后发表回答