F # Adventure (II): functional programming (II)

Source: Internet
Author: User
Tags finally block reflector ocaml

Pattern Matching)

Pattern Matching allows you to perform different operations based on the value of the identifier. It is a bit like a series of IF... else structures, and also like the switch in C ++ and C #, but it is more powerful and flexible.

Let's look at the following example of the Lucas sequence. The definition of the Lucas sequence is the same as that of the Fibonacci sequence, except that the starting value is different:

Code
let rec luc x =
  match x with
  | x when x <= 0 -> failwith "value must be greater than zero"
  | 1 -> 1
  | 2 -> 3
  | x -> luc(x - 1) + luc(x - 2)

printfn "(luc 2) = %i" (luc 2)
printfn "(luc 6) = %i" (luc 6)

Here we can see a simple application of pattern matching. The match and with keywords are used. Different pattern rules are separated by "|", while "->" indicates that if the pattern matches, what is the result.

The output of this example is:

Output
(luc 2) = 3
(luc 6) = 18

Matching rules are defined in the order they are defined, and pattern matching must be fully defined. That is, for any possible input value, there is at least one mode that can satisfy it (that is, it can handle it); otherwise
The compiler reports an error message. In addition, the rules at the beginning should not be more general than those at the end. Otherwise, the rules at the end will never be matched, and the compiler will report a warning message, which is similar
The exception handling method in C #. When capturing exceptions, we cannot first avoid "general" exception exceptions, and then capture "more specific"
Nullreferenceexception.

You can add a when guard statement to a pattern rule to understand the when statement as a stronger constraint on the current rule. Only when the value of the when statement is true, this
A pattern rule is a match. In the first rule in the preceding example, if no when statement is available, any integer can match the mode. After the when statement is added, only non-positive integers can be matched. For the simplest
We can omit the first "| ":

Code
let boolToString x =
  match x with false -> "False" | _ -> "True"

This example contains two pattern rules. "_" can match any value. Therefore, when the value of X is false, it matches the first rule. Otherwise, it matches the second rule.

Another useful feature is that we can merge two pattern rules to take the same processing method for them, which is like switch in C... Two cases can be merged in the case structure.

Code
let stringToBool x =
  match x with
  | "T" | "True" | "true" -> true
  | "F" | "False" | "false" -> false
  | _ -> failwith "Invalid input."

In this example, we combine the three pattern rules to convert the string value to the corresponding Boolean value.

Pattern matching can be performed on the types defined in most F #. The following example shows matching the tuples.

Code
let myOr b1 b2 =
  match b1, b2 with
  | true, _ -> true
  | _, true -> true
  | _ -> false

let myAnd p =
  match p with
  | true, true -> true
  | _ -> false

These two functions demonstrate how to apply pattern matching to tuples. Their function is to calculate the results of two boolean values "or" and "and. In myor, you can know the rules of the first and second modes.
If either B1 or B2 is true, the calculation result is true. Otherwise, the calculation result is false. Myor true false results in true, myand (true,
False) the result is false.

A common usage of pattern matching is to match the list. In fact, compared with if... Then... Else structure, better pattern matching. See the following example:

Code
let listOfList = [[2; 3; 5]; [7; 11; 13]; [17; 19; 23; 29]]
let rec concatenateList list =
  match list with
  | head :: tail -> head @ (concatenateList tail)
  | [] -> []

let rec concatenateList2 list =
  if List.nonempty list then
    let head = List.hd list in
    let tail = List.tl list in
    head @ (concatenateList2 tail)
  else
    []

let primes = concatenateList listOfList
print_any primes

Listoflist is a list. Both the concatenatelist and concatenatelist2 Functions
The listoflist element is connected to a large list, but is implemented in a pattern matching mode, and the if... Then... Else structure implementation. We can see the code using pattern matching.
It is more concise and clear. Observe the concatenatelist function, which processes the list by taking out the Header element (head) of the list, processing it, And then recursively processing the remaining elements. This is actually
The most common way to process a list by means of pattern matching (but not the only way ).

In F #, pattern matching is also available elsewhere, which will be introduced in subsequent articles.

 

 

Defining types)

The F # type system provides several features for creating custom types. All types can be divided into two types: tuple or record, which are similar to the class in C #; union, which is sometimes called Sum. Next, let's take a look at their features.

Tuples are an ordered set of any object. With them, we can quickly and conveniently combine a set of values. After creation, you can reference the values in the tuples.

Code
let pair = true, false
let b1, b2 = pair
let _, b3 = pair
let b4, _ = pair

The first line of code creates a tuples with the bool * bool type. This indicates that the pair tuples contain two values and their types are both bool. Through the second, third, and fourth lines of code, you can access the value of the tuples. "_" tells the compiler that we are not interested in this value and ignore it. Here, the B1 value is true, and the B3 value is false.

