F # функциональный обратный вызов - PullRequest
0 голосов
/ 01 ноября 2019

Я пытаюсь перенести программу на C # на F #. Программа представляет собой 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 может иметь другие функции и не соответствует принципу единой ответственности.

Большое спасибоза вашу помощь

Ответы [ 2 ]

3 голосов
/ 01 ноября 2019

Две вещи, которые очень важны для functional programming:

  • Разделение data и functions (как вы упомянули в своем вопросе).
  • Разделение pure и impure (все с побочными эффектами ) кода. Нечистый код непредсказуем и труден для тестирования, поэтому мы хотим получить как можно больше чистого кода и перенести нечистый код в границы приложения.

Адаптируя свой код в 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 бите приложения, оно создает и удаляет все зависимости всех вещей. client повторно используется обеими функциями listFiles и downloadFiles, что дает тот же результат, что и ваша функция обратного вызова .

Здесь вы также видите чистый и нечистый код, разделенные;нечистым является код с использованием клиента ftp, а чистый код фильтрует и отображает результаты из ftp (если вы извлекаете код фильтрации и сопоставления в отдельную функцию, вы могли бы unit test this, но я оставил этов этом примере).

Надеюсь, это немного прояснит ситуацию.

1 голос
/ 02 ноября 2019

Использование Вычислительного выражения для возможности повторного использования в различных рабочих процессах FTP может быть наиболее надежным решением, которое вы ищете. Вы могли бы даже использовать встроенное асинхронное выражение. Это может быть что-то вроде:

А затем использовать его как

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

В качестве альтернативы, вы можете создать собственное вычислительное выражение для обработки всего процесса сборки и разборки, но этоодна из наиболее мощных конструкций в F #, которой обычно пренебрегают.

...