Saturday, February 23, 2013

HASKELL PATTERN MATCHING

HASKELL PATTERN MATCHING

REVISED: Wednesday, January 24, 2024




Haskell Pattern Matching.


Make everything as simple as possible, but not simpler.

I. PATTERN MATCHING

Pattern matching is the Haskell way of doing object oriented programming dynamic dispatch.

"Copy, Paste" the following module into your text editor and "File, Save As" MyMath.hs to your working directory. 

module MyMath where

mulx :: Num a => a -> a

mulx x = x * x

main :: IO ()

main = do
    putStrLn "Please enter an integer and then press Enter."
    inpStr <- getLine
    let x = (read inpStr)::Integer
    putStrLn (show x ++  " multiplied by " ++  show x ++ " is " ++ show (mulx x))

Function mulx takes one argument. Any formal parameter, on the left hand side of a function definition, can be replaced with a pattern. When you call mulx with a number, the number is matched with the pattern x, and x takes up that value. 

Load MyMath into GHCi as follows:

Prelude>  :load MyMath
[1 of 1] Compiling MyMath           ( MyMath.hs, interpreted )
Ok, modules loaded: MyMath.
Prelude>

Call the MyMath function main as follows:

Prelude>  main
Please enter an integer and then press Enter.
5
5 multiplied by 5 is 25
Prelude>  

You can never execute an IO () action inside the program. GHC compiles your code and links it with a runtime system (RTS). The RTS calls main and the IO () actions are executed by the RTS in mainWithout IO () nothing would ever be evaluated in Haskell.

Prelude>  :type main
main :: IO ()  -- main returns an IO () action.
Prelude>  

Prelude>  :type putStrLn
putStrLn :: String -> IO ()   -- putStrLn returns an IO () action.
Prelude>

Pattern matching is about taking apart a value by finding out which constructor it was built with. A pattern lets us look inside a value and bind variables to the data it contains.  There is no limit on how deep within a value a pattern can look. The fundamental construct for doing pattern-matching in Haskell is the case expression.

Prelude>  let f x = case x of { (1) -> "Yes"; (2) -> "No"; (3) -> "Maybe" }
Prelude>

Prelude>  f 1
"Yes"
Prelude>

Prelude>  f 2
"No"
Prelude>

Prelude>  f 3
"Maybe"
Prelude>  

Functions which have inputs that make them recurse infinitely are called partial. Functions which are well-defined on all possible inputs are known as total functions. Partial functions can be replaced by pattern-matching.

Functions can get different types of input and pattern-matching is a powerful way for describing different input types. Patterns are syntactic expressions. A function can be multiple defined, each definition having a particular pattern for its input arguments. The first pattern that matches the argument is used for that function call.

An underscore _ is a pattern wildcard and is used where something should match, but where the matched value is not used. The special underscore pattern _ wildcard will match anything. You can only use the underscore _ pattern wildcard on the left hand side of a pattern match, and never on the right hand side of an = symbol.

A pattern that always succeeds is referred to as irrefutable. Plain variable names and the wild card _ are examples of irrefutable patterns.

Consider the definition of map:

map _ []       = []
map f (x:xs) = f x : map f xs

Haskell patterns are matched top-to-bottom and left-to-right.

In the above map example, there are four different patterns involved, two per equation. 

In the first equation:

_ is the pattern wildcard which matches anything, but does not do any binding.

[] is a pattern that matches the empty list. It does not bind any variables.

In the second equation:

f is a pattern which matches anything at all, and binds the f variable to whatever is matched.

(x:xs) is a pattern that matches a non-empty list which is formed by something which gets bound to the x variable which was cons'd by the (:) function onto something else which gets bound to xsThe idea is that the name xs has an “s” on the end of its name because xs is the “plural” of x. x is the head of the list, and xs are the remaining elements in the list.

A. HASKELL SYNTATIC CONSTRUCTS

You can pattern match on any data type. You can define separate function bodies for different patterns. Order is important when specifying patterns and it is always best to specify the most specific ones first, and then the more general ones later. When making patterns, always include a catch-all pattern so the program does not crash if you get some unexpected input.

You cannot use ++ in pattern matches.

Always use pattern matching instead of guards when both are appropriate.

where is a keyword that introduces local definitions. where bindings are a syntactic construct that let you bind to variables at the end of a function and the whole function can see them, including all the guards.

Function application has higher precedence than all other operators. For example, function application has higher precedence than any infix operators.

The following table shows the Haskell precedence levels from 0 to 9, with 9 being the strongest.





Prec-Left associativeNon-associativeRight associative
edenceoperatorsoperatorsoperators








9!!.




8^ , ^^ , **




7*/‘div‘,
‘mod‘‘rem‘‘quot‘




6+-




5:++




4==/=<<=>>=,
‘elem‘‘notElem‘




3&&




2||




1>>>>=




0$$!‘seq‘





As shown below :info (*) is used to determine the precedence of the (*) operator infixl 7 * matches the precedence for * as shown above.

