F#

Личный сайт Go-разработчика из Казани

F# is a general purpose functional/OO programming language. It’s free and open source, and runs on Linux, Mac, Windows and more.

It has a powerful type system that traps many errors at compile time, but it uses type inference so that it reads more like a dynamic language.

The syntax of F# is different from C-style languages:

  • Curly braces are not used to delimit blocks of code. Instead, indentation is used (like Python).
  • Whitespace is used to separate parameters rather than commas.

If you want to try out the code below, you can go to https://try.fsharp.org and paste it into an interactive REPL.

1// single line comments use a double slash 2(* multi line comments use (* . . . *) pair 3 4-end of multi line comment- *) 5 6// ================================================ 7// Basic Syntax 8// ================================================ 9 10// ------ "Variables" (but not really) ------ 11// The "let" keyword defines an (immutable) value 12let myInt = 5 13let myFloat = 3.14 14let myString = "hello" // note that no types needed 15 16// Mutable variables 17let mutable a=3 18a <- 4 // a is now 4. 19 20// Somewhat mutable variables 21// Reference cells are storage locations that enable you to create mutable values with reference semantics. 22// See https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/reference-cells 23let xRef = ref 10 24printfn "%d" xRef.Value // 10 25xRef.Value <- 11 26printfn "%d" xRef.Value // 11 27 28let a=[ref 0; ref 1] // somewhat mutable list 29a[0].Value <- 2 30 31// ------ Lists ------ 32let twoToFive = [2; 3; 4; 5] // Square brackets create a list with 33 // semicolon delimiters. 34let oneToFive = 1 :: twoToFive // :: creates list with new 1st element 35// The result is [1; 2; 3; 4; 5] 36let zeroToFive = [0; 1] @ twoToFive // @ concats two lists 37 38// IMPORTANT: commas are never used as delimiters, only semicolons! 39 40// ------ Functions ------ 41// The "let" keyword also defines a named function. 42let square x = x * x // Note that no parens are used. 43square 3 // Now run the function. Again, no parens. 44 45let add x y = x + y // don't use add (x,y)! It means something 46 // completely different. 47add 2 3 // Now run the function. 48 49// to define a multiline function, just use indents. No semicolons needed. 50let evens list = 51 let isEven x = x % 2 = 0 // Define "isEven" as a sub function. Note 52 // that equality operator is single char "=". 53 List.filter isEven list // List.filter is a library function 54 // with two parameters: a boolean function 55 // and a list to work on 56 57evens oneToFive // Now run the function 58 59// You can use parens to clarify precedence. In this example, 60// do "map" first, with two args, then do "sum" on the result. 61// Without the parens, "List.map" would be passed as an arg to List.sum 62let sumOfSquaresTo100 = 63 List.sum ( List.map square [1..100] ) 64 65// You can pipe the output of one operation to the next using "|>" 66// Piping data around is very common in F#, similar to UNIX pipes. 67 68// Here is the same sumOfSquares function written using pipes 69let sumOfSquaresTo100piped = 70 [1..100] |> List.map square |> List.sum // "square" was defined earlier 71 72// you can define lambdas (anonymous functions) using the "fun" keyword 73let sumOfSquaresTo100withFun = 74 [1..100] |> List.map (fun x -> x * x) |> List.sum 75 76// In F# there is no "return" keyword. A function always 77// returns the value of the last expression used. 78 79// ------ Pattern Matching ------ 80// Match..with.. is a supercharged case/switch statement. 81let simplePatternMatch = 82 let x = "a" 83 match x with 84 | "a" -> printfn "x is a" 85 | "b" -> printfn "x is b" 86 | _ -> printfn "x is something else" // underscore matches anything 87 88// F# doesn't allow nulls by default -- you must use an Option type 89// and then pattern match. 90// Some(..) and None are roughly analogous to Nullable wrappers 91let validValue = Some(99) 92let invalidValue = None 93 94// In this example, match..with matches the "Some" and the "None", 95// and also unpacks the value in the "Some" at the same time. 96let optionPatternMatch input = 97 match input with 98 | Some i -> printfn "input is an int=%d" i 99 | None -> printfn "input is missing" 100 101optionPatternMatch validValue 102optionPatternMatch invalidValue 103 104// ------ Printing ------ 105// The printf/printfn functions are similar to the 106// Console.Write/WriteLine functions in C#. 107printfn "Printing an int %i, a float %f, a bool %b" 1 2.0 true 108printfn "A string %s, and something generic %A" "hello" [1; 2; 3; 4] 109 110// There are also sprintf/sprintfn functions for formatting data 111// into a string, similar to String.Format in C#. 112 113// ================================================ 114// More on functions 115// ================================================ 116 117// F# is a true functional language -- functions are first 118// class entities and can be combined easily to make powerful 119// constructs 120 121// Modules are used to group functions together 122// Indentation is needed for each nested module. 123module FunctionExamples = 124 125 // define a simple adding function 126 let add x y = x + y 127 128 // basic usage of a function 129 let a = add 1 2 130 printfn "1 + 2 = %i" a 131 132 // partial application to "bake in" parameters 133 let add42 = add 42 134 let b = add42 1 135 printfn "42 + 1 = %i" b 136 137 // composition to combine functions 138 let add1 = add 1 139 let add2 = add 2 140 let add3 = add1 >> add2 141 let c = add3 7 142 printfn "3 + 7 = %i" c 143 144 // higher order functions 145 [1..10] |> List.map add3 |> printfn "new list is %A" 146 147 // lists of functions, and more 148 let add6 = [add1; add2; add3] |> List.reduce (>>) 149 let d = add6 7 150 printfn "1 + 2 + 3 + 7 = %i" d 151 152// ================================================ 153// Lists and collection 154// ================================================ 155 156// There are three types of ordered collection: 157// * Lists are most basic immutable collection. 158// * Arrays are mutable and more efficient when needed. 159// * Sequences are lazy and infinite (e.g. an enumerator). 160// 161// Other collections include immutable maps and sets 162// plus all the standard .NET collections 163 164module ListExamples = 165 166 // lists use square brackets 167 let list1 = ["a"; "b"] 168 let list2 = "c" :: list1 // :: is prepending 169 let list3 = list1 @ list2 // @ is concat 170 171 // list comprehensions (aka generators) 172 let squares = [for i in 1..10 do yield i * i] 173 174 // A prime number generator 175 // - this is using a short notation for the pattern matching syntax 176 // - (p::xs) is 'first :: tail' of the list, could also be written as p :: xs 177 // this means this matches 'p' (the first item in the list), and xs is the rest of the list 178 // this is called the 'cons pattern' 179 // - uses 'rec' keyword, which is necessary when using recursion 180 let rec sieve = function 181 | (p::xs) -> p :: sieve [ for x in xs do if x % p > 0 then yield x ] 182 | [] -> [] 183 let primes = sieve [2..50] 184 printfn "%A" primes 185 186 // pattern matching for lists 187 let listMatcher aList = 188 match aList with 189 | [] -> printfn "the list is empty" 190 | [first] -> printfn "the list has one element %A " first 191 | [first; second] -> printfn "list is %A and %A" first second 192 | first :: _ -> printfn "the list has more than two elements, first element %A" first 193 194 listMatcher [1; 2; 3; 4] 195 listMatcher [1; 2] 196 listMatcher [1] 197 listMatcher [] 198 199 // recursion using lists 200 let rec sum aList = 201 match aList with 202 | [] -> 0 203 | x::xs -> x + sum xs 204 sum [1..10] 205 206 // ----------------------------------------- 207 // Standard library functions 208 // ----------------------------------------- 209 210 // map 211 let add3 x = x + 3 212 [1..10] |> List.map add3 213 214 // filter 215 let even x = x % 2 = 0 216 [1..10] |> List.filter even 217 218 // many more -- see documentation 219 220module ArrayExamples = 221 222 // arrays use square brackets with bar 223 let array1 = [| "a"; "b" |] 224 let first = array1.[0] // indexed access using dot 225 226 // pattern matching for arrays is same as for lists 227 let arrayMatcher aList = 228 match aList with 229 | [| |] -> printfn "the array is empty" 230 | [| first |] -> printfn "the array has one element %A " first 231 | [| first; second |] -> printfn "array is %A and %A" first second 232 | _ -> printfn "the array has more than two elements" 233 234 arrayMatcher [| 1; 2; 3; 4 |] 235 236 // Standard library functions just as for List 237 238 [| 1..10 |] 239 |> Array.map (fun i -> i + 3) 240 |> Array.filter (fun i -> i % 2 = 0) 241 |> Array.iter (printfn "value is %i. ") 242 243 244module SequenceExamples = 245 246 // sequences use curly braces 247 let seq1 = seq { yield "a"; yield "b" } 248 249 // sequences can use yield and 250 // can contain subsequences 251 let strange = seq { 252 // "yield" adds one element 253 yield 1; yield 2; 254 255 // "yield!" adds a whole subsequence 256 yield! [5..10] 257 yield! seq { 258 for i in 1..10 do 259 if i % 2 = 0 then yield i }} 260 // test 261 strange |> Seq.toList 262 263 264 // Sequences can be created using "unfold" 265 // Here's the fibonacci series 266 let fib = Seq.unfold (fun (fst,snd) -> 267 Some(fst + snd, (snd, fst + snd))) (0,1) 268 269 // test 270 let fib10 = fib |> Seq.take 10 |> Seq.toList 271 printf "first 10 fibs are %A" fib10 272 273 274// ================================================ 275// Data Types 276// ================================================ 277 278module DataTypeExamples = 279 280 // All data is immutable by default 281 282 // Tuples are quick 'n easy anonymous types 283 // -- Use a comma to create a tuple 284 let twoTuple = 1, 2 285 let threeTuple = "a", 2, true 286 287 // Pattern match to unpack 288 let x, y = twoTuple // sets x = 1, y = 2 289 290 // ------------------------------------ 291 // Record types have named fields 292 // ------------------------------------ 293 294 // Use "type" with curly braces to define a record type 295 type Person = {First:string; Last:string} 296 297 // Use "let" with curly braces to create a record 298 let person1 = {First="John"; Last="Doe"} 299 300 // Pattern match to unpack 301 let {First = first} = person1 // sets first="John" 302 303 // ------------------------------------ 304 // Union types (aka variants) have a set of choices 305 // Only one case can be valid at a time. 306 // ------------------------------------ 307 308 // Use "type" with bar/pipe to define a union type 309 type Temp = 310 | DegreesC of float 311 | DegreesF of float 312 313 // Use one of the cases to create one 314 let temp1 = DegreesF 98.6 315 let temp2 = DegreesC 37.0 316 317 // Pattern match on all cases to unpack 318 let printTemp = function 319 | DegreesC t -> printfn "%f degC" t 320 | DegreesF t -> printfn "%f degF" t 321 322 printTemp temp1 323 printTemp temp2 324 325 // ------------------------------------ 326 // Recursive types 327 // ------------------------------------ 328 329 // Types can be combined recursively in complex ways 330 // without having to create subclasses 331 type Employee = 332 | Worker of Person 333 | Manager of Employee list 334 335 let jdoe = {First="John"; Last="Doe"} 336 let worker = Worker jdoe 337 338 // ------------------------------------ 339 // Modeling with types 340 // ------------------------------------ 341 342 // Union types are great for modeling state without using flags 343 type EmailAddress = 344 | ValidEmailAddress of string 345 | InvalidEmailAddress of string 346 347 let trySendEmail email = 348 match email with // use pattern matching 349 | ValidEmailAddress address -> () // send 350 | InvalidEmailAddress address -> () // don't send 351 352 // The combination of union types and record types together 353 // provide a great foundation for domain driven design. 354 // You can create hundreds of little types that accurately 355 // reflect the domain. 356 357 type CartItem = { ProductCode: string; Qty: int } 358 type Payment = Payment of float 359 type ActiveCartData = { UnpaidItems: CartItem list } 360 type PaidCartData = { PaidItems: CartItem list; Payment: Payment} 361 362 type ShoppingCart = 363 | EmptyCart // no data 364 | ActiveCart of ActiveCartData 365 | PaidCart of PaidCartData 366 367 // ------------------------------------ 368 // Built in behavior for types 369 // ------------------------------------ 370 371 // Core types have useful "out-of-the-box" behavior, no coding needed. 372 // * Immutability 373 // * Pretty printing when debugging 374 // * Equality and comparison 375 // * Serialization 376 377 // Pretty printing using %A 378 printfn "twoTuple=%A,\nPerson=%A,\nTemp=%A,\nEmployee=%A" 379 twoTuple person1 temp1 worker 380 381 // Equality and comparison built in. 382 // Here's an example with cards. 383 type Suit = Club | Diamond | Spade | Heart 384 type Rank = Two | Three | Four | Five | Six | Seven | Eight 385 | Nine | Ten | Jack | Queen | King | Ace 386 387 let hand = [ Club, Ace; Heart, Three; Heart, Ace; 388 Spade, Jack; Diamond, Two; Diamond, Ace ] 389 390 // sorting 391 List.sort hand |> printfn "sorted hand is (low to high) %A" 392 List.max hand |> printfn "high card is %A" 393 List.min hand |> printfn "low card is %A" 394 395 396// ================================================ 397// Active patterns 398// ================================================ 399 400module ActivePatternExamples = 401 402 // F# has a special type of pattern matching called "active patterns" 403 // where the pattern can be parsed or detected dynamically. 404 405 // "banana clips" are the syntax for active patterns 406 407 // You can use "elif" instead of "else if" in conditional expressions. 408 // They are equivalent in F# 409 410 // for example, define an "active" pattern to match character types... 411 let (|Digit|Letter|Whitespace|Other|) ch = 412 if System.Char.IsDigit(ch) then Digit 413 elif System.Char.IsLetter(ch) then Letter 414 elif System.Char.IsWhiteSpace(ch) then Whitespace 415 else Other 416 417 // ... and then use it to make parsing logic much clearer 418 let printChar ch = 419 match ch with 420 | Digit -> printfn "%c is a Digit" ch 421 | Letter -> printfn "%c is a Letter" ch 422 | Whitespace -> printfn "%c is a Whitespace" ch 423 | _ -> printfn "%c is something else" ch 424 425 // print a list 426 ['a'; 'b'; '1'; ' '; '-'; 'c'] |> List.iter printChar 427 428 // ----------------------------------- 429 // FizzBuzz using active patterns 430 // ----------------------------------- 431 432 // You can create partial matching patterns as well 433 // Just use underscore in the definition, and return Some if matched. 434 let (|MultOf3|_|) i = if i % 3 = 0 then Some MultOf3 else None 435 let (|MultOf5|_|) i = if i % 5 = 0 then Some MultOf5 else None 436 437 // the main function 438 let fizzBuzz i = 439 match i with 440 | MultOf3 & MultOf5 -> printf "FizzBuzz, " 441 | MultOf3 -> printf "Fizz, " 442 | MultOf5 -> printf "Buzz, " 443 | _ -> printf "%i, " i 444 445 // test 446 [1..20] |> List.iter fizzBuzz 447 448// ================================================ 449// Conciseness 450// ================================================ 451 452module AlgorithmExamples = 453 454 // F# has a high signal/noise ratio, so code reads 455 // almost like the actual algorithm 456 457 // ------ Example: define sumOfSquares function ------ 458 let sumOfSquares n = 459 [1..n] // 1) take all the numbers from 1 to n 460 |> List.map square // 2) square each one 461 |> List.sum // 3) sum the results 462 463 // test 464 sumOfSquares 100 |> printfn "Sum of squares = %A" 465 466 // ------ Example: define a sort function ------ 467 let rec sort list = 468 match list with 469 // If the list is empty 470 | [] -> 471 [] // return an empty list 472 // If the list is not empty 473 | firstElem::otherElements -> // take the first element 474 let smallerElements = // extract the smaller elements 475 otherElements // from the remaining ones 476 |> List.filter (fun e -> e < firstElem) 477 |> sort // and sort them 478 let largerElements = // extract the larger ones 479 otherElements // from the remaining ones 480 |> List.filter (fun e -> e >= firstElem) 481 |> sort // and sort them 482 // Combine the 3 parts into a new list and return it 483 List.concat [smallerElements; [firstElem]; largerElements] 484 485 // test 486 sort [1; 5; 23; 18; 9; 1; 3] |> printfn "Sorted = %A" 487 488// ================================================ 489// Asynchronous Code 490// ================================================ 491 492module AsyncExample = 493 494 // F# has built-in features to help with async code 495 // without encountering the "pyramid of doom" 496 // 497 // The following example downloads a set of web pages in parallel. 498 499 open System.Net 500 open System 501 open System.IO 502 open Microsoft.FSharp.Control.CommonExtensions 503 504 // Fetch the contents of a URL asynchronously 505 let fetchUrlAsync url = 506 async { // "async" keyword and curly braces 507 // creates an "async" object 508 let req = WebRequest.Create(Uri(url)) 509 use! resp = req.AsyncGetResponse() 510 // use! is async assignment 511 use stream = resp.GetResponseStream() 512 // "use" triggers automatic close() 513 // on resource at end of scope 514 use reader = new IO.StreamReader(stream) 515 let html = reader.ReadToEnd() 516 printfn "finished downloading %s" url 517 } 518 519 // a list of sites to fetch 520 let sites = ["http://www.bing.com"; 521 "http://www.google.com"; 522 "http://www.microsoft.com"; 523 "http://www.amazon.com"; 524 "http://www.yahoo.com"] 525 526 // do it 527 sites 528 |> List.map fetchUrlAsync // make a list of async tasks 529 |> Async.Parallel // set up the tasks to run in parallel 530 |> Async.RunSynchronously // start them off 531 532// ================================================ 533// .NET compatibility 534// ================================================ 535 536module NetCompatibilityExamples = 537 538 // F# can do almost everything C# can do, and it integrates 539 // seamlessly with .NET or Mono libraries. 540 541 // ------- work with existing library functions ------- 542 543 let (i1success, i1) = System.Int32.TryParse("123"); 544 if i1success then printfn "parsed as %i" i1 else printfn "parse failed" 545 546 // ------- Implement interfaces on the fly! ------- 547 548 // create a new object that implements IDisposable 549 let makeResource name = 550 { new System.IDisposable 551 with member this.Dispose() = printfn "%s disposed" name } 552 553 let useAndDisposeResources = 554 use r1 = makeResource "first resource" 555 printfn "using first resource" 556 for i in [1..3] do 557 let resourceName = sprintf "\tinner resource %d" i 558 use temp = makeResource resourceName 559 printfn "\tdo something with %s" resourceName 560 use r2 = makeResource "second resource" 561 printfn "using second resource" 562 printfn "done." 563 564 // ------- Object oriented code ------- 565 566 // F# is also a fully fledged OO language. 567 // It supports classes, inheritance, virtual methods, etc. 568 569 // interface with generic type 570 type IEnumerator<'a> = 571 abstract member Current : 'a 572 abstract MoveNext : unit -> bool 573 574 // abstract base class with virtual methods 575 [<AbstractClass>] 576 type Shape() = 577 // readonly properties 578 abstract member Width : int with get 579 abstract member Height : int with get 580 // non-virtual method 581 member this.BoundingArea = this.Height * this.Width 582 // virtual method with base implementation 583 abstract member Print : unit -> unit 584 default this.Print () = printfn "I'm a shape" 585 586 // concrete class that inherits from base class and overrides 587 type Rectangle(x:int, y:int) = 588 inherit Shape() 589 override this.Width = x 590 override this.Height = y 591 override this.Print () = printfn "I'm a Rectangle" 592 593 // test 594 let r = Rectangle(2, 3) 595 printfn "The width is %i" r.Width 596 printfn "The area is %i" r.BoundingArea 597 r.Print() 598 599 // ------- extension methods ------- 600 601 // Just as in C#, F# can extend existing classes with extension methods. 602 type System.String with 603 member this.StartsWithA = this.StartsWith "A" 604 605 // test 606 let s = "Alice" 607 printfn "'%s' starts with an 'A' = %A" s s.StartsWithA 608 609 // ------- events ------- 610 611 type MyButton() = 612 let clickEvent = new Event<_>() 613 614 [<CLIEvent>] 615 member this.OnClick = clickEvent.Publish 616 617 member this.TestEvent(arg) = 618 clickEvent.Trigger(this, arg) 619 620 // test 621 let myButton = new MyButton() 622 myButton.OnClick.Add(fun (sender, arg) -> 623 printfn "Click event with arg=%O" arg) 624 625 myButton.TestEvent("Hello World!")

More Information

For more demonstrations of F#, go to my why use F# series.

Read more about F# at fsharp.org and dotnet’s F# page.