An introduction to functional programming using Haskell CENG242 –Recitation 1
Introduction: Haskell The most popular purely functional, lazy programming language “Functional programming language”: a program is a collection of mathematical functions “Purely functional”: all variables refer to immutable, persistent values that is, new values can be created, but existing ones cannot be modified no destructive updates “Lazy”: expressions are evaluated “by need”
HUGS A well-known Haskell system An interpreter; test and debug programs in an interactive environment Can be downloaded from its webpage Prelude module is always loaded and contains the standard definitions ( (:) operator, (+), (*), fst, etc.) Hugs will not run without the prelude file. Some helpful commands: :?, :quit, :type / :t, :load / :l, :reload / :r, :main, etc.
HUGS Using the HUGS command prompt, you can evaluate expressions; however you cannot make definitions. You can define new variables and functions in files and load them from Haskell prompt. Hugs> take 3 a where a = [1, 2, 3, 4, 5, 6, 7] [1,2,3] Hugs> b = take 3 a where a = [1, 2, 3, 4, 5, 6, 7] ERROR - Syntax error in input (unexpected `=') Hugs> take 3 a where a = [1, 2, 3, 4, 5, 6, 7] [1,2,3] Hugs> b = take 3 a where a = [1, 2, 3, 4, 5, 6, 7] ERROR - Syntax error in input (unexpected `=') --sample.hs module Sample where b = take 3 a where a = [1, 2, 3, 4, 5, 6, 7] --sample.hs module Sample where b = take 3 a where a = [1, 2, 3, 4, 5, 6, 7] Hugs>:load sample Hugs>b [1,2,3] Hugs>:load sample Hugs>b [1,2,3]
Haskell Value and type concepts are essential in grasping the main idea of Haskell programming language. All computations are done via the evaluation of expressions (syntactic terms) to yield values (abstract entities that we regard as answers). Every value has an associated type. ( :: can be read as ‘has type’) a =1 b = [1, 2, 3] a =1 b = [1, 2, 3] a :: Integer b :: [Integer] a :: Integer b :: [Integer]
Cont’d… Haskell is case-sensitive Type names and constructor names begin with an upper-case letter e.g Expr or Rectangle Value names begin with lower-case letter e.g. x or intTemp Function names begin with lower-case letter (functions are values too) e.g. square or filter
Types Basic types: Integer, String, Float, Double, Char, … [X] : a list of x values [1,2,3,4] [] Lists are homogeneous (elements are of the same type) X -> Y: a function from X value to Y value (X, Y, Z) : a tuple of a X value, a Y value and a Z value Pair: (4, „a ‟ ) Triple: (“hi”, [1,2], 6) Elements do not need to have the same types …
Strong Typing Every expression has a type which can be deduced at compile-time. The main advantage of statically typed languages is well- known: All type errors are detected at compile-time. len :: [Integer] -> Integer len [] = 0 len (x:xs) = len(xs) + 1 number :: [Integer] number = [7, 19, 13] n :: Integer n = len numbers len :: [Integer] -> Integer len [] = 0 len (x:xs) = len(xs) + 1 number :: [Integer] number = [7, 19, 13] n :: Integer n = len numbers
Type Inference In Haskell, writing type annotations is optional Use them to express the intended meaning of your functions Omit them if the meaning is “obvious” When omitted, Haskell finds the type for you! …in fact, the best possible type (also called, the principle type)
Values and Types All Haskell values are "first-class"---they may be passed as arguments to functions, returned as results, placed in data structures, etc. Functions are values too! Hence you can pass functions as arguments to other functions (called higher order functions) Haskell types, on the other hand, are not first-class. Types in a sense describe values, and the association of a value with its type is called a typing. The static type system ensures that Haskell programs are type safe.
Some basic functions length, tail, head, null ++, ==, /= fst, snd sqrt show, read
Defining Functions square x = x * x Here we’re defining a function square that takes one argument, which we call x. Then we say that the value of square x is equal to x*x Functions in Haskell are normally defined by a series of equations (every line gives an equation in the following code): func 0 = 1 func 1 = 5 func 2 = 2 func _ = -1 –Underscore (_) means ‘don’t care’
Defining Functions Consider the following function: The left-hand sides of the equations contain patterns such as 0 and _. In a function application these patterns are matched against actual parameters. If the match succeeds, the right-hand side is evaluated and returned as the result of the application. Note that a stronger match is always preferred over a weaker match, otherwise the above function would always yield 0. unitImpulse :: Integer -> Integer unitImpulse 0 = 1 unitImpulse _ = 0 unitImpulse :: Integer -> Integer unitImpulse 0 = 1 unitImpulse _ = 0
Defining Functions Consider the previous example Haskell can match lists as the first element and remaining portion of list. It’s also possible to match the last element and the rest len :: [Integer] -> Integer len [] = 0 len (x:xs) = len(xs) + 1 len :: [Integer] -> Integer len [] = 0 len (x:xs) = len(xs) + 1 len :: [Integer] -> Integer len [] = 0 len (xs:x) = len(xs) + 1 –Note the difference len :: [Integer] -> Integer len [] = 0 len (xs:x) = len(xs) + 1 –Note the difference
If-then-else expressions if e 1 then e 2 else e 3 signum x = if x < 0 then -1 else if x > 0 then 1 else 0 You must have both a then and an else clause
Case expressions case expressions are used when there are multiple values that you want to check against casefunc x = case x of 0 -> 1 1 -> 5 2 -> 2 _ ->-1
Guards Enable you to allow piecewise function definitions to be taken according to arbitrary Boolean expressions. e.g. compare x y | x < y = “The first is less” | x > y = “The second is less” | otherwise= “They are equal” or; compare2 x y | x < y = "The first is less“ | x > y = "The second is less" compare2 _ _ = "They are equal"
Cont’d… More complicated functions can be built from simpler functions using function composition Function composition: taking the result of one function and use it as an argument e.g. square (sqrt 2) Here, parenthesis is necessary, otherwise the interpreter would think that you try to get the value of “square sqrt” Another way to express function composition is using the (.) function e.g. (square.sqrt) 2
Recursion No loops in Haskell Uses recursion Divide and conquer! e.g. exponential a 0 = 1 exponential a b = a * exponential a (b-1) lenFunc [] = 0 lenFunc (x:xs) = 1 + lenFunc xs
Cont’d… e.g. filterFunc func [] = [] filterFunc func (x:xs) = if func x then x : filterFunc func xs else filterFunc func xs e.g. reverse [] = [] reverse (x:xs) = reverse xs ++ [x]
Type definitions Red, Green, Blue, Rectangle, and Circle are called constructors data Color = Red | Green | Blue f Red = 1 f Blue = 2 f _ = 3 data Color = Red | Green | Blue f Red = 1 f Blue = 2 f _ = 3 data Shape = Rectangle Float Float | Circle Float Area (Rectangle x y) = x * y Area (Circle r) = pi * r * r data Shape = Rectangle Float Float | Circle Float Area (Rectangle x y) = x * y Area (Circle r) = pi * r * r
Cont’d… data keyword provides the capability to define new types. Both Bool and Color are examples of enumerated types, since they consist of a finite number of nullary data constructors. data Color = Red | Green | Blue data Bool = True | False data Color = Red | Green | Blue data Bool = True | False
Cont’d… You should distinguish between applying a data constructor to yield a value, and applying a type constructor to yield a type. The former happens at run-time and is how we compute things in Haskell, whereas the latter happens at compile- time and is part of the type system's process of ensuring type safety. --Point is the Cartesian product of two values of same type --Note x which is lower case. It’s possible to express a new --type, such as Point of Integer (Point Integer) or Point of Float data Point x = Pt x x p :: Point Integer p = Pt Point is the Cartesian product of two values of same type --Note x which is lower case. It’s possible to express a new --type, such as Point of Integer (Point Integer) or Point of Float data Point x = Pt x x p :: Point Integer p = Pt 2 3
Cont’d… It’s crucial to understand the distinction between data constructors and type constructors. Identifiers such as ‘x' above are called type variables, and are uncapitalized to distinguish them from specific types such as Int. --Point is a type constructor, whereas Pt is a data constructor data Point x = Pt x x --For type enforcement, we use the type constructor --In equations, we use data constructors since we try to match --values sumCoord :: Point Integer -> Integer sumCoord (Pt a b) = a + b p :: Point Integer p = Pt 2 3 s = sumCoord p --Point is a type constructor, whereas Pt is a data constructor data Point x = Pt x x --For type enforcement, we use the type constructor --In equations, we use data constructors since we try to match --values sumCoord :: Point Integer -> Integer sumCoord (Pt a b) = a + b p :: Point Integer p = Pt 2 3 s = sumCoord p
Type Synonyms For convenience, Haskell provides a way to define type synonyms; i.e. names for commonly used types. type String = [Char] type AssocList a b = [(a,b)] (Polymorphism) String is simply a list of characters
Examples data List a = Nil | Cons a (List a) listLength Nil = 0 listLength (Cons x xs) = 1 + listLength xs data BinaryTree a = Leaf a | Branch (BinaryTree a) a (Binary Tree a) treeSize (Leaf x) = 1 treeSize (Branch left x right) = 1 + treeSize left + treeSize right
Comments --Computes the length of a list len :: [Integer] -> Integer len [] = 0 len (x:xs) = len(xs) + 1 {- number :: [Integer] number = [7, 19, 13] n :: Integer n = len numbers -} --Computes the length of a list len :: [Integer] -> Integer len [] = 0 len (x:xs) = len(xs) + 1 {- number :: [Integer] number = [7, 19, 13] n :: Integer n = len numbers -}
Any questions?