This post will not be about how to "translate" code from C # to F #: different paradigms make each of these languages the best for their range of tasks. However, you will be able to appreciate all the advantages of functional programming more quickly if you do not think about translating code from one paradigm to another. It is time for curious, inquisitive and ready to learn completely new things. Let's start!
All materials from the translation series of the Russian-speaking community of F # -developers can be found by the tag #fsharplangru .
Earlier, in the post “ Why you should use F # ”, we told why F # is worth a try right now. Now we will examine the basics necessary for its successful application. The post is intended for people familiar with C #, Java or other object-oriented languages. If you are already writing in F #, these concepts should be familiar to you.
Before we start exploring the concepts of functional programming, let's look at a small example and determine how F # differs from C #. This is a basic example with two functions and displaying the result on the screen:
let square x = x * x let sumOfSquares n = [1..n] // 1 n |> List.map square // |> List.sum // ! printfn " 5 %d" (sumOfSquares 5)
Please note that there are no explicit types, no semicolons or curly braces. The brackets are used in one place: to call the function sumOfSquares
with the number 5
as an input value and then output the result to the screen. The pipeline operator |>
(pipeline operator) is used in the same way as pipelines (pipes, pipes) in Unix. square
is a function that is directly passed to the List.map
function as a parameter (functions in F # are treated as values, first-class functions).
Although there are still a lot of differences, you should first understand the fundamental things, since they are the key to understanding F #.
The following table shows the correspondences between some key concepts of C # and F #. This is a deliberately short and incomplete description, but it is easier to remember it at the beginning of the F # study.
C # and Object Oriented Programming | F # and Functional Programming |
---|---|
Variables | Immutable values |
Instructions | Expressions |
Objects with methods | Types and functions |
Fast cheat sheet in some terms:
Variables are values that can change . This follows from their name!
Immutable values are values that cannot be changed after assignment.
Instructions are commands executed after the program is started.
Expressions are code fragments that can be calculated and retrieved.
It is worth noting that everything specified in the C # column is also possible in F # (and is quite easy to implement). In the F # column there are also things that can be done in C #, although much more complicated. It should be mentioned that the elements in the left column are not “bad” in F #, and vice versa. Objects with methods are great for use in F # and are often the best solution depending on your situation.
One of the most unusual concepts in functional programming is immutability (immobility, immutability). He is often not given enough attention in the community of functional programming lovers. But if you have never used a language in which values are immutable by default, this is often the first and most significant obstacle to further study. Immutability is a fundamental concept in almost all functional languages.
let x = 1
In the previous expression, the value 1
is associated with the name x
. Throughout its existence, the name x
now refers to the value 1
and cannot be changed. For example, the following code cannot reassign the value of x
:
let x = 1 x = x + 1 // !
Instead, the second line is a comparison, determining whether x
equal to x + 1
. Although there is a way to change (mutate, mutate) x
by using the <-
operator and the mutable
modifier (see Mutable Variables for details), you will quickly realize that it is easier to think about solving problems without reassigning values. If you do not consider F # as another imperative programming language, you can use its strengths.
Immutability transforms your usual approaches to problem solving in a significant way. For example, for
loops and other basic imperative programming operations are not often used in F #.
Consider a more specific example: you want to square the numbers from the input list. Here's how to do this in F #:
// , let square x = x * x let getSquares items = items |> List.map square let lst = [ 1; 2; 3; 4; 5 ] // F# printfn " %A %A" lst (getSquares lst)
Note that in this example there is no for
loop. At the conceptual level, this is very different from the imperative code. We do not square each item in the list. We apply the square
function to the input list and get the values squared. This is a very subtle distinction, but in practice it can lead to significantly different code. First of all, the getSquares
function actually creates a completely new list.
Immutability is a much broader concept than just a different way to manage data in lists. The concept of reference transparency ( Referential Transparency ) is natural for F #, and has a significant impact, both on system design and on how parts of these systems are combined. The functional characteristics of the system become more predictable when the values do not change if you do not expect it.
Moreover, when values are immutable, competitive programming becomes easier. Some complex problems arising in C # due to a changeable state in F # do not occur at all. F # cannot magically solve all your multithreading and asynchronous problems, but it will make many things easier.
As mentioned earlier, F # uses expressions. This contrasts with C #, where statements are used for almost everything. The difference between them may seem insignificant at first glance, but there is one thing to remember: expressions produce meanings. No instructions.
// 'getMessage' -- , `name` - . let getMessage name = if name = "Phillip" then // 'if' - . "Hello, Phillip!" // . else "Hello, other person!" // . let phillipMessage = getMessage "Phillip" // getMessage, , . 'phillipMessage'. let alfMessage = getMessage "Alf" // 'alfMessage'!
In the previous example, you can see a few things that distinguish F # from imperative languages like C #:
if...then...else
is an expression, not an instruction.if
expression returns a value, which in this case will be the return value of the getMessage
function.getMessage
function call is an expression that takes a string and returns a string.This approach is very different from C #, but most likely it will seem natural to you when writing code in F #.
If you dig a little deeper, in F # even the instructions are described using expressions. Such expressions return a value of type unit
. unit
bit like void
in C #:
let names = [ "Alf"; "Vasily"; "Shreyans"; "Jin Sun"; "Moulaye" ] // `for`. 'do' , `unit`. // , . for name in names do printfn "My name is %s" name // printfn unit.
In the previous example with a for
loop, everything is of type unit
. unit
expressions are expressions that have no return value.
The previous code examples used arrays and F # lists. This section explains some details.
F # provides several types of collections and the most common ones are arrays, lists, and sequences.
IEnumerable<T>
. They are calculated lazily.Arrays, lists, and sequences in F # also have a special syntax for expressions. This is very convenient for various tasks when you need to generate data programmatically.
// 100 let first100Squares = [ for x in 1..100 -> x * x ] // , ! let first100SquaresArray = [| for x in 1..100 -> x * x |] // , // // Seq.take! let odds = let rec loop x = // seq { yield x yield! loop (x + 2) } loop 1 printfn " 3 : %A" (Seq.take 3 odds) // : " 3 : seq [1; 3; 5]
If you are familiar with LINQ methods, the following table will help you understand similar functions in F #.
LINQ | F # function |
---|---|
Where | filter |
Select | map |
GroupBy | groupBy |
SelectMany | collect |
Aggregate | fold or reduce |
Sum | sum |
You may also notice that the same set of functions exists for the Seq
, List
and Array
modules. Seq
module functions can be used for sequences, lists, or arrays. Functions for arrays and lists can only be used for arrays and lists in F #, respectively. Also, sequences in F # are lazy, and lists and arrays are vigorous. Using the functions of the Seq
module on lists or arrays entails a lazy evaluation, and the type of the return value will be a sequence.
The previous section contains quite a lot of information, but as you write programs on F # it will become intuitive.
You may have noticed that the |>
operator is used in the previous code examples. It is very similar to pipelines in unix: it takes something to the left of itself and transmits to the input something to the right. This operator (called “pipe” or “pipeline”) is used to create functional pipelines . Here is an example:
let square x = x * x let isOdd x = x % 2 <> 0 let getOddSquares items = items |> Seq.filter isOdd |> Seq.map square
In this example, items
first passed to the input of the Seq.filter
function. Then the return value of Seq.filter
(sequence) is passed to the input of the Seq.map
function. The output of Seq.map
is the output of the getOddSquares
function.
The conveyor operator is very convenient to use, so it is rarely possible without it. Perhaps this is one of the favorite features of F #!
Since F # is the language of the .NET platform, there exist the same primitive types as C #: string
, int
and so on. It uses .NET objects and supports the four main pillars of object-oriented programming. F # provides tuples , as well as two basic types that are not found in C #: records (records) and marked associations (discriminated unions).
A record is a group of ordered named values that automatically implements a comparison operation — in the most literal sense. No need to think about how the comparison: through equality of links or using a custom definition of equality between two objects. Records are values, and values can be compared. They are types of works, if we speak in the language of category theory. They have many uses, but one of the most obvious is that they can be used as a POCO or POJO.
open System // -. // type Person = { Name: string Age: int Birth: DateTime } // `Person` . // , let p1 = { Name="Charles"; Age=27; Birth=DateTime(1990, 1, 1) } // let p2 = { Name="Moulaye" Age=22 Birth=DateTime(1995, 1, 1) } // . Equals() GetHasCode(). printfn " ? %b" (p1 = p2) // `false`, .
The other main type in F # is tagged unions, or PO , or DU in English literature. POs are types representing a number of named variants. In the language of category theory, this is called type-sum. They can also be defined recursively, which greatly simplifies the description of hierarchical data.
// . // // , - ' . type BST<'T> = | Empty | Node of 'T * BST<'T> * BST<'T> // BST<'T> // BST ! let rec flip bst = match bst with | Empty -> bst | Node(item, left, right) -> Node(item, flip right, flip left) // BST let tree = Node(10, Node(3, Empty, Node(6, Empty, Empty)), Node(55, Node(16, Empty, Empty), Empty)) // ! printfn "%A" (flip tree)
Tadam! Armed with the power of markup associations and F #, you can pass any interview in which you want to deploy a binary search tree.
You probably saw a strange syntax in the definition of the Node
variant. This is actually the signature of the tuple. This means that the BST defined by us can be either empty or be a tuple (, , )
. For more information about this, see the section on signatures .
The following code example is presented with the permission of Scott Vlashin , the hero of the F # community, who wrote this excellent review of the F # syntax. You will read it in about a minute. The example has been slightly edited.
// , . . // . (* ( ). *) // ======== "" ( ) ========== // "let" () let myInt = 5 let myFloat = 3.14 let myString = "" // - // ======== ============ let twoToFive = [ 2; 3; 4; 5 ] // , // . let oneToFive = 1 :: twoToFive // :: // : [1; 2; 3; 4; 5] let zeroToFive = [0;1] @ twoToFive // @ // : , ! // ======== ======== // "let" . let square x = x * x // - . square 3 // . . let add xy = x + y // add (x,y)! // . add 2 3 // . // , . // . let evens list = let isEven x = x % 2 = 0 // "isEven" ("") List.filter isEven list // List.filter - // : // , evens oneToFive // // , . // , "map" , // "sum" . // "List.map" "List.sum" let sumOfSquaresTo100 = List.sum (List.map square [ 1 .. 100 ]) // "|>" // sumOfSquares, let sumOfSquaresTo100piped = [ 1 .. 100 ] |> List.map square |> List.sum // "square" // - ( ) // "fun" let sumOfSquaresTo100withFun = [ 1 .. 100 ] |> List.map (fun x -> x * x) |> List.sum // F# - "return" // // ======== ======== // Match..with.. - case/switch " ". let x = "a" match x with | "a" -> printfn "x - a" | "b" -> printfn "x - b" | _ -> printfn "x - - " // " " // Some(..) None Nullable<T> let validValue = Some(99) let invalidValue = None // match..with "Some" "None" // "Some". let optionPatternMatch input = match input with | Some i -> printfn " %d" i | None -> printfn " " optionPatternMatch validValue optionPatternMatch invalidValue // ========= ========= // - , . // . let twoTuple = (1, 2) let threeTuple = ("a", 2, true) // . . type Person = { First: string; Last: string } let person1 = { First="John"; Last="Doe" } // // . let person2 = { First="Jane" Last="Doe" } // . - . type Temp = | DegreesC of float | DegreesF of float let temp = DegreesF 98.6 // . // , -, // : type Employee = | Worker of Person | Manager of Employee list let jdoe = { First="John"; Last="Doe" } let worker = Worker jdoe // ========= ========= // printf/printfn Console.Write/WriteLine C#. printfn " int %i, float %f, bool %b" 1 2.0 true printfn " %s, - %A" "hello" [ 1; 2; 3; 4 ] // printfn "twoTuple=%A,\nPerson=%A,\nTemp=%A,\nEmployee=%A" twoTuple person1 temp worker
In addition, in our official documentation for .NET and supported languages there is the “F # Tour” material.
All described in this post - only superficial possibilities of F #. We hope that after reading this article you will be able to immerse yourself in F # and functional programming. Here are some examples of what you can write as an exercise for further study of F #:
There are so many other tasks for which F # can be used; The previous list is by no means exhaustive. F # is used in various applications: from simple scripts to build to the backend of online stores with billions in revenue . There are no restrictions on projects for which you can use F #.
For F #, there are many tutorials, including materials for those who come with C # or Java experience. The following links may be helpful as you learn more about F #:
Several other ways to get started with learning F # are also described.
Finally, the F # community is very friendly to beginners. There is a very active Slack chat, supported by the F # Software Foundation, with rooms for beginners that you can freely join . We recommend you to do it!
Do not forget to visit the site of the Russian-speaking community F # ! If you have any questions about learning the language, we will be happy to discuss them in chat rooms:
#ru_general
in Slack chat F # Software Foundation The article was translated by the efforts of the Russian-speaking community of F # developers .
@schvepsss .
Source: https://habr.com/ru/post/335560/
All Articles