Prelude> :info (*)
class Num a where
  ...
  (*) :: a -> a -> a
  ...
  -- Defined in `GHC.Num'
infixl 7 *                                   -- infix left associative precedence level 7.
Prelude>  

Associativity and precedence rules are fixity rules.

Associativity determines whether an expression containing multiple uses of an operator is evaluated from left to right, or right to left.

You might have learned precedence asParentheses, Exponents, Multiplication, Division, Addition, Subtraction or "Please Excuse My Dear Aunt Sally" (PEMDAS).  

Things can be compared for equality with (==) and (/=), or compared for order using (<), (>), (<=), and (>=).

Copy the following Haskell reverse Polish notation calculator function and paste it into your text editor:

module Calculator where

import Data.List

calculator :: String -> Float  
calculator = head . foldl folder [] . words  
    where   
    folder (x:y:ys) "^"        = (y ** x):ys   
    folder (x:y:ys) "*"         = (x * y):ys  
    folder (x:y:ys) "/"         = (y / x):ys  
    folder (x:y:ys) "+"         = (x + y):ys   
    folder xs "sum"              = [sum xs] 
    folder (x:y:ys) "-"          = (y - x):ys 
    folder (x:xs) "ln"           = log x:xs  
    folder xs numericInput = read numericInput:xs  

Pattern matching on function parameters can only be done when defining functions.

The matching process itself occurs top-down, left-to-right.

Case expressions can also be created by using syntactic sugar pattern matching in function definitions.

From your text editor do a File, Save As, and save the file to your working directory as:

Calculator.hs

The calculator function is coded as seven patterns. The patterns will be tried from top to bottom.

Open GHCi, after the GHCi  Prelude> prompt type :load Calculator as shown below, then press Enter:

Prelude> :load Calculator
[1 of 1] Compiling Main             ( Calculator.hs, interpreted )
Ok, modules loaded: Main.
Prelude>

Always test new functions; for example, you could use the following GHCi code to test the calculator:

Prelude> calculator "2 2 *"
4.0
it :: Float
Prelude>

Prelude> calculator "2 2 +"
4.0
it :: Float
Prelude>

Prelude> calculator "2 2 -"
0.0
it :: Float
Prelude>

Prelude> calculator "2 2 /"
1.0
it :: Float
Prelude>

Prelude> calculator "2 2 ^"
4.0
it :: Float
Prelude>

Prelude> calculator "2 2 ln"
0.6931472
it :: Float
Prelude>

Prelude> calculator "2 2 sum"
4.0
it :: Float
Prelude>

You can easily modify the calculator function to support other operators. If you want to make changes to the "calculator function" use the following GHCi code:

Prelude> :edit
Ok, modules loaded: Main.
Prelude>

A Notepad window will open and display the "calculator function" code.

After you make changes do a Notepad "File Save" and GHCi :load Calculator before you test your revisions.

B. RECORD SYNTAX

Using record syntax defines a data type, and accessor functions for each of the fields, simultaneously.

C. ACCESSOR FUNCTIONS

For each of the fields that we name in our type definition, Haskell creates an accessor function of that name. The accessor functions that we get for free when we use record syntax are normal Haskell functions.

D. HASKELL PATTERN MATCHING ADVANTAGES

Use pattern matching instead of nested ifs.

Use pattern matching instead of guards.

In general, you should use pattern matching whenever possible.

E. AS-PATTERN @

The @ Symbol is used to both give a name to a parameter and match that parameter against a pattern that follows the @. The pattern xxs@(_:xs) shown below is called an "as-pattern," and it means "bind the variable xxs to the value that matches the right side of the @ symbol." It is not specific to lists and can also be used with other data structures.

This is useful if you want to decompose a parameter into its parts while still needing the parameter as a whole somewhere in your function. One example where this is often the case is the tails function from the standard library:

-- C:\Users\Tinnel\Haskell2024\asPattern.hs

import Data.List
import System.IO

tails'                           ::  [a] -> [[a]]
tails' []                       =  [[]]
tails' xxs@(_:xs)       =  xxs : tails' xs

Prelude> :l asPattern
[1 of 1] Compiling Main             ( asPattern.hs, interpreted )
Ok, modules loaded: Main.
Prelude>

Prelude> tails' [0,1,2,3]
[[0,1,2,3],[1,2,3],[2,3],[3],[]]
Prelude>

Notice the first list in the above list of lists is the original list, followed by all possible tail combinations and ending with the empty list.

Prelude> let horse = tails' [0,1,2,3]
Prelude>

Prelude> horse
[[0,1,2,3],[1,2,3],[2,3],[3],[]]
Prelude>

Prelude> head horse
[0,1,2,3]
Prelude>

II. CONCLUSION

In this tutorial, you have received an introduction to Haskell pattern matching.

III. REFERENCES

Bird, R. (2015). Thinking Functionally with Haskell. Cambridge, England: Cambridge University Press.

Davie, A. (1992). Introduction to Functional Programming Systems Using Haskell. Cambridge, England: Cambridge University Press.

Goerzen, J. & O'Sullivan, B. &  Stewart, D. (2008). Real World Haskell. Sebastopol, CA: O'Reilly Media, Inc.

Hutton, G. (2007). Programming in Haskell. New York: Cambridge University Press.

Lipovača, M. (2011). Learn You a Haskell for Great Good!: A Beginner's Guide. San Francisco, CA: No Starch Press, Inc.

Thompson, S. (2011). The Craft of Functional Programming. Edinburgh Gate, Harlow, England: Pearson Education Limited.