Haskell

From Ggl's wiki

Jump to: navigation, search

Contents

What is Haskell

Features

Haskell is a pure functional programming language. Its main features are:

  • purity (no side-effects)
  • high-order functions
  • lazy evaluation: a expression is evaluated only when needed
  • strongly typed with type inference
  • polymorphism
  • currying
  • pattern matching
  • list comprehension
  • monads
  • type classes
  • layout parsing: code blocks can be delimited by the layout (tab spaces)

Functions

composition: rotate = flipV . flipH forward composition: rotate = flipH >.> flipV

lambda: \x y = y*y

partial application:

sum x y = x + y
sum 1 -> 1 + y

Lists

(head:tail) commonly written (x:xs)

List comprehension: [y | y <- xs, y <= x]

Useful functions: map, filter, foldr1, foldr, zip, zipWith

Short Examples

Some examples could be find there.

module Main where
import System.IO
import Char

Define some custom types. Simple types:

type Person = String
type Book = String

List [] of (,) tuples (here pairs):

type LoanDB = [(Person, Book)]

Define the function that build the exemple database list:

exampleDB :: LoanDB
exampleDB = [("Alice", "Tintin"),
             ("Anna", "Little Women"),
             ("Alice", "Asterix"),
             ("Rory", "Tintin")]

Retrieve books from LoadDB db that were loaned by Person borrower. It uses a list comprehension where a book is in the returned list, if the person in the tuple (person, book) from db is the borrower:

books :: LoanDB -> Person -> [Book]
books db borrower
    = [ book | (person, book) <- db, person==borrower]

Prepend the tuple (borrower, book) to db. ++ is the concatenation operator.

makeLoan :: LoanDB -> Person -> Book -> LoanDB
makeLoan db borrower book
   = [(borrower, book)] ++ db

Remove (borrower, book) from db. Actually, it uses a list comprehension to return a list where all but (borrower, book) tuples are.

returnLoan :: LoanDB -> Person -> Book -> LoanDB
returnLoan db borrower book
   = [(bwer, bk) | (bwer, bk) <- db, (bwer, bk) /= (borrower, book)]

Return a list of people who borrowed book:

borrowers :: LoanDB -> Book -> [Person]
borrowers db book
    = [borrower | (borrower, bk) <- db, bk==book]

Tell if book was borrowed. Return True is there is at least one borrower e.g. length(borrowers db book) > 0.

borrowed :: LoanDB -> Book -> Bool
borrowed db book
    = if length(borrowers db book) > 0
        then True
        else False

How many books did borrower borrow:

numBorrowed :: LoanDB -> Person -> Int
numBorrowed db borrower
    = length(books db borrower)

In a list (x:xs), x is the head and xs the tail. It's often used in list recursion.

Pattern matching:

sum :: [Int] -> Int
sum [] = 0
sum (x:xs) = x + sum xs
firstDigit st
    = case (digits st) of
        [] ->'\O'
        (x:_) -> X


Guards:

comparison x y 
       | x < y = "The first is less"
       | x > y = "The second is less"
       | otherwise = "They are equal"

Insertion in an ordered list:

ins :: Int -> [Int] -> [Int]
ins x [] = [x]
ins x (y:ys) =
  | x <= y = x:(y:ys)
  | otherwise = y : ins x ys

Quicksort:

qSort :: [Int] -> [Int]
qSort [] = []
qSort (x:xs)
   = qSort [y | y <- xs, y <= x] ++ [x] ++ qSort [y | y <- xs, y > x]

Sample Programs

Fastcgi configuration

With nginx, add this to your /etc/nginx/sites-enabled/<vhost> (might be in /etc/nginx/sites-enabled/default):

server {
         listen 80;
         [...]
         location /hs {
            include fastcgi_params;
            fastcgi_pass localhost:9000;
            fastcgi_index index.hs;
         }
         [...]
}

You should also have the file /etc/nginx/fastcgi_params (by default in debian package) with these values:

fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length; 

fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;

fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;

fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;

Then when you go to http://localhost/hs/index.hs the fastcgi handler will connect to the fastcgi process on tcp port 9000. The fastcgi process may listen to a unix socket for better performance.

Feeds fetcher

module Main where

import Control.Applicative ((<*>))
import System.Environment ( getArgs )
import Data.List ( sortBy )
import Data.Ord ( comparing )
import Data.Maybe ( fromJust )
import Network.URI ( parseURI )
import Network.HTTP.Base
import Network.HTTP ( Request, simpleHTTP, rspBody )
import Text.Feed.Types ( Feed, Item )
import Text.Feed.Import
import Text.Feed.Query
import System.Locale ( defaultTimeLocale )
import System.Time ( formatCalendarTime )
import System.Time.Parse ( parseCalendarTime )

-- |A configuration has the format: "uri [name]"
parseConfigLine :: String -> (String, String)
parseConfigLine l = (uri l, name l)
    where
        uri = takeWhile (/= ' ')
        name = takeWhile (/= ']') . tail . dropWhile (/= '[')

parseConfigString :: String -> [(String, String)]
parseConfigString = map parseConfigLine . lines

parseConfigPath :: FilePath -> IO [(String, String)]
parseConfigPath = fmap parseConfigString . readFile

getFeed :: (String, String) -> IO Feed
getFeed (uri, name) =
    simpleHTTP (Request uri' GET [] "") >>= \(Right x) ->
        return . fromJust . parseFeedString $ rspBody x
    where
        Just uri' = parseURI uri

getFeeds :: [(String, String)] -> IO [Feed]
getFeeds = mapM getFeed

itemTitle = fromJust . getItemTitle
itemDate = fromJust . getItemDate
itemLink = fromJust . getItemLink

-- "Wed, 9 Dec 2009 16:49:31 +0000"
dateFormat = "%a, %d %b %Y %H:%M:%S %Z"
convertDate = fromJust . parseCalendarTime defaultTimeLocale dateFormat
-- "2009/12/09 16:49 UTC"
showDate = formatCalendarTime defaultTimeLocale "%Y/%m/%d %R %Z"

itemFormat "date" item  = Just $  showDate $ convertDate $ itemDate item
itemFormat "uri" item   = Just $ concat ["\tURI: ", itemLink item]
itemFormat _ item = Nothing

showItem :: (Item, String) -> [String] -> String
showItem (i, ft) fmt = concat $ (["[" ++ ft ++ "- "]) ++
        (map (\x -> case itemFormat x i of
                Nothing -> ""
                Just f -> f) fmt) ++ ["] "] ++ [itemTitle i]

main :: IO ()
main = do
    feeds <- getArgs >>= parseConfigPath . head >>= getFeeds
    let items_sorted = sortBy (\(i1, _) (i2, _) ->
            comparing (convertDate . itemDate) i1 i2) (items feeds)
    putStrLn $ unlines $ map (flip showItem ["date"]) $ items_sorted
    where
        items = concatMap (\x ->
                map (\y -> (y, getFeedTitle x)) (getFeedItems x))

Del.icio.us example

A simple example of haskell delicious module usage to retrieve the last 10 links.

import Network.Delicious

user = User { userName = "user", userPass = "password" }
posts = withUser user (getAll Nothing)
posts' <- runDM user posts
take 10 . map postDesc $ posts'
Personal tools