【问题标题】:F# functional CallbackF# 函数式回调
【发布时间】:2020-02-27 22:16:21
【问题描述】:

我正在尝试在 F# 中迁移 C# 程序。 该程序是一个FtpClient,它封装了FluentFtp库并通过如下接口暴露ftp方法:

  public interface IFtpSession
{
    IEnumerable<string> ListFiles(string remoteFolder);
}

sealed class FtpSession : IFtpSession
{
    private FluentFTP.FtpClient FluentFtpClient { get; set; }

    internal FtpSession(FluentFTP.FtpClient fluentFtpClient)
    {
        this.FluentFtpClient = fluentFtpClient;
    }

    public IEnumerable<string> ListFiles(string remoteFolder)
    {
        return FluentFtpClient.GetListing(remoteFolder).Where(fileItem => fileItem.Type == FtpFileSystemObjectType.File).Select(fileItem => fileItem.FullName);
    }

}}

private static void CreateFtpSession(FtpConnectionSettings settings, Action<IFtpSession> onSessionOpen) {
        FluentFTP.FtpClient ftpClient = new FluentFTP.FtpClient(settings.Host, settings.Port, settings.UserName, settings.UserPassword);
        ftpClient.Connect();
        FtpSession ftpSession = new FtpSession(ftpClient);
        onSessionOpen(ftpSession);
        ftpClient.Disconnect();
    }

我想在 F# 中做同样的事情,但以一种功能性的方式。这意味着我不想使用具有成员的对象以 OO F# 样式复制上述代码,而是仅使用函数和值类型。

做这种工作的正确方法是什么?

在上面的示例中,只有一个函数(ListFiles),但我的问题是假设接口 IFtpSession 可以具有其他功能并且不尊重单一责任原则。

非常感谢您的帮助

【问题讨论】:

    标签: design-patterns callback functional-programming f#


    【解决方案1】:

    functional programming 中非常重要的两件事是:

    • datafunctions 分开(就像您在问题中提到的那样)。
    • 分隔 pureimpure(所有具有副作用)代码。不纯代码是不可预测且难以测试的,因此我们希望尽可能多的代码,并将不纯代码推送到应用程序的边界。

    将您的代码调整为F#,您可以获得类似的结果

    open FluentFTP
    
    let listFiles (client:FtpClient, remoteFolder:string) =
        let items = client.GetListing(remoteFolder)
        items
    
    let downloadFiles (client:FtpClient, localDir:string, remotePaths:seq<string>) =
        let _ = client.DownloadFiles(localDir, remotePaths)
        () // return unit, similar to 'void' in C#
    
    [<EntryPoint>]
    let main _ =
    
        use client =  new FtpClient() // pass arguments in ctor
        client.Connect();
    
        let items = listFiles (client, "dir") // impure
        let fileNames = items |> Seq.filter (fun x -> x.Type = FtpFileSystemObjectType.File) // pure
                              |> Seq.map (fun x -> x.FullName)
    
        let localDir = "C:\Temp"
        downloadFiles (client, localDir, fileNames) // impure
        0 // return an integer exit code (client also gets disposed)
    

    这是一个控制台应用程序,编译(我还没有测试过)。 main 创建 IDisposable FtpClient,通过使用 use 关键字处理。您可以将这个main 视为应用程序的dependency injection 位,它创建并处理所有事物的所有依赖项。 clientlistFilesdownloadFiles 两个函数重用,结果与您的 回调 设置相同。

    在这里您还可以看到纯代码和非纯代码分开;不纯的代码是使用ftp 客户端的代码,而纯代码正在过滤和映射来自ftp 的结果(如果您将过滤和映射代码提取到一个单独的函数中,您可以unit test 这个,但我留下了那个在这个例子中)。

    希望这能澄清一点。

    【讨论】:

    • 您好,非常感谢您的回答。但是使用这个解决方案,我必须为每个操作打开一个 ftp 会话。例如,如果我想做一个 listFiles 后跟一个 GetFiles,我想在同一个会话下执行它而不重新打开一个新会话。在 C# 中,我将使用一个回调操作,该操作将我的 IFtpSession 接口作为参数(如我的示例中所示)。然后调用者可以调用同一会话下的任何函数。我怎么能在函数式 F# 中做到这一点?
    • @Fede 这很简单,您只需将回调函数作为参数传递给listFiles 并在客户端被释放之前调用它。或者您可以传入客户端并自行处理。它甚至可能会产生更简洁的代码来预先收集所需的操作。然后,您可以实例化一个客户端,通过一系列操作发送它,并在最后处理它。
    • @Fede 我已经更新了我的答案。现在您可以通过将client 作为参数显式传递来重新使用它。参数现在是tuples,这是因为它们是输入的。
    • @aage,您的解决方案运行良好。但是在 C# 代码中,调用者并不关心会话的打开和处置。 FluentFTP 库未公开。他只能通过 onOpenSession 回调看到接口和不同的可调用函数。实际上,我可以在 F# 中做同样的事情并将 FluentFTP api 包装在我自己的类下,如此处所述fssnip.net/k8/title/F-Strategy-Pattern,但后来我又回到了 OO 范例。但如果有办法的话,我看不出我怎么能以纯粹的功能方式完成这项工作..
    • @Fede 我明白你在说什么,我的解决方案封装了这些细节,而你的解决方案却封装了这些细节。如果你想要这种抽象,你可以有一个函数,它接受一个ftp 函数,其中类型为string -&gt; string list(对于你的listFiles 函数),然后这可以部分应用于依赖注入部分应用程序。使用 this 函数的函数将不关心 (FluentFTP) 实现。
    【解决方案2】:

    使用Computation Expression 能够在不同的 FTP 工作流程中重复使用可能是您正在寻找的最强大的解决方案。您甚至可以使用内置的异步表达式。可能是这样的:

    然后像这样使用它

    async {
       using ftp = FluentFTP...
       // do your things here
    }
    |> Async.RunSynchronously
    

    或者,您可以构建自己的计算表达式来处理所有构建和拆卸,但这是 F# 中更强大的构造之一,往往被忽略。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-07-05
      • 2011-02-11
      • 1970-01-01
      • 1970-01-01
      • 2012-06-21
      • 1970-01-01
      • 1970-01-01
      • 2022-10-01
      相关资源
      最近更新 更多