函数式编程和依赖性反转:如何抽象存储?

Ove*_*urg 8 f# storage abstraction functional-programming xamarin

我正在尝试创建一个具有较低级别库的解决方案,该库将知道在调用某些命令时需要保存和加载数据,但是在特定于平台的项目中将提供保存和加载函数的实现它引用了较低级别的库.

我有一些模型,例如:

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

type Situation = { SituationID: SituationID }
Run Code Online (Sandbox Code Playgroud)

而我想要做的是能够定义和调用以下函数:

do saveUser ()
let user = loadUser (UserID 57)
Run Code Online (Sandbox Code Playgroud)

有没有办法在功能习语中干净地定义它,最好是在避免可变状态时(无论如何都不需要)?

一种方法可能看起来像这样:

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)
Run Code Online (Sandbox Code Playgroud)

这方面有各种变化,但我能想到的所有都涉及某种未初始化的状态.(在Xamarin中,还有DependencyService,但这本身就是我想要避免的依赖.)

有没有办法编写调用存储函数的代码,但尚未实现,然后实现它,没有使用可变状态?

(注意:这个问题不是关于存储本身 - 这只是我正在使用的例子.它是关于如何在不使用不必要的可变状态的情况下注入函数.)

Mar*_*ann 15

这里的其他答案可能会教你如何在F#中实现IO monad,这当然是一种选择.但是在F#中,我经常只用其他函数编写函数.您不必定义"接口"或任何特定类型即可.

Outside-In开发您的系统,并通过关注他们需要实现的行为来定义您的高级功能.通过将依赖项作为参数传递,使它们成为高阶函数.

需要查询数据存储?传递loadUser参数.需要保存用户?传递saveUser参数:

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

loadUser参数被推断为类型User -> User option,saveUser因为User -> unit,因为doSomethingInterestingWith是类型的函数User -> User.

您现在可以"实现" loadUsersaveUser编写调用较低级库的函数.

我对这种方法的典型反应是:这将要求我向我的函数传递太多参数!

实际上,如果发生这种情况,请考虑这是否是该功能试图做太多的气味.

由于在这个问题的标题中提到了依赖性倒置原则,我想指出,如果所有这些原则都是一致的,那么SOLID原则的效果最好.该接口分离原则说,接口应尽可能小,而你没有得到他们比当每一个"接口"是一个单一功能的小.

有关描述此技术的更详细的文章,您可以阅读我的Type-Driven Development文章.