[.NET, C#, F#] Букварь по F# для любопытствующих C#-разработчиков (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Предисловие
Мой переход на F# в качестве излюбленного языка был слегка усеян препятствиями. Примерно через десять лет почти постоянного использования C# у меня пробудилось любопытство, когда я услышал об этом другом #-языке. Моя первая реакция была той, которую с тех пор видел у других C#-разработчиков — отрицание, — C# является хорошим языком, и мне с ним комфортно, так зачем тратить силы на изучение другого? Но любопытство осталось — и, по крайней мере, несколько раз выделил вечер, чтобы прочитать базовый вводный пост и попытаться написать каких-нибудь ката на F#. Это не прижилось, потому что я просто чувствовал себя потерянным и не мог воплотить свой опыт использования C# в ощущение даже отдаленного комфорта с F#. Достаточно легко опустить фигурные скобки, немного замяться, чтобы не забыть let вместо var — но как сделать то, что я хотел?
Тогда я этого не осознавал, но, на мой взгляд, наблюдал потенциальный недостаток в том, как F#-разработчики говорят, описывают и представляют свой язык внешнему миру. Существует обширная база материалов обо всех возможностях и функциональности F#: Algebraic Data Types, Exhaustive Matching, Type Inference и т.д. Есть много статей, посвященных тому, как решать широкий спектр задач с помощью F#. Но, как мне кажется, не хватает чего-то вроде следующего: некоторых указаний о том, как взять то, что вам уже удобно в C#, и перевести их на F#. Так что мне интересно, можем ли мы как-то закрыть этот недостаток.
При этом от читателя требуется немного — поверхностное знакомство с тремя основными моментами синтаксиса F#:
- let используется как var в C# — для объявления переменной;
- |> — это оператор пайпа (piping) в F#, который берет результат левой части и передает его в качестве аргумента для правой части;
- F# использует строчные буквы и апостроф для аннотаций обобщенного типа, поэтому SomeType<T> представлен как SomeType<'a>.
Остальное должно быть понятно из практики и контекста по мере продвижения. Это не должно быть исчерпывающим, замысловатым руководством, но обладать достаточной информацией, чтобы охватить большинство начальных вопросов и поставить людей на правильный путь. Букварь, если хотите.
- Мне необходимо
- Работать с коллекциями
- Работать асинхронно
- Сообщать об ошибке или контролировать выполнение программы
- Использовать C#-библиотеки в F#
Мне необходимо
Работать с коллекциями
В F# базовые типы коллекций (в основном) как правило очень похожи на C#, но часто имеют (иногда незначительные) различия в поведении для обеспечения иммутабельности. В большинстве случаев функции, которые работают с этими коллекциями, будут возвращать ссылки и не будут изменять содержимое исходной ссылки.
Подобрать тип коллекции
Что-то похожее на Array<T>
Тебе повезло! Массивы в F# такие же как в C#. Однако следует отметить несколько моментов:
- Массивы в F# обычно используют нотацию [|element|], потому что [] — это нотация для списков в F#.
- Для разделения элементов коллекции в F# используется точка с запятой, а не запятая: [|elementA;elementB|].
- Для доступа по индексу в F# требуется префиксная точка перед фигурными скобками:
let myArray = [|1;2;3|]
myArray.[1] // 2
- F# также предлагает многомерные массивы до 4-х измерений через типы Array2<'a>, Array3<'a> и Array4<'a>.
Что-то похожее на List<T>
По умолчанию в F# тип списка немного отличается от типа List<T> в C#.
Вот что вам нужно знать:
- Списки в F# обычно используют нотацию [element] в отличие от массивов.
- Списки, как и массивы, разделяют элементы точками с запятой вместо запятых: [elementA;elementB]
- Списки в F# реализованы как односвязные списки — это означает, что добавление отдельных элементов выполняется в начале списка с помощью оператора :::
let myList = [1;2;3]
4 :: myList // [4;1;2;3]
- Если нам необходимо добавить в конец, мы можем использовать оператор @ для объединения двух списков:
let listA = [1;2]
let listB = [3;4]
listA @ listB // [1;2;3;4]
Что-то похожее на Dictionary<TKey,TValue>
По мотивам списка «выглядит похоже, но не нет» — F# предоставляет стандартный Map<'key,'value> тип, который не является родным для C# Dictionary<TKey,TValue>, но реализует обычную группу интерфейсов .NET, таких как IDictionary<TKey,TValue> и IEnumerable<T>
Вот что вам нужно знать:
- Словари могут быть созданы из любой коллекции двух элементных кортежей, где первый элемент является ключом, а второй — значением:
[(1,2);(3,4)] |> Map.ofList // [1] = 2, [3] = 4
- Если создаем из последовательности, где есть дубликаты, то последний элемент для данного ключа является значением:
[(1,2);(1,3)] |> Map.ofList |> Map.find 1 = 3 // true
- Верен и обратный процесс: словари можно легко превратить в коллекции кортежей из двух элементов:
[(1,2);(3,4)] |> Map.ofList |> Map.toList // [(1,2);(3,4)]
- Встроенный тип Map в F# не очень хорошо подходит для использования в C#, в случаях интеропа мы можем создать более удобный для C# словарь IDictionary, используя функцию dict с любой коллекцией кортежей из двух элементов. Но учтите, что это по-прежнему неизменяемая структура, и при попытках добавить в нее элементы будет генерироваться исключение.
[(1,2);(3,4)] |> dict
Подобрать функцию
Одно важное различие между F# и C#, когда дело доходит до работы с коллекциями, заключается в том, что в C# вы, как правило, оперируете над экземпляром коллекции, используя метод этого типа через точку; в то время как F# предпочитает предоставлять семейства функций в модулях, которые принимают экземпляры в качестве аргумента. Итак, C#-вариант myDictionary.Add(someKey,someValue) в F# будет Map.add someKey someValue myMap.
Просто хочу свой LINQ
F# предлагает функции, аналогичные тем, с которыми программисты на C# знакомы по LINQ, но названия часто отличаются, поскольку F# использует систему условных обозначений, которая больше соответствует терминологии, используемой в остальной части мира функционального программирования. Будьте уверены, они в основном ведут себя так, как вы и ожидаете. Дабы не утомлять — LINQ огромен, — я сопоставлю, по моему опыту, наиболее распространенные методы LINQ и их аналоги на F#:
- .Aggregate() именуется как .fold или .reduce, в зависимости от того, предоставляете ли вы начальное состояние или просто используете первый элемент, соответственно;
- .Select() именуется как .map;
- .SelectMany() именуется как .collect;
- .Where() именуется как .where или .filter (одно и то же, два имени, длинная история)
- .All() именуется как .forall;
- .Any() именуется как .exists, если мы подаем предикат, или .isEmpty, если мы просто хотим знать, есть ли в коллекции какие-либо элементы;
- .Distinct() по-прежнему как .distinct или .distinctBy, если мы подаем функцию проекция;
- .GroupBy() по-прежнему как .groupBy;
- .Min() и .Max() по-прежнему остаются как .min и .max с альтернативами .minBy и .maxBy для использования проекции
- .OrderBy() именуется как .sortBy, и аналогично .OrderByDescending() именуется как .sortbyDescending;
- .Reverse() именуется как .rev;
- .First() именуется как .head, если нам нужен первый элемент, или .find, если нам нужен первый элемент, который соответствует предикату. Точно так же вместо .FirstOrDefault() мы используем .tryHead и .tryFind, которые вернут Option, являющимся либо Some matchingValue, либо None, когда он не найден, вместо того, чтобы выбрасывать исключение;
- .Single() именуется как .exactlyOne, и аналогично .SingleOrDefault() именуется как .tryExactlyOne.
Не уверен, какая функция нужна. У меня есть
Коллекция, а хочу
Отдельное значение или элемент
- .min, .minBy, .max и .maxBy найдут элемент коллекции относительно других;
- .sum, .sumBy, .average, .averageBy;
- .find, .tryFind, .pick и .tryPick позволят найти один конкретный элемент коллекции;
- .head, .tryHead, .last и .tryLast найдут элементы из начала или конца коллекции;
- .fold и .reduce позволят применить логику и использовать каждый элемент коллекции для формирования другого значения;
- .foldBack и .reduceBack делают то же самое, но с конца коллекции.
Равное количество элементов
- .map позволит преобразовать каждый элемент коллекции;
- .indexed свернет каждый элемент вашей коллекции в кортеж, первым элементом которого является индексом в коллекции: например, [1] станет [(0,1)];
- .mapi делает это неявно, учитывая индекс в качестве дополнительного первого аргумента функции маппинга;
- .sort, .sortDescending, .sortBy и .sortByDescending позволяют изменить порядок вашей коллекции.
Возможно меньшее количество элементов
- .filter вернет коллекцию, содержащую только элементы, соответствующие указанному предикату;
- .choose похож на .filter, но заодно позволяет маппить элементы;
- .skip вернет оставшиеся элементы после игнорирования первых n;
- .take и .truncate возвращают первые n-элементов, выбрасывая или нет исключение, соответственно;
- .distinct и .independentBy позволят удалить дубликаты из коллекции.
Возможно большее количество элементов
- .collect применит функцию создания коллекции к каждому элементу вашей коллекции и объединит все результаты воедино.
Чтобы изменить форму коллекции
- .windowed вернет новую коллекцию всех групп размером n из исходной коллекции: например, [1; 2; 3] станет [[1; 2]; [2; 3]], когда n = 2;
- .groupBy вернет новую коллекцию кортежей, где первый элемент является ассоциативным ключом, а второй — набором начальных элементов, которые соответствуют ассоциации: например, [1; 2; 3], преобразованной (fun i -> i % 2), приведет к [(0, [2]); (1, [1; 3])];
- .chunkBySize вернет новую коллекцию, содержащую до n коллекций оригинала: например, [1; 2; 3] станет [[1; 2]; [3]], когда n = 2;
- .splitInto вернет новую коллекцию, содержащую n коллекций одинакового размера из исходного: например, [1; 2; 3] станет [[1]; [2]; [3]], когда n = 3.
Чтобы пройти по коллекции без ее изменения
- .iter и .iteri берут и применяют функцию к каждому элементу вашей коллекции, но не возвращают никакого значения.
Отдельное значение и хочу
Чтобы было частью коллекции
- .singleton можно использовать для создания коллекции из одного элемента из значения;
- .init примет размер и функцию инициализатора и создаст новую коллекцию этого размера.
Несколько коллекций и хотите
Скомбинировать их
- .append принимает две коллекции и создает новую единую коллекцию, содержащую все элементы обеих;
- .concat делает то же самое, но для коллекции коллекций;
- .map2 и .fold2 действуют как выше указанные .map и .fold, но будут предоставлять элементы из одного индекса в двух исходных коллекциях для функции маппинга / свертки;
- .allPairs принимает две коллекции и образует все перестановки по 2 элемента между ними;
- .zip и .zip3 берут 2 (или 3) коллекции и создают одну коллекцию, состоящую из кортежей элементов из одного индекса в источниках.
Работать асинхронно
Модель асинхронности в F# похожа на модель в C#, но имеет несколько важных отличий, которые иногда застают врасплох C#-разработчиков:
- F# имеет отдельный тип Async<'t>, похожий на Task<T> в C#.
- Из-за того, что система типов F# требует возврата, она использует Async<unit> вместо Task в случаях, когда мы не возвращаем фактического значения.
- F# может генерировать и использовать Task<T> с помощью функций Async.StartAsTask и Async.AwaitTask из базовой библиотеки.
У F# есть еще одно очень заметное отличие от C# в отношении асинхронного кода: C# "включает" ключевое слово await внутри метода, применяя ключевое слово async к сигнатуре этого метода; F# использует языковую функцию, называемую computation expression, в результате чего асинхронность становится частью тела функции. Это также имеет некоторые последствия на то, как вы пишете код внутри этого тела функции:
let timesTwo i = i * 2 // У нас есть определение нашей базовой функции
// И теперь мы можем сделать это асинхронным
let timesTwoAsync i = async { // Обратите внимание, что при работе с computation expression мы начинаем с нашего ключевого слова, а затем с самой функции внутри фигурных скобок
return i * 2 // Мы также используем ключевое слово `return` для завершения выражения
}
let timesFour i = async {
let! doubleOnce = timesTwoAsync i // Обратите внимание на `!` в нашем `let!` — это похоже на `await` в C# — правосторонняя функция должна возвращать `Async<'a>`
// После того, как мы связали результат асинхронной функции с помощью `let!` — мы можем использовать его потом как обычно
let doubleTwice = timesTwo doubleOnce // В случае неасинхронных функций мы можем написать наш код как обычно
return doubleTwice
}
- Имейте в виду, что let! в Async-блоках работают только при вызове Async-образующих функций — аналогично тому, как в C# await можно использовать только для методов, возвращающих Task.
- Другой путь, однако, заключается в том, что поскольку F# обрабатывает асинхронность исключительно в теле функций, нет никаких требований о том, какие функции вы можете связывать с let! — все, что возвращает Async<'a>, допустимо. Это противоположно требованиям C# о том, что вы можете применять await только к методам, помеченным как async.
Сообщать об ошибке или контролировать выполнение программы
Во-первых, определение: когда мы говорим об ошибках и выполнении программы, я не имею в виду исключения — в F# они есть и вполне схожим образом работают как в C#. Я имею в виду предсказуемые и потенциально исправимые ошибки; потому что эта та область, в которой F# с первого взгляда может показаться похож на C#, но очень быстро становится очевидно, насколько они различаются. В частности, это проявляется в использовании значения null как распространенного сигнала об ошибки в C#. Это не редкий паттерн в C#, который выглядит примерно так:
public Foo DoSomething(Bar bar)
{
if (bar.IsInvalid)
{
return null;
}
return new Foo(bar.Value);
}
И затем, вызывающий DoSomething может проверить возвращаемое значение на null и либо обработать, либо передать его дальше. По моему опыту, одна из областей, где это часто возникает — это функция LINQ FirstOrDefault(), которая используется, чтобы избежать исключения в случае пустого IEnumerable<T>, но часто заканчивается просто продвижением дальше null.
Изначально кажется, что F# пытается осуществить это с помощью своего типа Option<'a> — и часто возникает вопрос: не является ли None просто ярлыком для null, за исключением того, что теперь труднее получить значение обернутое в Some? Потому что для этого потребуется pattern matching или проверка .HasValue для опции — и действительно ли это лучше? Это не так, и именно поэтому F# посредством функционального программирования предлагает более чистое решение: разрабатывать основную часть кодовой базы, не беспокоясь о проверке на существующие ошибки, а вместо этого беспокоясь только об оповещении потенциально новых, специфичных для данной функции. Мы можем сделать это, написав большинство наших функций так, как будто входные данные уже были проверены для нас, и затем, с помощью функций map или bind, связать наши безответственные функции вместе. Давайте посмотрим на них в контексте Option:
- map требуется два аргумента: функция 'a -> 'b и Option<'a>, из которых она будет генерировать Option<'b>;
- bind также требует два аргумента: функция 'a -> Option<'b> и Option<'a>, из которых она будет генерировать Option<'a>.
Давайте посмотрим, что они могут для нас сделать:
// string -> Option<string>
let getConfigVariable varName =
Private.configFile
|> Map.tryFind varName
// string -> Option<string[]>
let readFile filename =
if File.Exists(filename)
then Some File.ReadLines(filename)
else None
// string[] -> int
let countLines textRows = Seq.length file
getConfigVariable "storageFile" // 1
|> Option.bind readFile // 2
|> Option.map countLines // 3
Так что тут происходит?
- Мы пытаемся взять переменную из нашей конфигурации. Может быть, она существует, а может и нет, но это имеет значение только для этой единственной функции.
- Затем мы перенаправляем в Option.bind — который неявно обрабатывает логику безопасности для нас: если предыдущий шаг имеет значение Some — используйте его в качестве аргумента этой функции, — в противном случае оставьте его как None и двигайтесь дальше.
- Option.map делает то же самое — если есть значение Some, используйте его с этой функцией, в противном случае просто двигайтесь дальше.
Прозорливый наблюдатель заметит, что на шаге 3 нет непосредственной разницы между bind и map — они оба автоматически обрабатывают одно и то же, верно? Но обратите внимание на разные сигнатуры между readFile и countLines — bind имеет дополнительный шаг, который производит flatten (прим. перев.: разворачивает вложенную структуру, Option.flatten) над параметром Option, который выводит его функция. Рассмотрим альтернативу: если бы мы использовали map, то в конце строки 2 у нас было бы Option<Option<string[]>> — и так в строке 3 нам потребуется Option.map (Option. map countLines)!
Но возникает вопрос, как мне на самом деле получить значение, если оно есть выводом этого Option? И это справедливый вопрос. И ответ — избегать этого как можно дольше. Поскольку, чем позже вы откладываете попытку развернуть Option, тем меньше кода вам нужно написать, который хоть как-то предполагает, что ошибка возможна. И в тот момент, когда вам, наконец, определенно необходимо получить значение, у вас есть два варианта:
- Option.defaultValue принимает 'a и Option<'a> — если Option имеет значение, он возвращает его, в противном случае он возвращает значение 'a, которое вы ему дали.
- Option.defaultWith — то же самое, но вместо значения для генерации значения требуется функция unit -> 'a.
Так уж совпало, что та же самая логика применима к встроенному в F# типу Result<'a,'b>, который также предлагает bind и map (и mapError, если вам это нужно) — но вместо None у вас есть вариант Error, который вы можете использовать для хранения информации о том, что пошло не так — будь то string или пользовательский тип ошибки по вашему выбору.
Использовать C#-библиотеки в F
Одно из восхитительных преимуществ F# — и, вероятно, почему C#-разработчик сначала смотрит на него, а не на что-то вроде Haskell, — это то, что он является частью большой экосистемы .NET и поддерживает взаимодействие со всеми C#-библиотеками, с которыми разработчик уже знаком. Код на C# может (в основном) использоваться в F#, но иногда возникают некоторые затруднения, но обычно с легкими обходными путями:
- При вызове C#-методов компилятор F# рассматривает метод как кортеж с одним аргументом. Из-за этого частичное применение строго невозможно, и пайпинг может быть затруднен из-за перегрузки:
"1" |> Int32.Parse // Подобно Int32.Parse("1")
("1", NumberStyles.Integer) |> Int32.Parse // Подобно Int32.Parse("1", NumberStyles.Integer)
NumberStyles.Integer |> Int32.Parse "1" // Не компилируется, потому что ожидает кортежный аргумент, а не два отдельных аргумента.
- C#-Библиотеки — особенно те, которые включают сериализацию или рефлексию, — часто не приспособлены для понимания встроенных типов F#. Наиболее распространенным случаем здесь являются библиотеки JSON, которые могут затрудняются над сериализацией и/или десериализацией Unions и Records — в таких случаях настоятельно рекомендуется проверить на существование библиотеки расширений, которая предоставляет специфичную функциональность F#. Например, Newtonsoft.Json имеет пакет Newtonsoft.Json.FSharp, System.Text.Json — FSharp.SystemTextJson. С другой стороны, в этих случаях может быть также хорошо проверить нативные библиотеки на F# подобно Thoth или Chiron.
- Благодаря возможности C# создавать null для любого ссылочного типа, и отсутствию (на момент написания) (прим. перев.: fsharp/fslang-suggestions#577) встроенного интеропа для обозначения nullable reference type в C#, полезно попытаться изолировать код C# на внешнем уровне вашей логики и использовать утилиты, такие как Option.ofNullable (для Nullable<T>) или Option.ofObj (для ссылочных типов), чтобы быстро обеспечить безопасность типов для вашего собственного кода.
- Методы в C#, которые ожидают типы делегатов, такие как Action<T> или Func<T>, могут получить лямбда-выражение F# соответствующей сигнатуры, и компилятор будет обрабатывать преобразование. Помните: unit заменяет void в F# — и его () значение — поэтому Action<T> будет ожидать 'T -> unit, например (fun _ -> printfn "I'm a lambda!"); и аналогично, Fun <T> ожидает unit -> 'T, например (fun () -> 123).
- В тех случаях, когда C#-библиотека ожидает, что объекты будут декорированы атрибутами, то для этого используется хитрость в виде <>, которую F# использует внутри квадратных скобок — так что [Serializable] C# превратится в [<Serializable>] F#. Аргументы работают одинаково: [<DllImport('user32.dll', CharSet = CharSet.Auto)>]. И, как и в случае с коллекциями выше, несколько атрибутов разделяются точкой с запятой, а не запятой: например, [<AttributeOne; AttributeTwo>].
===========
Источник:
habr.com
===========
===========
Автор оригинала: Ryan Coy
===========Похожие новости:
- [Программирование, .NET, C#] Шпион под прикрытием: проверяем исходный код ILSpy с помощью PVS-Studio
- [Разработка игр, C#, Unity, Дизайн игр] Жидкий персонаж на Unity 3D
- [Программирование, .NET, C#] A Spy Undercover: PVS-Studio to Check ILSpy Source Code
- [.NET, C++] Разбор протокола World of Tanks
- [C#, VueJS] Оптимизация страницы с использованием RxJS и Expression Tree
- [.NET, Разработка игр, C#] Бэк-офис для игр или «результат борьбы с пенсионной скукой»
- [Java, C#, Учебный процесс в IT, Карьера в IT-индустрии] Как стать разработчиком Java и С#: открываем онлайн-практикум с поддержкой менторов
- [Программирование, .NET, C#] C# программист, испытай себя — найди ошибку
- [Программирование, .NET, C#] C# Programmer, It's Time to Test Yourself and Find Error
- [Разработка под iOS, Разработка мобильных приложений, Разработка под Android, C#, Xamarin] Использование сервисов и обработка их результатов в Xamarin
Теги для поиска: #_.net, #_c#, #_f#, #_.net_c#_f#_primer, #_.net, #_c#, #_f#
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 12:20
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Предисловие Мой переход на F# в качестве излюбленного языка был слегка усеян препятствиями. Примерно через десять лет почти постоянного использования C# у меня пробудилось любопытство, когда я услышал об этом другом #-языке. Моя первая реакция была той, которую с тех пор видел у других C#-разработчиков — отрицание, — C# является хорошим языком, и мне с ним комфортно, так зачем тратить силы на изучение другого? Но любопытство осталось — и, по крайней мере, несколько раз выделил вечер, чтобы прочитать базовый вводный пост и попытаться написать каких-нибудь ката на F#. Это не прижилось, потому что я просто чувствовал себя потерянным и не мог воплотить свой опыт использования C# в ощущение даже отдаленного комфорта с F#. Достаточно легко опустить фигурные скобки, немного замяться, чтобы не забыть let вместо var — но как сделать то, что я хотел? Тогда я этого не осознавал, но, на мой взгляд, наблюдал потенциальный недостаток в том, как F#-разработчики говорят, описывают и представляют свой язык внешнему миру. Существует обширная база материалов обо всех возможностях и функциональности F#: Algebraic Data Types, Exhaustive Matching, Type Inference и т.д. Есть много статей, посвященных тому, как решать широкий спектр задач с помощью F#. Но, как мне кажется, не хватает чего-то вроде следующего: некоторых указаний о том, как взять то, что вам уже удобно в C#, и перевести их на F#. Так что мне интересно, можем ли мы как-то закрыть этот недостаток. При этом от читателя требуется немного — поверхностное знакомство с тремя основными моментами синтаксиса F#:
Остальное должно быть понятно из практики и контекста по мере продвижения. Это не должно быть исчерпывающим, замысловатым руководством, но обладать достаточной информацией, чтобы охватить большинство начальных вопросов и поставить людей на правильный путь. Букварь, если хотите.
Мне необходимо Работать с коллекциями В F# базовые типы коллекций (в основном) как правило очень похожи на C#, но часто имеют (иногда незначительные) различия в поведении для обеспечения иммутабельности. В большинстве случаев функции, которые работают с этими коллекциями, будут возвращать ссылки и не будут изменять содержимое исходной ссылки. Подобрать тип коллекции Что-то похожее на Array<T> Тебе повезло! Массивы в F# такие же как в C#. Однако следует отметить несколько моментов:
Что-то похожее на List<T> По умолчанию в F# тип списка немного отличается от типа List<T> в C#. Вот что вам нужно знать:
Что-то похожее на Dictionary<TKey,TValue> По мотивам списка «выглядит похоже, но не нет» — F# предоставляет стандартный Map<'key,'value> тип, который не является родным для C# Dictionary<TKey,TValue>, но реализует обычную группу интерфейсов .NET, таких как IDictionary<TKey,TValue> и IEnumerable<T> Вот что вам нужно знать:
Подобрать функцию Одно важное различие между F# и C#, когда дело доходит до работы с коллекциями, заключается в том, что в C# вы, как правило, оперируете над экземпляром коллекции, используя метод этого типа через точку; в то время как F# предпочитает предоставлять семейства функций в модулях, которые принимают экземпляры в качестве аргумента. Итак, C#-вариант myDictionary.Add(someKey,someValue) в F# будет Map.add someKey someValue myMap. Просто хочу свой LINQ F# предлагает функции, аналогичные тем, с которыми программисты на C# знакомы по LINQ, но названия часто отличаются, поскольку F# использует систему условных обозначений, которая больше соответствует терминологии, используемой в остальной части мира функционального программирования. Будьте уверены, они в основном ведут себя так, как вы и ожидаете. Дабы не утомлять — LINQ огромен, — я сопоставлю, по моему опыту, наиболее распространенные методы LINQ и их аналоги на F#:
Не уверен, какая функция нужна. У меня есть Коллекция, а хочу Отдельное значение или элемент
Равное количество элементов
Возможно меньшее количество элементов
Возможно большее количество элементов
Чтобы изменить форму коллекции
Чтобы пройти по коллекции без ее изменения
Отдельное значение и хочу Чтобы было частью коллекции
Несколько коллекций и хотите Скомбинировать их
Работать асинхронно Модель асинхронности в F# похожа на модель в C#, но имеет несколько важных отличий, которые иногда застают врасплох C#-разработчиков:
У F# есть еще одно очень заметное отличие от C# в отношении асинхронного кода: C# "включает" ключевое слово await внутри метода, применяя ключевое слово async к сигнатуре этого метода; F# использует языковую функцию, называемую computation expression, в результате чего асинхронность становится частью тела функции. Это также имеет некоторые последствия на то, как вы пишете код внутри этого тела функции: let timesTwo i = i * 2 // У нас есть определение нашей базовой функции
// И теперь мы можем сделать это асинхронным let timesTwoAsync i = async { // Обратите внимание, что при работе с computation expression мы начинаем с нашего ключевого слова, а затем с самой функции внутри фигурных скобок return i * 2 // Мы также используем ключевое слово `return` для завершения выражения } let timesFour i = async { let! doubleOnce = timesTwoAsync i // Обратите внимание на `!` в нашем `let!` — это похоже на `await` в C# — правосторонняя функция должна возвращать `Async<'a>` // После того, как мы связали результат асинхронной функции с помощью `let!` — мы можем использовать его потом как обычно let doubleTwice = timesTwo doubleOnce // В случае неасинхронных функций мы можем написать наш код как обычно return doubleTwice }
Сообщать об ошибке или контролировать выполнение программы Во-первых, определение: когда мы говорим об ошибках и выполнении программы, я не имею в виду исключения — в F# они есть и вполне схожим образом работают как в C#. Я имею в виду предсказуемые и потенциально исправимые ошибки; потому что эта та область, в которой F# с первого взгляда может показаться похож на C#, но очень быстро становится очевидно, насколько они различаются. В частности, это проявляется в использовании значения null как распространенного сигнала об ошибки в C#. Это не редкий паттерн в C#, который выглядит примерно так: public Foo DoSomething(Bar bar)
{ if (bar.IsInvalid) { return null; } return new Foo(bar.Value); } И затем, вызывающий DoSomething может проверить возвращаемое значение на null и либо обработать, либо передать его дальше. По моему опыту, одна из областей, где это часто возникает — это функция LINQ FirstOrDefault(), которая используется, чтобы избежать исключения в случае пустого IEnumerable<T>, но часто заканчивается просто продвижением дальше null. Изначально кажется, что F# пытается осуществить это с помощью своего типа Option<'a> — и часто возникает вопрос: не является ли None просто ярлыком для null, за исключением того, что теперь труднее получить значение обернутое в Some? Потому что для этого потребуется pattern matching или проверка .HasValue для опции — и действительно ли это лучше? Это не так, и именно поэтому F# посредством функционального программирования предлагает более чистое решение: разрабатывать основную часть кодовой базы, не беспокоясь о проверке на существующие ошибки, а вместо этого беспокоясь только об оповещении потенциально новых, специфичных для данной функции. Мы можем сделать это, написав большинство наших функций так, как будто входные данные уже были проверены для нас, и затем, с помощью функций map или bind, связать наши безответственные функции вместе. Давайте посмотрим на них в контексте Option:
Давайте посмотрим, что они могут для нас сделать: // string -> Option<string>
let getConfigVariable varName = Private.configFile |> Map.tryFind varName // string -> Option<string[]> let readFile filename = if File.Exists(filename) then Some File.ReadLines(filename) else None // string[] -> int let countLines textRows = Seq.length file getConfigVariable "storageFile" // 1 |> Option.bind readFile // 2 |> Option.map countLines // 3 Так что тут происходит?
Прозорливый наблюдатель заметит, что на шаге 3 нет непосредственной разницы между bind и map — они оба автоматически обрабатывают одно и то же, верно? Но обратите внимание на разные сигнатуры между readFile и countLines — bind имеет дополнительный шаг, который производит flatten (прим. перев.: разворачивает вложенную структуру, Option.flatten) над параметром Option, который выводит его функция. Рассмотрим альтернативу: если бы мы использовали map, то в конце строки 2 у нас было бы Option<Option<string[]>> — и так в строке 3 нам потребуется Option.map (Option. map countLines)! Но возникает вопрос, как мне на самом деле получить значение, если оно есть выводом этого Option? И это справедливый вопрос. И ответ — избегать этого как можно дольше. Поскольку, чем позже вы откладываете попытку развернуть Option, тем меньше кода вам нужно написать, который хоть как-то предполагает, что ошибка возможна. И в тот момент, когда вам, наконец, определенно необходимо получить значение, у вас есть два варианта:
Так уж совпало, что та же самая логика применима к встроенному в F# типу Result<'a,'b>, который также предлагает bind и map (и mapError, если вам это нужно) — но вместо None у вас есть вариант Error, который вы можете использовать для хранения информации о том, что пошло не так — будь то string или пользовательский тип ошибки по вашему выбору. Использовать C#-библиотеки в F Одно из восхитительных преимуществ F# — и, вероятно, почему C#-разработчик сначала смотрит на него, а не на что-то вроде Haskell, — это то, что он является частью большой экосистемы .NET и поддерживает взаимодействие со всеми C#-библиотеками, с которыми разработчик уже знаком. Код на C# может (в основном) использоваться в F#, но иногда возникают некоторые затруднения, но обычно с легкими обходными путями:
=========== Источник: habr.com =========== =========== Автор оригинала: Ryan Coy ===========Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 12:20
Часовой пояс: UTC + 5