Элегантные фрагменты F #

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

Мне интересно найти некоторые действительно замечательные "вау-фактор" фрагменты кода F #, которые демонстрируют элегантность языка, особенно по сравнению с С#. Например, мне очень нравится: -

#light
let ListProduct l = List.fold_left (*) 1 l

Который вводит список int и умножает каждый элемент в списке, т.е. получает произведение списка (например, список из 1,2,3 будет вычисляться как 1 * 2 * 3 = 6). Ближайшим С# equivilent, использующим LINQ и функциональными концепциями, является следующее: -

using System;
using System.Collections.Generic;
using System.Linq;

...

public static class ListHelper {
  public static int ListProduct(List<int> l) {
    return l.Aggregate(1, (i, j) => i * j);
  }
}    

До LINQ, который был бы: -

using System;
using System.Collections.Generic;

...

public static class ListHelper {
  public static int ListProduct(List<int> l) {
    int ret = 1;
    foreach (int i in l) ret *= i;
    return ret;
  }
}

Я, конечно, не пытаюсь критиковать С# здесь, я думаю, что это замечательный язык, просто приятно видеть, как F # сравнивает и видеть, как он может сделать что-то более элегантно - есть ли у кого-нибудь что-нибудь действительно приятное?

Ответы

Ответ 1

Мой любимый рекурсивно перечисляет все файлы под папкой в ​​выражении с четырьмя строками:

open System.IO

let rec filesUnderFolder basePath =
    seq {
        for file in Directory.GetFiles(basePath) do
            yield file
        for subDir in Directory.GetDirectories(basePath) do
            yield! filesUnderFolder subDir
        }

Ответ 2

Несмотря на преобладающую негативность, связанную с примерами Фибоначчи. Мне они нравятся. Оказывается, "практическое" программирование часто является составной частью многих "непрактичных" взглядов. Пример Фибоначчи показывает нам, что может выглядеть дважды повторяющееся объявление. Haskell имеет особенно элегантное решение, которое может нас чему-то научить F #:

fibonacci = 0 : 1 : zipWith (+) fibonacci (tail fibonacci)

Прежде всего, изучите этот фрагмент и получите его. Фиб сам по себе лениво определяет себя. Мы можем получить некоторые ленивые из F #, используя последовательности. Используя FSI, вот как:


> let fibonacci = Seq.unfold (fun (x, y) -> Some(x, (y, x + y))) (0I,1I);;

val fibonacci: seq


> #time;;

- > Сроки сейчас


> fibonacci |> Seq.nth 10000;;

Реал: 00: 00: 00.027, CPU: 00: 00: 00.031, GC gen0: 0, gen1: 0, gen2: 0

val it: System.Numerics.BigInteger =

3364476487643178326662161200510754331030214846068006390656476997468008144216666236815559551363373402558206533268083615937373479048386526826304089246305643188735454436955982749160660209988418393386465273130008883026923567361313511757929743785441375213052050434770160226475831890652789085515436615958298727968298751063120057542878345321551510387081829896979161312785626503319548714021428753269818796204693609787990035096230229102636813149319527563022783762844154036058440257211433496118002309120828704608892396232883546150577658327125254609359112820392528539343462090424524892940390170623388899108584106518317336043747073790855263176432573399371287193758774689747992630583706574283016163740896917842637862421283525811282051637029808933209990570792006436742620238978311147005407499845925036063356093388383192338678305613643535189213327973290813373264265263398976392272340788292817795358057099369104917547080893184105614632233821746563732124822638309210329770164805472624384237486241145309381220656491 4032751086643394517512161526545361333111314042436854805106765843493523836959653428071768775328348234345557366719731392746273629108210679280784718035329131176778924659089938635459327894523777674406192240337638674004021330343297496902028328145933418826817683893072003634795623117103101291953169794607632737589253530772552375943788434504067715555779056450443016640119462580972216729758615026968443146952034614932291105970676243268515992834709891284706740862008587135016260312071903172086094081298321581077282076353186624611278245537208532365305775956430072517744315051539600905168603220349163222640885248852433158051534849622434848299380905070483482449327453732624567755879089187190803662058009594743150052402532709746995318770724376825907419939632265984147498193609285223945039707165443156421328157688908058783183404917434556270520223564846495196112460268313970975069382648706613264507665074611512677522748621598642530711298441182622661057163515069260029861704945425047491378115154139941550671256271197 133252763631939606902895650288268608362241082050562430701794976171121233066073310059947366875

