Лучший подход к разработке библиотек F# для использования как в F#, так и в C#.C#

Место общения программистов C#
Ответить
Anonymous
 Лучший подход к разработке библиотек F# для использования как в F#, так и в C#.

Сообщение Anonymous »

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

Код: Выделить всё

let compose (f: 'T -> 'TResult) (a : 'TResult -> unit) = f >> a
Это прекрасно можно использовать из F#:

Код: Выделить всё

let useComposeInFsharp() =
let composite = compose (fun item -> item.ToString) (fun item -> printfn "%A" item)
composite "foo"
composite "bar"
В C# функция compose имеет следующую сигнатуру:

Код: Выделить всё

FSharpFunc compose(FSharpFunc f, FSharpFunc a);
Но, конечно, мне не нужен FSharpFunc в подписи, вместо этого мне нужны Func и Action, вот так:

Код: Выделить всё

Action compose2(Func f, Action a);
Для этого я могу создать функцию compose2 следующим образом:

Код: Выделить всё

let compose2 (f: Func) (a : Action(f.Invoke >> a.Invoke)
Это прекрасно можно использовать в C#:

Код: Выделить всё

void UseCompose2FromCs()
{
compose2((string s) => s.ToUpper(), Console.WriteLine);
}
Но теперь у нас возникла проблема с использованием compose2 из F#! Теперь мне нужно обернуть все стандартные функции F# fun в Func и Action, например:

Код: Выделить всё

let useCompose2InFsharp() =
let f = Func(fun item -> item.ToString())
let a = Action(fun item -> printfn "%A" item)
let composite2 = compose2 f a

composite2.Invoke "foo"
composite2.Invoke "bar"
Вопрос: Как мы можем добиться первоклассного взаимодействия с библиотекой, написанной на F#, для пользователей F# и C#?
Пока я не смог придумать ничего лучшего, чем эти два подхода:
  • Две отдельные сборки: одна предназначена для пользователей F#, а вторая - для пользователей C#.
  • Одна сборка, но разные пространства имен: одно для пользователей F#, второе для пользователей C#.
Для первого подхода я бы сделал что-то вроде этого:
  • Создайте проект F#, назовите его FooBarFs и скомпилируйте его в FooBarFs.dll.
    • Нацельте библиотеку исключительно на F# пользователей.
    • Скройте все ненужное из файлов .fsi.
  • Создайте еще один проект F#, вызовите if FooBarCs и скомпилируйте его в FooFar.dll.
    • Повторно используйте первый проект F# на уровне исходного кода.
    • Создайте файл .fsi, который скроет все от этот проект.
    • Создайте файл .fsi, который представляет библиотеку в стиле C#, используя идиомы C# для имени, пространств имен и т. д.
    • Создайте оболочки, которые делегируют основную библиотеку, выполняя преобразование там, где это необходимо.
Я думаю, что второй подход с пространствами имен может сбить с толку пользователей, но у вас есть один сборка.
Вопрос: Ни один из них не идеален, возможно, мне не хватает какого-то флага/переключателя/атрибута компилятора
или какого-то трюка, и есть лучший способ сделать это?
Вопрос: пытался ли кто-нибудь еще достичь чего-то подобного, и если да, то как вы это сделали?
EDIT: чтобы уточнить, вопрос касается не только функций и делегатов но общий опыт пользователя C# с библиотекой F#. Сюда входят пространства имен, соглашения об именах, идиомы и тому подобное, присущие C#. По сути, пользователь C# не сможет обнаружить, что библиотека была создана на F#. И наоборот, пользователь F# должен испытывать желание иметь дело с библиотекой C#.

РЕДАКТИРОВАНИЕ 2:
Из ответов и комментариев я вижу, что моему вопросу не хватает необходимой глубины,
возможно, в основном из-за использования только одного примера, в котором возникают проблемы совместимости между F# и C#
, а именно проблемы значений функций. Я думаю, что это самый очевидный пример, и поэтому
побудил меня использовать его, чтобы задать вопрос, но в то же время создалось впечатление, что это
единственный вопрос, который меня беспокоит.
Позвольте мне привести более конкретные примеры. Я прочитал самый превосходный документ
F# Component Design Guidelines
(большое спасибо @gradbot за это!). Рекомендации в документе, если они используются, действительно решают
некоторые проблемы, но не все.
Документ разделен на две основные части: 1) рекомендации для пользователей F#; и 2) рекомендации по
ориентации пользователей C#. Нигде даже не делается попытка сделать вид, что возможен единый
подход, что в точности перекликается с моим вопросом: мы можем ориентироваться на F#, мы можем ориентироваться на C#, но каково
практическое решение для обеих целей?
Напоминаем, что цель состоит в том, чтобы иметь библиотеку, написанную на F#, и которую можно было бы использовать идиоматически из
как языков F#, так и C#.
Ключевое слово здесь — идиоматический. Проблема не в общей совместимости, при которой можно
использовать библиотеки на разных языках.
Теперь перейдем к примерам, которые я беру прямо из
F# Component Design Guidelines.
  • Модули+функции (F#) против пространств имен+типов+функций
    • F#: используйте пространства имен или модули для содержания ваши типы и модули.
      Идиоматическое использование — размещение функций в модулях, например:

      Код: Выделить всё

      // library
      module Foo
      let bar() = ...
      let zoo() = ...
      
      // Use from F#
      open Foo
      bar()
      zoo()
      
    • C#: используйте пространства имен, типы и члены в качестве основной организационной структуры для ваших
      компонентов (в отличие от модулей) для ванильных API .NET.
      Это несовместимо с рекомендациями F#, и пример необходимо будет
      переписать, чтобы он подходил пользователям C#:

      Код: Выделить всё

      []
      type Foo =
      static member bar() = ...
      static member zoo() = ...
      
      Тем самым мы нарушаем идиоматическое использование F#, потому что
      мы больше не можем использовать bar и Zoo без префикса Foo.
  • Использование кортежей
    • F#: используйте кортежи, когда это необходимо для возврата значения.
    • C#: избегайте использования кортежей в качестве возвращаемых значений в стандартных API .NET.
  • Async
    • F#: используйте Async для асинхронного программирования на границах API F#.
    • C#: Предоставляйте асинхронные операции, используя либо модель асинхронного программирования .NET
      (BeginFoo, EndFoo), либо методы, возвращающие задачи .NET (Task), а не асинхронные
      объекты F#.
  • Использование Option
    • F#: рассмотрите возможность использования значений параметров для возврата типов вместо создания исключений (для кода, ориентированного на F#).
    • Рассмотрите возможность использования шаблона TryGetValue вместо возврата значений параметров F# (опция) в ванильных API
      .NET и отдайте предпочтение перегрузке метода, а не использованию значений параметров F# в качестве аргументов.
  • Дискриминированные объединения
    • F#: используйте размеченные объединения в качестве альтернативы иерархиям классов для создания древовидных данных
    • C#: нет конкретных указаний для этого, но концепция размеченных объединений чужда C#
  • Каррированные функции
    • F#: каррированные функции являются идиоматическими для F#.
    • C#: не используйте каррирование параметров в стандартных API .NET.
  • Проверка нулевых значений
    • F#: это не идиоматично для F#
    • C#: рассмотрите возможность проверки нулевых значений на границах стандартного .NET API.
  • Использование списка типов F#, карты, set и т. д.
    • F#: Идиоматично использовать их в F#
    • C#: рассмотрите возможность использования типов интерфейса коллекции .NET IEnumerable и IDictionary
      для параметров и возвращаемых значений в стандартных API .NET. (т. е. не используйте список F#, карту, set)
  • Типы функций (очевидный)
    • F#: использование функций F# в качестве значений является идиоматическим для F#, очевидно
    • C#: используйте типы делегатов .NET вместо типов функций F# в ванильных API .NET.
Я думаю, этого должно быть достаточно, чтобы продемонстрировать суть моего вопроса.
Кстати, в рекомендациях также есть частичный ответ:

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

Подводя итог.
Есть один определенный ответ: компилятора не существует. приемы, которые я пропустил.
Согласно руководящему документу, кажется, что сначала разработка для F#, а затем создание
оболочки фасада для .NET является разумной стратегией.
Тогда остается вопрос относительно практической реализации этого:
  • Отдельные сборки? или
  • Разные пространства имен?


Подробнее здесь: https://stackoverflow.com/questions/101 ... nd-c-sharp
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C#»