Application architecture/composition in F#

十年热恋 提交于 2019-11-28 15:13:23
Mark Seemann

This is easy once you realize that Object-Oriented Constructor Injection corresponds very closely to Functional Partial Function Application.

First, I'd write Dings as a record type:

type Dings = { Lol : string; Rofl : string }

In F#, the IGetStuff interface can be reduced to a single function with the signature

Guid -> seq<Dings>

A client using this function would take it as a parameter:

let Client getStuff =
    getStuff(Guid("055E7FF1-2919-4246-876E-1DA71980BE9C")) |> Seq.toList

The signature for the Client function is:

(Guid -> #seq<'b>) -> 'b list

As you can see, it takes a function of the target signature as input, and returns a list.

Generator

The generator function is easy to write:

let GenerateDingse id =
    seq {
        yield { Lol = "Ha!"; Rofl = "Ha ha ha!" }
        yield { Lol = "Ho!"; Rofl = "Ho ho ho!" }
        yield { Lol = "asd"; Rofl = "ASD" } }

The GenerateDingse function has this signature:

'a -> seq<Dings>

This is actually more generic than Guid -> seq<Dings>, but that's not a problem. If you only want to compose the Client with GenerateDingse, you could simply use it like this:

let result = Client GenerateDingse

Which would return all three Ding values from GenerateDingse.

Decorator

The original Decorator is a little bit more difficult, but not much. In general, instead of adding the Decorated (inner) type as a constructor argument, you just add it as a parameter value to a function:

let AdsFilteredDingse id s = s |> Seq.filter (fun d -> d.Lol = "asd")

This function has this signature:

'a -> seq<Dings> -> seq<Dings>

That's not quite what we want, but it's easy to compose it with GenerateDingse:

let composed id = GenerateDingse id |> AdsFilteredDingse id

The composed function has the signature

'a -> seq<Dings>

Just what we're looking for!

You can now use Client with composed like this:

let result = Client composed

which will return only [{Lol = "asd"; Rofl = "ASD";}].

You don't have to define the composed function first; you can also compose it on the spot:

let result = Client (fun id -> GenerateDingse id |> AdsFilteredDingse id)

This also returns [{Lol = "asd"; Rofl = "ASD";}].

Alternative Decorator

The previous example works well, but doesn't really Decorate a similar function. Here's an alternative:

let AdsFilteredDingse id f = f id |> Seq.filter (fun d -> d.Lol = "asd")

This function has the signature:

'a -> ('a -> #seq<Dings>) -> seq<Dings>

As you can see, the f argument is another function with the same signature, so it more closely resembles the Decorator pattern. You can compose it like this:

let composed id = GenerateDingse |> AdsFilteredDingse id

Again, you can use Client with composed like this:

let result = Client composed

or inline like this:

let result = Client (fun id -> GenerateDingse |> AdsFilteredDingse id)

For more examples and principles for composing entire applications with F#, see my on-line course on Functional architecture with F#.

For more about Object-Oriented Principles and how they map to Functional Programming, see my blog post on the SOLID principles and how they apply to FP.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!