📜 ⬆️ ⬇️

[F #] pattern matching

As in many other functional programming languages, pattern matching (pattern matching) is supported in F #. Those who are not familiar with the OP can imagine it as a much improved switch.


Go to the code.

let rec factorial n =
match n with
| 0 | 1 -> 1
| _ -> n * factorial ( n - 1 )
')
This code defines a recursive factorial function. As you can see, it is very similar to switch.

Couples, threes, etc.


let ( + ) vec multiplier =
match vec with x, y ->
x * multiplier, y * multiplier

Here the element is matched with a pair of values ​​(touple). Accordingly, x takes the value of the first element of the pair, y - the second. Note that x and y are new value descriptors. If earlier in the program another value was described with the name x, then it will be hidden again by the end of the branch. In order not to introduce a new description, you must use the underscore character, as was done in the first code example. It can occur in a sample as many times as desired and is essentially a placeholder.

Lists



let rec sum = function
| [ ] -> 0
| head :: tail ->
head + sum tail

This abbreviated entry is equivalent to

let rec sum value =
match value with
| [ ] -> 0
| head :: tail ->
head + sum tail

The only difference is that a descriptor with the name value is not entered.

The first pattern, [], corresponds to an empty list. The second sample describes the list as two parts, the head (the first element) and the tail (a list of all the other elements). Names in place of head and tail can be arbitrarily chosen. Their field of view is to the end of the branch.

You can separate multiple items from the beginning of the list. el1 :: _ :: el3 :: tail

Several meanings


When comparing samples, one can not only break a pair into its components, but also work with a pair of values.

match host port with
| “Microsoft.com” , 80 -> Allow
| “Warezzz.ws” , 80 -> Deny
| _ -> Forward

Pattern matching can be used to determine types (dynamic).

match obj with
| : ? String as str -> "String!"
| : ? Int32 as integer -> "Int!"
| _ -> "What is it?"

Additional terms


let rec hasNegative = function
| [ ] -> false
| head :: tail when head < 0 -> true
| _ :: tail -> hasNegative tail

I want to note that the comparison of samples occurs consistently and therefore if one of the samples came up, then the subsequent ones are no longer compared. Due to this, it is not necessary to write when head> = 0 in the last branch.

Active Patterns


This feature is probably unique to F #. We are given the opportunity to define our own matching methods. Consider a simple example.

let ( | Odd | Even | ) = function
| x when x% 2 = 0 -> Even
| _ -> Odd

match 10 with
| Odd -> "oO"
| Even -> ":)"

The matching method is essentially a special defined function. She does not have a name, but a pair of samples is determined at once - Odd and Even.

In addition to the simple choice, you can organize arbitrary. Partial matching methods are used for this.

let ( | Prime | _ | ) value =
let composite = List . exists ( fun n -> value% n = 0 ) [ 2 . . int ( Math . Sqrt ( value ) ) ]
Some ( value )
else None

match n with
| Prime x -> sprintf "input% d is prime!" X
| x -> sprintf "this value is not special"

This was a fairly simple example. Let's look more complicated.

type StackItem =
| Int of int32
| String of string

let ( | Add | _ | ) = function
| OpCode . Add, Int ( a ) :: Int ( b ) :: stackRest ->
Some ( Int ( a + b ) :: stackRest )
| OpCode . Add, String ( a ) :: String ( b ) :: stackRest ->
Some ( String ( a + b ) :: stackRest )
| OpCode . Add, _ ->
InvalidProgramException ( "invalid add args" ) | > raise
| _ -> None

let ( | Sub | _ | ) = function
| OpCode . Add, Int ( a ) :: Int ( b ) :: stackRest ->
Some ( Int ( a - b ) :: stackRest )
| OpCode . Add, _ ->
InvalidProgramException ( "invalid sub args" ) | > raise
| _ -> None

let compute opcode currentStack =
match opcode, currentStack with
| Add ( newStack ) | Sub ( newStack ) -> newStack
| _ -> InvalidProgramException ( sprintf "invalid opcode% A" opcode )


In the partial matching method, a special type of option (analogue of Nullable) is used, described in the following way:
type Option 'a =
| None
| Some of 'a

Accordingly, to indicate that the object does not match the pattern, None is returned. In this case, we also return the result of applying the pattern as a parameter Some and then use it (newStack).

Results


As you can see, the comparison of samples is quite a useful thing. They are almost the only reason why I choose F # instead of C # (it is very convenient to use them during translation, see the example code with the stack machine). Many use cases, compact code, extensibility due to its matching methods - all this makes pattern matching in F # very, very attractive.

PS I apologize for the fact that not all examples of the code are the correct quotes. This is a malicious joke played either habraparser, or a means of highlighting the code.

Source: https://habr.com/ru/post/89912/


All Articles