{- ghci command Start ghci: prompt>ghci -- or -- prompt> ghci intro.hs Prelude>:load intro.hs -- load the definitions in intro.hs Prelude>:r -- reload the previous file. Prelude> :set +t -- asks for more type information. -} module Main where import List import Test.QuickCheck l = [1,2,3] r = foldl (\accumulator i -> i + accumulator) 0 l {- We can type expressions directly into ghci: (5+3) - 2 if 5>3 then "Harry" else "Hermione" 5 == 4 -} -- Overview by Type -- Base Types -- Bool true = True false = False ifExpression = if true then 10 else 13 -- Integers anInt = 2 anIntExpression = 2 * 4 -- Strings (note, String = [Char]) aString :: String -- "::" means "has type" aString = "Ron Weasley" -- Real numbers aFloat = 2.2 -- Simple compound types -- Tuples aTuple = (4,5,"Griffendor") -- Lists nilList :: [a] -- Lowercase types are type variables. nilList = [] -- polymorphic type anIntList = 1 : [2,3,4] -- Infix cons notation (:) -- Note, ML uses :: for cons and : for "has type". -- Records data Person = Person {firstName :: String, lastName :: String} deriving Show hg = Person {firstName = "Hermione", lastName = "Granger"} -- Patterns and Declarations -- Patterns can be used in place of variables -- ::= | | | -- Value declarations -- myTuple = ("Flitwick", "Snape") (x,y) = myTuple myList = [1,2,3,4] z:zs = myList -- Let allows us to introduce local declarations localDecl = let (x,y) = (2,"Snape") in x * 4 -- Functions and Pattern Matching anonymousFunction = \x -> x + 1 -- Like Lisp lambda, function (...) in Javascript -- Declaration form: -- = -- = -- = -- Single-branch function defined using tuple pattern as argument. f (x,y) = x + y -- Multiple-branch function defined using list pattern as argument. myLength [] = 0 myLength (x:xs) = 1 + myLength xs -- Map function on Lists myMap f [] = [] myMap f (x:xs) = f x : myMap f xs applyMap = myMap (\x -> x + 1) [1,2,3] -- More functions on lists. append([], ys) = ys append(x:xs, ys) = x : append(xs, ys) myReverse [] = [] myReverse (x:xs) = (reverse xs) ++ [x] -- How efficient is reverse? -- This pattern of writing a related function with an extra "accumulator" -- is very common in functional programming. accumReverse xs = let rev ( [], z ) = z rev ( y:ys, z ) = rev( ys, y:z ) in rev( xs, [] ) -- List Comprehensions myData = [1,2,3,4,5,6,7] twiceData = [2 * x | x <- myData] twiceEvenData = [2*x| x <- myData, x `mod` 2 == 0] -- Datatype declarations -- Type Names and data constructors start with capital letters. data Color = Red | Yellow | Blue -- Elements of type Color are Red, Yellow, and Blue -- Data constructors can take arguments data Atom = Atom String | Number Int deriving Show atom1 = Atom "oxygen" atom2 = Number 8 -- Data declarations can be recursive. data List = Nil | Cons (Atom,List) list1 = Nil list2 = Cons (atom1, list1) -- Data declarations can be parameterized by type variables data Tree a = Leaf a | Node (a, Tree a, Tree a) deriving Show aTree = Node(4, Node(3, Leaf 1, Leaf 2), Node(5, Leaf 6, Leaf 7)) -- Functions over datatypes use pattern matching over data constructors to access values: tsum (Leaf n) = n tsum (Node(n,t1,t2)) = n + tsum(t1) + tsum(t2) -- Example: Evaluating Expressions -- Define datatype of expressions: data Exp = Var String | Const Int | Plus (Exp, Exp) deriving Show -- exampleExp is (x + 3) + y exampleExp = Plus(Plus(Var "x", Const 3), Var "y") -- We can also use a case expression to deconstruct values of a datatype: -- Indentation matters in branches of a case in Haskell. -- All branches must start at the same column. exampleCase = case exampleExp of Var n -> 0 Const n -> 0 Plus(e1,e2) -> 2 -- Suppose we want to write an evaluation function to simplify our expressions exampleExp2 = Plus(Const 3, Const 2) -- Const 5 exampleExp3 = Plus(Var "x", Plus(Const 2, Const 3)) -- Plus (Var "x", Const 5) -- Definition of evaluation function: ev ( Var s) = Var s ev ( Const n ) = Const n ev ( Plus ( e1,e2 ) ) = case ev e1 of Var s -> Plus( Var s, ev e2) Const n -> case ev e2 of Var s -> Plus(Const n, Var s) Const m -> Const (n+m) Plus(e3,e4) -> Plus ( Const n, Plus ( e3, e4 )) Plus(e3, e4) -> Plus( Plus ( e3, e4 ), ev e2) -- Laziness -- Haskell is lazy language. -- Functions and data constructors don't evaulate their arguments until they need them. -- Programmers can write their own control-flow operators: cond :: Bool -> a -> a -> a cond True t e = t cond False t e = e -- Why can't programmers write such operations in eager languages? -- Using laziness -- "Harry" `isSubString` "Harry Potter" -- (Putting ticks around a function makes it an infix operator.) -- (Putting parens around an infix operator makes it a normal function.) isSubString :: String -> String -> Bool x `isSubString` s = or [ x `isPrefixOf` t | t <- suffixes s ] suffixes :: String -> [String] -- All suffixes of s suffixes [] = [""] suffixes (x:xs) = (x:xs) : suffixes xs -- Another example -- (or bs) returns True if any of the bs is True myOr :: [Bool] -> Bool myOr [] = False myOr (b:bs) = b || myOr bs -- Infinte data structures -- Laziness allows us to use conceptually infinte data structures. Because values -- are only computed when they are needed, the infinite structure is not materialized -- (unless the code is looping infinitely..) -- The haskell notation [n..] generates the infinite sequence of natural numbers starting at n naturals = [0..] -- The function 'take n list' returns the first n elements of the list 'list', so -- first10 is the list [0,1,2,3,4,5,6,7,8,9] first10 = take 10 naturals -- primesN is the infite list of primes. -- It is defined using list comprehension notation. primesN :: [Int] primesN = let sieve(p:xs) = p : sieve [ x | x <- xs, x `mod` p > 0] in sieve [2..] -- A lazy paradigm -- Generate all solutions (a potentially enormous tree) -- Walk the tree to find the solution you want nextMove :: Board -> Move nextMove b = selectMove allMoves where allMoves = allMovesFrom b data Board = Board -- Put real definition here data Move = Move -- Put real definition here selectMove moves = undefined -- undefined is Haskell function that runs for ever, has type a allMovesFrom b = undefined -- replace with real definition -- Testing -- It is good to write tests as you write code. -- Printf-style debugging doesn't work in Haskell. Why? -- You can use ghci as an interactive shell to test code as you write it. -- The QuickCheck library helps with testing by generating random test data -- automatically from the type of the function being tested. -- Consider the function 'eReverse' that reverses a list using an accumulator: eReverse xs = let rev ( [], z ) = z rev ( y:ys, z ) = rev( ys, y:z ) in rev( xs, [] ) -- Define a non-polymorphic type at which to test the function type TS = [Int] -- Define property we wish to test: prop_RevRev :: TS -> Bool prop_RevRev l = eReverse (eReverse l) == l -- Run in ghci test_result= quickCheck prop_RevRev -- We can fix the function and rerun property checker. -- Note that quickCheck is a Haskell ** library **. There is no special interaction -- with ghc or ghci -- This file can also be compiled, using ghc --make AlgolHaskell.hs main = putStrLn "Code snippets from lecture on Algol and Haskell. Use ghci AlgolAndHaskell.hs to play with snippets."