Hi, Habr! We are a little late return from the New Year holidays with the continuation of our series of articles on functional programming. Today we will tell about understanding of functions through signatures and the definition of own types for signatures of functions. Details under the cut!
Not obvious, but in F # there are two syntaxes: for ordinary (meaningful) expressions and for defining types. For example:
[1;2;3] // int list // Some 1 // int option // (1,"a") // int * string //
Expressions for types have a special syntax that differs from the syntax of ordinary expressions. You may have noticed many examples of this syntax while working with FSI (FSharp Interactive), since The types of each expression are displayed along with the results of its execution.
As you know, F # uses the type inference algorithm, so often you don’t need to explicitly type in the code, especially in functions. But in order to work effectively with F #, you need to understand the type syntax so that you can define your own types, debug type-casting errors, and read function signatures. In this article, I will focus on using types in function signatures.
Here are some examples of type syntax signatures:
// // let add1 x = x + 1 // int -> int let add xy = x + y // int -> int -> int let print x = printf "%A" x // 'a -> unit System.Console.ReadLine // unit -> string List.sum // 'a list -> 'a List.filter // ('a -> bool) -> 'a list -> 'a list List.map // ('a -> 'b) -> 'a list -> 'b list
Often, even just by examining the signature of a function, you can get some idea of ​​what it does. Consider a few examples and analyze them in turn.
int -> int -> int
This function takes two int
parameters and returns another int
. Most likely, this is a kind of mathematical functions, such as addition, subtraction, multiplication or exponentiation.
int -> unit
This function takes an int
and returns unit
, which means that the function does something important in the form of a side effect. Since it does not return a useful value, the side effect is most likely to write to the IO, such as logging, writing to the database, or something similar.
unit -> string
This function takes nothing, but returns a string
, which may mean that the function gets a string from the air. Since there is no explicit input, the function probably does something with reading (say from a file) or generation (for example, a random string).
int -> (unit -> string)
This function accepts an int
and returns another function that, when called, will return a string. Again, the function is likely to perform a read or generate operation. Input, most likely, somehow initializes the returned function. For example, input may be a file identifier, and the returned function is similar to readline()
. Alternatively, the input may be the initial value for the random string generator. We cannot say for sure, but we can draw some conclusions.
'a list -> 'a
The function accepts a list of any type, but returns only one value of this type. This may indicate that the function aggregates the list or selects one of its elements. A similar signature has List.sum
, List.max
, List.head
, etc.
('a -> bool) -> 'a list -> 'a list
This function takes two parameters: the first is a function that converts something into a bool
(predicate), the second is a list. The return value is a list of the same type. Predicates are used to determine if an object meets a certain criterion, and it looks like this function selects items from the list according to the predicate — true or false. After that, it returns a subset of the source list. An example of a function with this signature is List.filter
.
('a -> 'b) -> 'a list -> 'b list
The function takes two parameters: a conversion from type 'a
to type 'b
and a list of type 'a
. The return value is a list of type 'b
. It is reasonable to assume that the function takes each element from the list of 'a
, and converts it to 'b
, using the function passed as the first parameter, and then returns the list 'b
. And indeed, List.map
is a prototype of a function with such a signature.
Function signatures are very important in finding library functions. The F # libraries contain hundreds of functions, which at first can be confusing. Unlike object-oriented languages, you cannot simply “enter an object” through a dot to find all the related methods. But if you know the signature of the desired function, you can quickly narrow down the search.
For example, you have two lists, and you want to find a function combining them into one. What signature would the desired function have? It would take two lists as parameters and return a third one, all of the same type:
'a list -> 'a list -> 'a list
Now let's go to the MSDN documentation site for the List module , and look for a similar function. It turns out that there is only one function with this signature:
append : 'T list -> 'T list -> 'T list
Exactly what is needed!
Someday you will want to define your own types for the desired function. This can be done using the "type" keyword:
type Adder = int -> int type AdderGenerator = int -> Adder
In the future, you can use these types to limit the values ​​of the parameters of functions.
For example, the second declaration due to the constraint imposed will fall with a type conversion error. If we remove it (as in the third ad), the error will disappear.
let a:AdderGenerator = fun x -> (fun y -> x + y) let b:AdderGenerator = fun (x:float) -> (fun y -> x + y) let c = fun (x:float) -> (fun y -> x + y)
Do you understand function signatures well? Check yourself if you can create simple functions with the signatures below. Avoid explicit types!
val testA = int -> int val testB = int -> int -> int val testC = int -> (int -> int) val testD = (int -> int) -> int val testE = int -> int -> int -> int val testF = (int -> int) -> (int -> int) val testG = int -> (int -> int) -> int val testH = (int -> int -> int) -> int
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 strongly recommend that you do this!
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 FoundationTranslated by @kleidemos Translation and editorial changes are made by the efforts of the Russian-speaking community of F # -developers . We also thank @schvepsss and @shwars for preparing this article for publication.
Source: https://habr.com/ru/post/433402/
All Articles