For further analysis, tuples are a type, but we do not explicitly use the type keyword to declare the type. pair is essentially an instance of the tuple class in F #, rather than a custom type. If you need to declare a custom type, you need to use the type keyword. The simplest case is to give an alias for an existing type:

Code
type Name = string
// FirstName, LastName
type FullName = string * string

For the name type, it is only an alias of the string type, and fullname is an alias of the tuples type.

The record type is similar to the tuples. It also combines multiple types of values into the same type. The difference is that all fields in the record type have names. See the following example:

Code
type Organization = { Boss : string; Lackeys : string list }
let family =
  { Boss = "Children";
   Lackeys = ["Wife"; "Husband"] }

The first line is to create the organization type, and the second line is to create its instance. Surprisingly, the instance type does not need to be declared. F # the compiler can deduce its class according to its field name.
Type. This function is very powerful, but F # does not force that fields of each type are different. What if the names of each field of the two types are the same? In this case, the type can be explicitly declared:

Code
type Company = { Boss : string; Lackeys : string list }
let myCom =
  { new Company
   with Boss = "Bill"
   and Lackeys = ["Emp1"; "Emp2"] }

Generally, the scope of the type ranges from the declaration to the end of the source file. If a type needs to be declared after it, you can declare the two types in the same code block. Use and to separate the types. See the following recipe example:

Code
type recipe =
  { recipeName : string;
   ingredients : ingredient list;
   instructions : string }
and ingredient =
  { ingredientName : string;
   quantity : int }
let greenBeansPineNuts =
  { recipeName = "Green Beans & Pine Nuts";
   ingredients =
    [{ingredientName = "Green beans"; quantity = 200};
     {ingredientName = "Pine nuts"; quantity = 200}];
   instructions = "Parboil the green beans for about 7 minutes." }

let name = greenBeansPineNuts.recipeName
let toBuy =
  List.fold_left
    (fun acc x ->
      acc + (Printf.sprintf "t%s - %irn" x.ingredientName x.quantity))
    "" greenBeansPineNuts.ingredients
let instructions = greenBeansPineNuts.instructions
printf "%srn%srnrnt%s" name toBuy instructions

This example not only shows how to declare two types in one, but also shows how to access the recorded field values. We can see that the access record field is more convenient than the value of the access tuples.

You can also apply the following pattern to the record type:

Code
type couple = { him : string; her : string }
let couples =
  [ { him = "Brad"; her = "Angelina" };
   { him = "Becks"; her = "Posh" };
   { him = "Chris"; her = "Gwyneth" } ]

let rec findDavid list =
  match list with
  | { him = x; her = "Posh" } :: tail -> x
  | _ :: tail -> findDavid tail
  | [] -> failwith "Couldn't find David"

print_string(findDavid couples)

First, a list of the couple type is created. The finddavid function will perform a pattern match on the list to compare the field with a constant value, such as her = "posh". The field value is assigned to the identifier, for example, he = x; you can also use "_" to ignore the value of a field. The printed result of the above example is Becks.

The field value can also be a function, which will be introduced in the third part of this series.

The Union type, sometimes called sum or discriminated Union, can combine a group of data with different meanings or structures. It can be used in combination with C or enumeration in C. Let's look at an example:

Code
type Volume =
| Liter of float
| UsPint of float
| ImperialPint of float

The volume type belongs to the Union type and contains three data constructor. Each constructor contains a single float value. Declaring an instance is very simple:

Code
let vol1 = Liter 2.5
let vol2 = UsPint 2.5
let vol3 = ImperialPint 2.5

In fact, we can see from reflector that liter, uspint, and imperialpint are derived classes of the volume type. When resolving the Union type to its basic type, we need to match the pattern.

Code
let convertVolumeToLiter x =
  match x with
  | Liter x -> x
  | UsPint x -> x * 0.473
  | ImperialPint x -> x * 0.568

The record type and union type can be parameterized (parameterized ). Parameterization means that in a type definition, it uses one or more other types, which are not determined in the definition, but determined in the Customer Code of the Code. This is similar to the variable type mentioned above.

F # supports two types of parameterization syntax. Let's look at the first one:

OCaml-Style
type 'a BinaryTree =
  | BinaryNode of 'a BinaryTree * 'a BinaryTree
  | BinaryValue of 'a

let tree1 =
  BinaryNode(
    BinaryNode(BinaryValue 1, BinaryValue 2),
    BinaryNode(BinaryValue 3, BinaryValue 4))

In the binarytree family of the type keyword and type name, 'a is a variable type, and its exact type is determined in the code that uses it. This is the ocaml-style syntax. In the identifier tree1, the value used to define binaryvalue is 1, and the compiler resolves 'a to the int type. Let's look at the second syntax:

.NET-Style
type Tree<'a> =
  | Node of Tree<'a> list
  | Value of 'a

let tree2 =
  Node( [Node([Value "One"; Value "Two"]);
    Node([Value "Three"; Value "Four"])])

This syntax is closer to the generic definition in C # And is A. Net-style syntax. In tree2, 'a is parsed to the string type. No matter which syntax is followed by single quotes, we generally only use a single letter.

The process for creating and using instances of parameterized types is the same as that of non-parameterized types, because the compiler automatically derives parameterized types.

In this section, we discuss the tuples, records, and Union types one by one. Reflector shows that the values of tuples are tuple instances, while tuple implements
Microsoft. fsharp. Core. istructuralhash and system. icomparable interfaces; records and union are directly implemented.
These two interfaces. For more information about the istructualhash interface, see jome Fisher's article.

At this point, we have discussed how to define types, create and use their instances, but we have not mentioned the changes to them. That's because we cannot modify these types of values, which is one of the features of functional programming. However, F # provides multiple programming paradigms that can be modified for some types, which will be described in the next section (imperative programming.

 

Exception Handling)

In F #, the exception definition is similar to the Union type definition, while the Exception Processing syntax is similar to the pattern matching. Use the exception keyword to define an exception. (optional) if the exception contains data, we should declare the data type. Note that the exception can contain multiple types of data:

Code
exception SimpleException
exception WrongSecond of int
// Hour, MInute, Second
exception WrongTime of int * int * int

To throw an exception, use the raise keyword. F # provides another method. If you only want to throw an exception containing text information, you can use the failwith function, which throws a failureexception type exception.

Code
let testTime() =
  try
    let now = System.DateTime.Now in
    if now.Second < 10 then
      raise SimpleException
    elif now.Second < 30 then
      raise (WrongSecond now.Second)
    elif now.Second < 50 then
      raise (WrongTime (now.Hour, now.Minute, now.Second))
    else
      failwith "Invalid Second"
  with
    | SimpleException ->
      printf "Simple exception"
    | WrongSecond s ->
      printf "Wrong second: %i" s
    | WrongTime(h, m, s) ->
      printf "Wrong time: %i:%i:%i" h m s
    | Failure str ->
      printf "Error msg: %s" str

testTime()

This example shows how to throw and capture various exceptions. If you are familiar with exception handling in C #, you should not be unfamiliar with this.

Similar to C #, F # also supports the finally keyword, which must be used together with the try keyword. No matter whether an exception is thrown or not, the code in the Finally block is executed. In the following example, the Finally block is used to ensure that the file is properly closed and released:

Code
let writeToFile() =
  let file = System.IO.File.CreateText("test.txt") in
  try
    file.WriteLine("Hello F# Fans")
  finally
    file.Dispose()

writeToFile()

Note that, due to the CLR architecture, it is very expensive to throw an exception, so be careful when using it.

Latency (or inertia, lazy evaluation)

Lazyload in ibatis is the first thing that comes into contact with lazy, that is, delayed loading. It does not load all data at the beginning, but reads data only when necessary. Latency is similar to this. In addition to performance improvement, latency can also be used to create infinite data structures.

Latency is closely related to functional programming languages. The principle is that if a language does not have any side effects, the compiler or runtime can select the order in which expressions are evaluated at will. F # Allow functions to have side effects. Therefore, the compiler or runtime cannot evaluate functions in random order, it can be said that F # has a strict order of value or F # is a strict language.

If you want to use the latency evaluation feature, you must explicitly declare the expressions that require latency. This uses the lazy keyword. To evaluate the expression, call the lazy module.
. When the force function is called, it calculates the value of the expression, and the obtained value is cached. When the force function is applied to the expression again, the obtained value is actually cached.
.

Code
let sixtyWithSideEffect = lazy(printfn "Hello, sixty!"; 30 + 30)
print_endline "Force value the first time:"
let actualValue1 = Lazy.force sixtyWithSideEffect
print_endline "Force value the second time:"
let actualValue2 = Lazy.force sixtyWithSideEffect

The output is as follows:

Code
Force value the first time:
Hello, sixty!
Force value the second time:

Section

This article continues to discuss the core content of F # functional programming paradigm, including pattern matching, custom types, exception handling, and latency evaluation, F # functional programming
. Pattern matching can greatly simplify our programs; custom types can help us better organize programs; latency evaluation can not only improve performance, it can also be used to create infinite data structures, such as natural
Number Sequence. In addition, when developing the F # program, we recommend that you use reflector to look at the compiled code to understand what is behind its elegant functional programming. At the next stop, let's look at our lives.
The landscape of ordered programming.

Refer:

Foundations of F # by Robert Pickering

Expert F # by Don Syme, Adam granicz, Antonio cisternino

F # specs

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.