{IsEven = false; IsOne = false; IsPowerOfTwo = false; IsZero = false; Знак = 1;}


Это хорошо. Даже для моей скромной рабочей станции. Это практично, потому что мы определили что-то элегантно в одной строке, которая превосходит наивные многострочные реализации. Мы использовали некоторые практические концепции, пока мы это делаем. Currying (как показано на конвейере до Seq.nth), ленивая оценка (как показано Seq.unfold) и lambdas (анонимная функция, данная Seq.unfold). Мы также воспользовались "формой" вычисления последовательности Фибоначчи не совсем так же, как версия Haskell, но достаточно похожи.

Ответ 5

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

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

let rec ListProduct xs =
    match xs with
    //If xs is an empty list, we have a match with an empty list.  Return 1
    | []    -> 1
    //Otherwise match with an item + the rest of the list.  
    //Return the first item * the rest of the list. 
    | y::ys -> y * ListProduct ys

Этот код, очевидно, не призван дать какой-либо вау-фактор, как вы упомянули. Но вы можете увидеть некоторые действительно крутые варианты использования F # на этом сайте. Проверьте решение sudoku в F #. Сравните этот код с реализацией С# решения Sudoku. Сайт также демонстрирует, как легко кодировать графический интерфейс с помощью F #.

Этот сайт покажет вам, как интегрировать F # с ASP.Net

Ответ 6

Недавно я узнал, как использовать sum для коллекций типов, реализуя оператор (+) и свойство Zero:

// create record with (+) operator and Zero member
type myRec = {
    v1 : float
    v2 : float
} with 
    static member (+) (l, r) = { v1=l.v1+r.v1; v2=l.v2+r.v2 }
    static member Zero       = { v1=0.0;       v2=0.0}

Это серьезно экономит на типизации и повышает читаемость в моем коде, где я часто использую записи. fold всегда делал мою голову крутой с более сложными типами.

Добавленная выгода заключается в том, что если вам нужно добавить поле, вам нужно изменить только определение записи, везде, где оно используется, просто следует.

// list to test it with
let l = [{ v1=1.0; v2=2.0}; { v1=2.0; v2=3.0}; { v1=1.0; v2=2.0}]


// simple test **************************
l |> List.sum
// equivalent old ways
l |> List.fold (fun t i -> {v1=t.v1+i.v1; v2=t.v1+i.v1}) {v1=0.0; v2=0.0}


// groupBy test ***************************
// be aware v1 now holds bogus data though
l
|> Seq.groupBy (fun r -> r.v1)
|> Seq.map     (fun (v, s) -> (v, s |> Seq.sum))
// equivalent old ways
l
|> Seq.groupBy (fun r -> r.v1)
|> Seq.map     (fun (v, s) -> (v, s |> Seq.fold (fun t i -> {v1=t.v1+i.v1; v2=t.v1+i.v1}) {v1=0.0; v2=0.0}))

Это означает, что для более удобочитаемого кода нужно использовать каждый раз, чтобы сбрасывать его и держать его ближе к тому, как я думаю.

Ответ 7

Я думаю, что элегантная вещь о сворачивании - это то, что вы можете "прокормить" ее:

//takes a max/min tuple + new value, returns expanded max/min tuple
let limits (mn, mx) a = (min mn a, max mx a)

//Initialise a test list
let lst = [1; 3; 5; -1; -9; 0]

//feeds each value in lst to limits - for first call, uses (0, 0) for (mn,mx)
List.fold_left limits (0, 0) lst

//(two extra functions for following example)
let cube x = x * x * x    //does this need explaining?
let range (a, b) = b - a  //returns range of a tuple

//particularly sexy with pipe forward operator
lst |> List.map cube
    |> List.fold_left limits (0, 0)
    |> range