Application architecture/composition in F#

后端 未结 1 400
[愿得一人]
[愿得一人] 2020-12-12 11:03

I have been doing SOLID in C# to a pretty extreme level in recent times and at some point realized I\'m essentially not doing much else than composing functions nowadays. An

相关标签:
1条回答
  • 2020-12-12 11:13

    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.

    0 讨论(0)
提交回复
热议问题