Haskell Notes

    科技2022-07-10  152

    Haskell Note

    1 Introduction1.1 So what's Haskell1.2 What you need to dive in 2 Starting out2.12.2 Baby's first functions2.2.1 If statement 2.3 An Introduction to lists2.3.1 ++ , : , !! operator2.3.2 Some properties about List2.3.4 Some basic functions that operate on lists 2.4 Ranges2.4.1 Some functions that produces infinite lists 2.5 list comprehension2.6 Tuples2.6.1 Tuples and List2.6.2 Two useful functions that operate on pairs(tuple with two components):2.6.3 A cool function that produces a list of pairs: zip 3 Types and Typeclasses3.1 Believe the type3.1.1 use GHCI to examine the types of some expressions3.1.2 The type of functions3.1.3 An overview ofsome common types 3.2 Type variables3.3 Typeclasses3.3.1 Some basic type classes 4 Syntax in Functions4.1 Pattern matching

    1 Introduction

    1.1 So what’s Haskell

    Haskell is a purely functional programming language. In purely functional programming you don’t tell the computer what to do as such but rather you tell it what stuff is. You cannot set a variable to something and then set it to something else later. The only thing a function can do is calculate something and return it as a result. It can have some very nice e consequences: if a function is called twice with the same parameters, it’s guaranteed to return the same result, which is called referential transparency.

    Haskell is lazy.That means that unless specifically told otherwise, Haskell won’t execute functions and calculate things until it’s really forced to showyou a result.

    Haskell is statically typed. That means that a lot of possible errors are caught at compile time.Haskell uses a very good type system that has type inference.That means that you don’t have to explicitly label every piece of code with a type because the type system can intelligently figure out a lot about it. Type inference also allows your code to be more general.

    Haskell is elegant and concise. Because it uses a lot of high level concepts, Haskell programs are usually shorter than their imperative equivalents.

    1.2 What you need to dive in

    A text editor and a Haskell compiler. GHC can take a Haskell script (they usually have a .hs extension) and compile it but it also has an interactive mode which allows you to interactively interact with scripts.You can call functions from scripts that you load and the results are displayed immediately. The interactive mode is invoked by typing in ghci at your prompt.

    2 Starting out

    2.1

    + works only on things that are considered numbers,== works on any two things that can be compared. They both have to be the same type of thing. * is an infix function.In Haskell, functions are called by writing the function name, a space and then the parameters, separated by spaces.The succ function takes anything that has a defined successor and returns that successor.Function application (calling a function by putting a space after it and then typing out the parameters) has the highest precedence of them all.If a function takes two parameters, we can also call it as an infix function by surrounding it with backticks. 92 `div` 10

    2.2 Baby’s first functions

    Functions are defined in a similar way that they are called. The function name is followed by parameters seperated by spaces. doubleUs x y = 2*x + 2*y

    2.2.1 If statement

    doubleSmallNumber x = if x > 100 then x else x*2

    The difference between Haskell’s if statement and if statements in imperative languages is that the else part is mandatory in Haskell. In Haskell every expression and function must return something. Another thing about the if statement in Haskell is that it is an expression.

    doubleSmallNumber' x = (if x > 100 then x else x*2) + 1

    That apostrophe doesn’t have any special meaning in Haskell’s syntax. It’s a valid character to use in a function name. We usually use ’ to either denote a strict version of a function (one that isn’t lazy) or a slightly modified version of a function or a variable.

    Note:

    Functions can’t begin with uppercase lettersWhen a function doesn’t take any parameters, we usually say it’s a definition (or a name) conanO'Brien = "It's a-me, Conan O'Brien!"

    2.3 An Introduction to lists

    In Haskell, lists are a homogenous data structure. It stores several elements of the same type.We can use the let keyword to define a name right in GHCI. ghci> let lostNumbers = [4,8,15,16,23,42] ghci> lostNumbers [4,8,15,16,23,42]

    Lists are denoted by square brackets and the values in the lists are separated by commas. Speaking of characters, strings are just lists of characters. “hello” is just syntactic sugar for [‘h’,‘e’,‘l’,‘l’,‘o’] . Because strings are lists, we can use list functions on them, which is really handy.

    2.3.1 ++ , : , !! operator

    A common task is putting two lists together. This is done by using the ++ operator.

    ghci> [1,2,3,4] ++ [9,10,11,12] [1,2,3,4,9,10,11,12] ghci> "hello" ++ " " ++ "world" "hello world" ghci> ['w','o'] ++ ['o','t'] "woot"

    ++ operator can take a long time when dealing with long lists. Putting something at the beginning of a list using the : operator (also called the cons operator) is instantaneous.

    ghci> 'A':" SMALL CAT" "A SMALL CAT" ghci> 5:[1,2,3,4,5] [5,1,2,3,4,5]

    Notice how : takes a number and a list of numbers or a character and a list of characters, whereas ++ takes two lists.

    If you want to get an element out of a list by index, use !! . The indices start at 0.

    ghci> "Steve Buscemi" !! 6 'B' ghci> [9.4,33.2,96.2,11.2,23.25] !! 1 33.2

    2.3.2 Some properties about List

    Lists can also contain lists. They can also contain lists that contain lists that contain lists … The lists within a list can be of different lengths but they can’t be of different types. Lists can be compared if the stuff they contain can be compared. When using < , <= , > and >= to compare lists, they are compared in lexicographical order.

    2.3.4 Some basic functions that operate on lists

    head takes a list and returns its head. The head of a list is basically its first element. tail takes a list and returns its tail. In other words, it chops off a list’s head. last takes a list and returns its last element. init takes a list and returns everything except its last element.

    length takes a list and returns its length.

    null checks if a list is empty. If it is, it returns True , otherwise it returns False . Use this function instead of xs == [] (if you have a list called xs )

    reverse reverses a list. -

    take takes number and a list. It extracts that manyelements from the beginning of the list.

    drop works in a similar way, only it drops the number of elements from the beginning of a list.

    maximum takes a list of stuff that can be put in some kind of order and returns the biggest element.

    minimum returns the smallest.

    sum takes a list ofnumbers and returns their sum.

    product takes a list of numbers and returns their product.

    elem takes a thing and a list ofthings and tells us if that thing is an element ofthe list. It’s usually called as an infix function because it’s easier to read that way.

    2.4 Ranges

    Ranges are a way of making lists that are arithmetic sequences of elements that can be enumerated. Numbers and characters can be enumerated. What if we want all even numbers between 1 and 20? Or every third number between 1 and 20? To make a list with all the numbers from 20 to 1, you can’t just do [20…1] , you have to do [20,19…1] . Note: Not to use floating point numbers in ranges! They are not completely precise! You can also use ranges to make infinite lists by just not specifying an upper limit.

    2.4.1 Some functions that produces infinite lists

    cycle takes a list and cycles it into an infinite list. If you just try to display the result, it will go on forever so you have to slice it off somewhere.repeat takes an element and produces an infinite list of just that element. Note: It’s simpler to just use the replicate function if you want some number of the same element in a list.

    2.5 list comprehension

    List comprehensions are very similar to set comprehensions. The list comprehension we could use is [x*2 | x <- [1…10]] . x is drawn from [1…10] and for every element in [1…10] (which we have bound to x ), we get that element, only doubled. We can add a condition (or a predicate) to that comprehension. Predicates go after the binding parts and are separated from them by a comma. Note that weeding out lists by predicates is also called filtering .

    Not only can we have multiple predicates in list comprehensions (an element must satisfy all the predicates to be included in the resulting list), we can also draw from several lists. When drawing from several lists, comprehensions produce all combinations of the given lists and then join them by the output function we supply.

    ghci> [ x*y | x <- [2,5,10], y <- [8,10,11], x*y > 50] [55,80,100,110]

    Our own version of length

    length' xs = sum [1 | _ <- xs]

    _ means that we don’t care what we’ll draw from the list anyway so instead of writing a variable name that we’ll never use, we just write _. This function replaces every element of a list with 1 and then sums that up. This means that the resulting sum will be the length of our list.

    Note: because strings are lists, we can use list comprehensions to process and produce strings.

    Define function that takes a string and removes everything except uppercase letters from it.

    removeNonUppercase st = [ c | c <- st, c `elem` ['A'..'Z']]

    2.6 Tuples

    2.6.1 Tuples and List

    Tuples are like lists — they are a way to store several values into a single value. However, there are a few fundamental differences. Tuples are used when you know exactly how many values you want to combine and its type depends on how many components it has and the types of the components. They are denoted with parentheses and their components are separated by commas. Another key difference is that they don’t have to be homogenous. e.g. [(1,2), (8,11), (4,5)]Use tuples when you know in advance how many components some piece of data should have. Tuples are much more rigid because each different size of tuple is its own type, so you can’t write a general function to append an element to a tuple.Tuples can be compared with each other if their components can be compared

    2.6.2 Two useful functions that operate on pairs(tuple with two components):

    fst takes a pair and returns its first component. snd takes a pair and returns its second component.

    2.6.3 A cool function that produces a list of pairs: zip

    zip It takes two lists and then zips them together into one list by joining the matching elements into pairs.

    ghci> zip [1,2,3,4,5] [5,5,5,5,5] [(1,5),(2,5),(3,5),(4,5),(5,5)] ghci> zip [1 .. 5] ["one", "two", "three", "four", "five"] [(1,"one"),(2,"two"),(3,"three"),(4,"four"),(5,"five")]

    Notice that because pairs can have different types in them, zip can take two lists that contain different types and zip them up. If the lengths of the lists don’t match, the longer list simply gets cut offto match the length of the shorter one. Because Haskell is lazy, we can zip finite lists with infinite lists:

    ghci> zip [1..] ["apple", "orange", "cherry", "mango"] [(1,"apple"),(2,"orange"),(3,"cherry"),(4,"mango")]

    3 Types and Typeclasses

    3.1 Believe the type

    Everything in Haskell has a type, so the compiler can reason quite a lot about your program before compiling it.(Haskell has type inference.) A type is a kind of label that every expression has.

    3.1.1 use GHCI to examine the types of some expressions

    The :t command which, followed by any valid expression, tells us its type. :t on an expression prints out the expression followed by :: and its type. :: is read as “has type of”.

    3.1.2 The type of functions

    Functions also have types. When writing our own functions, we can choose to give them an explicit type declaration. Here’s how it looks like with a type declaration.

    removeNonUppercase :: [Char] -> [Char] removeNonUppercase st = [ c | c <- st, c `elem` ['A'..'Z']]

    The [Char] type is synonymous with String so it’s clearer ifwe write removeNonUppercase : String -> String. Write out the type of a function that takes several parameters.

    addThree :: Int -> Int -> Int -> Int addThree x y z = x + y + z

    The parameters are separated with -> and there’s no special distinction between the parameters and the return type. The return type is the last item in the declaration and the parameters are the first three. If you want to give your function a type declaration but are unsure as to what it should be, you can always just write the function without it and then check it with :t.

    3.1.3 An overview ofsome common types

    Int stands for integer. Int is bounded, which means that it has a minimum and a maximum value.Integer stands for integer. The main difference is that it’s not bounded so it can be used to represent really really big numbers.Float is a real floating point with single precision.Double is a real floating point with double the precisionBool is a boolean type. It can have only two values: True and False.Tuples are types but they are dependent on their length as well as the types oftheir components

    3.2 Type variables

    ghci> :t head head :: [a] -> a

    a is a type variable, which means that a can be of any type. Functions that have type variables are called polymorphic functions. Although type variables can have names longer than one character, we usually give them names of a, b, c, d…

    3.3 Typeclasses

    A typeclass is a sort of interface that defines some behavior. If a type is a part of a typeclass, that means that it supports and implements the behavior the typeclass describes.

    ghci> :t (==) (==) :: (Eq a) => a -> a -> Bool

    Everything before the => symbol is called a class constraint.

    3.3.1 Some basic type classes

    Eq is used for types that support equality testing. The functions its members implement are == and /=.Ord is for types that have an ordering. All the types we covered so far except for functions are part of Ord. Ord covers all the standard comparing functions such as >, <, >= and <=. The compare function takes two Ord members ofthe same type and returns an ordering. Ordering is a type that can be GT, LT or EQ. ghci> "Abrakadabra" < "Zebra" True ghci> "Abrakadabra" `compare` "Zebra" LT ghci> 5 >= 2 True ghci> 5 `compare` 3 GT Members of Show can be presented as strings. All types covered so far except for functions are a part of Show. The most used function that deals with the Show typeclass is show. It takes a value whose type is a member of Show and presents it to us as a string.Read is sort of the opposite typeclass ofShow. The read function takes a string and returns a type which is a member of Read. ghci> read "True" || False True ghci> read "8.2" + 3.8 12.0

    We can use explicit type annotations. Type annotations are a way of explicitly saying what the type ofan expression should be. We do that by adding :: at the end of the expression and then specifying a type.

    ghci> read "5" :: Int 5 ghci> read "5" :: Float 5.0 Enum members are sequentially ordered types—they can be enumerated. The main advantage ofthe Enum typeclass is that we can use its types in list ranges. They also have defined successors and predecesors, which you can get with the succ and pred functions. Types in this class: (), Bool, Char, Ordering, Int, Integer, Float and Double.Bounded members have an upper and a lower bound. All tuples are also part ofBounded if the components are also in it.Num is a numeric typeclass. Its members have the property of being able to act like numbers. To join Num, a type must already be friends with Show and Eq.Integral is also a numeric typeclass. Integral includes only integral (whole) numbers. In this typeclass are Int and Integer.Floating includes only floating point numbers, so Float and Double. A very useful function for dealing with numbers is fromIntegral. It takes an integral number and turns it into a more general number. That’s useful when you want integral and floating point types to work together nicely.

    4 Syntax in Functions

    4.1 Pattern matching

    Pattern matching consists ofspecifying patterns to which some data should conform and then checking to see if it does and deconstructing the data according to those patterns. When defining functions, you can define separate function bodies for different patterns. The patterns will be checked from top to bottom and when it conforms to a pattern, the corresponding function body will be used.

    Define a factorial function recursively factorial :: (Integral a) => a -> a factorial 0 = 1 factorial n = n * factorial (n - 1)

    When making patterns, we should always include a catch-all pattern so that our program doesn’t crash if we get some unexpected input. Pattern matching can also be used on tuples.

    addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a) addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)

    You can also pattern match in list comprehensions. Should a pattern match fail, it will just move on to the next element.

    Lists themselves can also be used in pattern matching.
    Processed: 0.012, SQL: 8