И здесь я немного застрял. Я могу сделать его дружественным к F# или к C#, но проблема в том, как сделать его дружественным для обоих.
Вот пример. Представьте, что у меня есть следующая функция в F#:
Код: Выделить всё
let compose (f: 'T -> 'TResult) (a : 'TResult -> unit) = f >> a
Код: Выделить всё
let useComposeInFsharp() =
let composite = compose (fun item -> item.ToString) (fun item -> printfn "%A" item)
composite "foo"
composite "bar"
Код: Выделить всё
FSharpFunc compose(FSharpFunc f, FSharpFunc a);
Код: Выделить всё
Action compose2(Func f, Action a);
Код: Выделить всё
let compose2 (f: Func) (a : Action(f.Invoke >> a.Invoke)
Код: Выделить всё
void UseCompose2FromCs()
{
compose2((string s) => s.ToUpper(), Console.WriteLine);
}
Код: Выделить всё
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#, а вторая - для пользователей 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#:Тем самым мы нарушаем идиоматическое использование F#, потому чтоКод: Выделить всё
[] type Foo = static member bar() = ... static member zoo() = ...
мы больше не можем использовать bar и Zoo без префикса Foo.
- F#: используйте пространства имен или модули для содержания ваши типы и модули.
- Использование кортежей
- 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
Мобильная версия