函数式编程和依赖倒置:如何抽象存储? [英] Functional programming and dependency inversion: how to abstract storage?

查看:27
本文介绍了函数式编程和依赖倒置:如何抽象存储?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to create a solution that has a lower-level library 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 in a platform-specific project which references the lower-level library.

我有一些模型,例如:

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

type Situation = { SituationID: SituationID }

而我想要做的是能够定义和调用函数,例如:

And what I want to do is be able to define and call functions such as:

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

有什么方法可以在函数式惯用语中清楚地定义它,最好同时避免可变状态(无论如何都不需要)?

Is there any way to define this cleanly in the functional idiom, preferably while avoiding mutable state (which shouldn't be necessary anyway)?

一种方法可能如下所示:

One way to do it 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)

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

And there are variations on this, but all the ones I can think of involve some kind of uninitialized state. (In Xamarin, there's also DependencyService, but that is itself a dependency I would like to avoid.)

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

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

(Note: this question is not about storage itself -- that's just the example I'm using. It's about how to inject functions without using unnecessary mutable state.)

推荐答案

这里的其他答案可能会教您如何在 F# 中实现 IO monad,这当然是一个选项.但是,在 F# 中,我通常只是将函数与其他函数组合.为此,您不必定义接口"或任何特定类型.

Other answers here will perhaps educate you on how to implement the IO monad in F#, which is certainly an option. In F#, though, I'd often just compose functions with other functions. You don't have to define an 'interface' or any particular type in order to do this.

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

Develop your system from the Outside-In, and define your high-level functions by focusing on the behaviour they need to implement. Make them higher-order functions by passing in dependencies as arguments.

需要查询数据存储?传入 loadUser 参数.需要保存用户吗?传入一个 saveUser 参数:

Need to query a data store? Pass in a loadUser argument. Need to save the user? Pass in a saveUser argument:

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

loadUser 参数被推断为类型 User ->用户选项,和saveUser as User ->unit,因为 doSomethingInterestingWithUser -> 类型的函数用户.

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

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

You can now 'implement' loadUser and saveUser by writing functions that call into the lower-level library.

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

The typical reaction I get to this approach is: That'll require me to pass in too many arguments to my function!

确实,如果发生这种情况,请考虑这是否是函数试图做太多事情的气味.

Indeed, if that happens, consider if that isn't a smell that the function is attempting to do too much.

由于这个问题的标题中提到了依赖倒置原则,我想指出SOLID 原则如果全部应用于音乐会.Interface Segregation Principle 说接口应该尽可能小,而你没有得到它们比每个接口"都是单个函数时要小.

Since the Dependency Inversion Principle is mentioned in the title of this question, I'd like to point out that the SOLID principles work best if all of them are applied in concert. The Interface Segregation Principle says that interfaces should be as small as possible, and you don't get them smaller than when each 'interface' is a single function.

有关描述此技术的更详细文章,您可以阅读我的类型- 驱动开发文章.

For a more detailed article describing this technique, you can read my Type-Driven Development article.

这篇关于函数式编程和依赖倒置:如何抽象存储?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