Functional programming and dependency inversion: how is abstract storage?

I am trying to create a solution with a library of a lower level that will know that it needs to save and load data when certain commands are called, but the implementation of the save and load functions will be provided on a platform-specific project that references the lower level library.

I have some models, for example:

type User = { UserID: UserID Situations: SituationID list } type Situation = { SituationID: SituationID } 

And what I want to do is the ability to define and call functions such as:

 do saveUser () let user = loadUser (UserID 57) 

Is there a way to define this purely in a functional idiom, preferably avoiding a volatile state (which should not be necessary in any case)?

One way to do this might look something like this:

 type IStorage = { saveUser: User->unit; loadUser: UserID->User } module Storage = // initialize save/load functions to "not yet implemented" let mutable storage = { saveUser = failwith "nyi"; loadUser = failwith "nyi" } // ....elsewhere: do Storage.storage = { a real implementation of IStorage } do Storage.storage.saveUser () let user = Storage.storage.loadUser (UserID 57) 

And there are variations in this, but everything that I can think of is connected with some kind of uninitialized state. (Xamarin also has a DependencyService, but this is a dependency itself, which I would like to avoid.)

Is there a way to write code that calls a storage function that is not yet implemented, and then implement it WITHOUT using a mutable state?

(Note: this question does not concern the repository itself, but only what I use. It's about how to enter functions without using an unnecessary mutable state.)

+8
functional-programming abstraction f # xamarin storage
source share
2 answers

The other answers here may teach you how to implement the IO monad in F #, which is certainly an option. However, in F #, I often just composed functions with other functions . You do not need to define an “interface” or any specific type for this.

Design your system from an External Entrance and define your high-level functions by focusing on the behavior that they need to implement. Make them higher order functions by passing dependencies as arguments.

Do I need to request a data warehouse? Go to the loadUser argument. Do you need to save the user? Go to the saveUser argument:

 let myHighLevelFunction loadUser saveUser (userId) = let user = loadUser (UserId userId) match user with | Some u -> let u' = doSomethingInterestingWith u saveUser u' | None -> () 

The loadUser argument loadUser defined as the type User -> User option and saveUser as the User -> unit , because doSomethingInterestingWith is a function of the type User -> User .

Now you can "implement" loadUser and saveUser by writing functions that call the lower level library.

The typical reaction I get for this approach is: It will require me to pass too many arguments to my function!

In fact, if this happens, consider whether it is a smell that the function is trying to do too much.

Since the principle of Dependency Inversion Principle is mentioned in the title of this question, I would like to point out that SOLID Principles work best if they are all applied in a consistent manner. The principle of segment separation says that the interfaces should be as small as possible, and you will not get them less than when each "interface" is a single function.

For a more detailed article describing this technique, you can read my article An article by type .

+15
source share

You can abstract storage behind the IStorage interface. I think that was your intention.

 type IStorage = abstract member LoadUser : UserID -> User abstract member SaveUser : User -> unit module Storage = let noStorage = { new IStorage with member x.LoadUser _ -> failwith "not implemented" member x.SaveUser _ -> failwith "not implemented" } 

In another part of your program, you may have several storage implementations.

 type MyStorage() = interface IStorage with member x.LoadUser uid -> ... member x.SaveUser u -> ... 

And after you have defined all your types, you can decide what to use.

 let storageSystem = if today.IsShinyDay then MyStorage() :> IStorage else Storage.noStorage let user = storageSystem.LoadUser userID 
+1
source share

All Articles