Haskell
Александр Гранин
graninas@gmail.com
О чем доклад?
● Часть 1. Ликбез по ФП и Haskell
○ Функциональное программирование
○ Язык Haskell. Применимость языка
○ Язык Haskell. Основы
○ Инструменты разработчика
● Часть 2. Haskell: We need to go deeper
○ Обработка данных: NgnTrafficParser
○ XML и Domain Specific Languages
○ Parsec, HaXml, GenXml
● Часть 3. Немного хардкора
○ Монады!!!
Программирование:
● Императивное
● Объектно-ориентированное
● Функциональное
● Логическое
● Декларативное
(println "Hello World!")
printfn "Hello World!"
io:format("Hello, World!~n").
putStrLn "Hello World!"
println("Hello World!")
Функциональное
программирование
?
Функциональное
программирование
● Все есть функция
● ...
● ...
● ...
● ...
● ...
Функциональное
программирование
● Все есть функция
● Рекурсия
● ...
● ...
● ...
● ...
Функциональное
программирование
● Все есть функция
● Рекурсия
● Функции высших
порядков
● ...
● ...
● ...
Функциональное
программирование
a := 1
a := 2
DO NOT CHANGE!
The immutability is with you
● Все есть функция
● Рекурсия
● Функции высших
порядков
● Иммутабельность
данных
● ...
● ...
Функциональное
программирование
● Все есть функция
● Рекурсия
● Функции высших
порядков
● Иммутабельность
данных
● Чистые функции
● ...
Функциональное
программирование
● Все есть функция
● Рекурсия
● Функции высших
порядков
● Иммутабельность
данных
● Чистые функции
● Нет побочных
эффектов
Язык Haskell
?
Язык Haskell
Haskell - это...
● Чистый функциональный,
● строго статически типизированный,
● кроссплатформенный,
● компилируемый язык
● общего назначения
● с ленивой семантикой
● и автоматическим выводом типов.
Краткая история Haskell
● В честь Хаскеля Карри
● GHC: Саймон Пейтон Джонс
● Семейство языков ML
● Прародитель - Miranda
● 1990 год - Haskell 1.0
● 1998 год - Haskell '98
● 2009 год - Haskell 2010
«Доказательство —
это программа, а
доказываемая
формула — это тип
программы»
(c)MiranLipovača
Где Haskell плох:
● Системное программирование
● Real-time системы
● GUI
Где Haskell хорош:
● Надежность кода
● Надежность кода
● Domain Specific Languages
Abstract Syntax
Tree
Code base
Где Haskell хорош:
● Надежность кода
● Domain Specific Languages
● Безопасный параллелизм
Где Haskell хорош:
● Надежность кода
● Domain Specific Languages
● Безопасный параллелизм
● Обработка данных
Телекомы:
TrafficParser
Где Haskell хорош:
● Надежность кода
● Domain Specific Languages
● Безопасный параллелизм
● Обработка данных
● Парсинг
Где Haskell хорош:
Name
Description
Body Type
XML
DSL
Haskell - не мэйнстрим?..
Причины непопулярности
● Pascal, C, C++, Java, C# - в вузах
● Традиционное мировоззрение очень сильно
● Заработать на Haskell очень трудно
● Мифы и стереотипы
"Избегать успеха любой ценой."
Саймон Пейтон Джонс
Язык Haskell: Основы
fib n = case n of
0 -> 0
1 -> 1
n -> fib (n-1) + fib (n-2)
fact n = case n of
0 -> 1
n -> fact (n-1) * n
Функции.
case - аналог switch
Функции,
аргументы -
lowerCamelCase
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
fact 0 = 1
fact n = fact (n-1) * n
Сопоставление с образцом
Функции,
аргументы -
lowerCamelCase
fib :: Word -> Word
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
fact :: Word -> Word
fact 0 = 1
fact n = fact (n-1) * n
Система типов
Функции,
аргументы -
lowerCamelCase
Типы, классы
типов, модули -
UpperCamelCase
fact :: Word -> Word
replicate :: Int -> a -> [a]
map :: (a -> b) -> [a] -> [b]
(+) :: Int -> Int -> Int
add :: Int -> Int -> Int
Система типов
Списки. Кортежи
phonebook :: [(String, String)]
phonebook =
[ ("Bob", "953 777-44-45")
, ("Fred", "919 33-555-11")
, ("Alice", "383 11111111")
, ("Jane", "964 4000004") ]
Списки. Кортежи
type PhoneBook = [(String, String)]
validate :: PhoneBook -> PhoneBook
validate book= map addPrefix book
addPrefix :: (String, String) -> (String, String)
addPrefix (name, phone) = (name, "+7 " ++ phone)
Лямбды - анонимные функции
nums = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0]
oddNumbers = filter (n -> odd n) nums
oddNumbers2 = filter odd nums
// C#:
int[] nums = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var oddNumbers = nums.Where(n => n % 2 == 1);
Генераторы списков
(c) Miran Lipovača http://learnyouahaskell.com/
Cоздать список:
1. На множестве от 1 до 100.
2. Только нечетные.
3. Делящиеся на 3 без остатка.
4. Делящиеся на 7 без остатка.
list1 = [21, 63]
list2 = [x | x <- [1..100], odd x,
x `mod` 3 == 0,
x `mod` 7 == 0]
1. x - от 1 до 100, y - от 100 до 200.
2. x - четные, y - нечетные.
3. x + y < 200.
4. |x - y| > 190.
Cоздать список [(x, y)]:
list = [(x, y) | x <- [1..100], even x,
y <- [100..200], odd y,
x + y < 200,
abs (x - y) > 190]
Алгебраические типы данных
data Bool = True
| False
Тип Конструкторы
Алгебраические типы данных
data STree
= Tip
| Branch
{
leftBranch :: STree,
value :: Int,
rightBranch :: STree
}
Сопоставление с образцом
height :: STree -> Int
height Tip = 0
height (Branch lt _ rt) = 1 + max (height lt) (height rt)
5
3 7
1 4
Data.Maybe - аналог Nullable
phonebook :: [(String, String)]
...
printPhone :: String -> [(String, String)] -> IO ()
printPhone name book = case lookup name book of
Nothing -> putStrLn "Name not exist."
Just phone -> putStrLn phone
data Maybe a = Nothing
| Just a
lookup :: Eq a => a -> [(a, b)] -> Maybe b
Инструменты разработки
Haskell Platform
● Компилятор GHC
● Интерпретатор GHCi
● Базовые библиотеки
● Пакетный менеджер
Cabal
● Документирование -
Haddock
● MinGW (Windows) http://www.haskell.org/platform/
Glasgow Haskell Compiler
Компиляция:
ghc --make FizzBuzz.hs
ghc --make -O2 FizzBuzz.hs
ghc --make -O2 -threaded FizzBuzz.hs
Выполнение:
runghc FizzBuzz.hs
REPL - GHCi
(GHC interpreter)
Read
EvalPrint
Loop
REPL - GHCi
(GHC interpreter)
> [1..10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
> (3 + 30)
33
> fizzBuzz
<interactive>:1:1:
No instance for (Show (Int -> String))
arising from a use of 'print'
Possible fix:
add an instance declaration for (Show (Int -> String))
In a stmt of an interactive GHCi command: print it
Hackage - репозиторий библиотек
Библиотеки и программы
● xmonad - тайловый оконный менеджер
● Parsec - комбинаторные парсеры
● HaXmL, HXT - обработка XML
● darcs - система контроля версий
● Yesod - RESTful веб-фреймворк
● QuickCheck - тестирование кода
● House, Kinetic - операционные системы
● Всевозможные эффективные коллекции
● OpenGL, OpenAL, OpenCL биндинги
● ... несколько игр и многое другое
IDE
EclipseFP
Leksah
SublimeHaskell
Want
more?
NGN Traffic Parser
|R200|99999|333333|CR,CS,AM|1|1|3022|222222|333333|||...
|R200|44455|012345|CR,CS,AM|1|1|3022|555555|111111|||...
|R200|45678|543210|CR,CS,AM|1|1|3022|444444|555555|||...
|R200|88888|222222|CR,CS,AM|1|1|3022|666666|777777|||...
file1.txt file2.txt .........
Billing
CMD,
DTS,
SQL
NGN Traffic Parser
Billing
Merge Files
Process data
MS SQL DTS
АТД: Predicate
data Predicate = NotInList [ByteString]
| InList [ByteString]
| Like [ByteString]
| LengthLess Int
type PredicateMap = [(FieldIndex, Predicate)]
predicates =
[ (7, NotInList (map C.pack ["3022", "3012"]))
, (8, Like [C.pack "44", C.pack "45"])
, (9, LengthLess 7) ]
NGN Traffic Parser
checkPredicate :: Predicate -> C.ByteString -> Bool
checkPredicate (NotInList l) str = (not . elem str) l
checkPredicate (InList l) str = elem str l
checkPredicate (LengthLess n) str = (length str) < n
Алгебраический тип данных,
Сопоставление с образцом
replaceSymbols :: String -> String
replaceSymbols s = map (replaceChar '|' ' ')
((map (replaceChar ' ' '*')
(refieldDoubles s)))
Ver. 2.0
ByteString
Ver. 1.0
String
XML и Domain Specific Languages
<?xml version="1.0" encoding="utf-8" ?>
- <PolicySet xmlns="urn:oasis:names:tc:xacml:3.0:core:schema:wd-
PolicySetId="PolicSet-04b0613533354df196715b032388325c" Vers
PolicyCombiningAlgId="urn:oasis:names:tc:xacml:1.0:policy-combin
permit-overrides">
<Description>All active policices</Description>
<Target />
- <Policy PolicyId="a25099f1-d6fd-412e-b827-65fd39f799e1" Versio
<Description>Match file(s) content</Description>
<Target />
Политики безопасности
XML
Тестирование политики
Алгоритмы тестирования:
Безопасность обеспечена?
Нет
Исправляем
дыры
Да
ОК, запускаем!!
Policy.xml
Policy.xml
XML?!. Сложно! Хочу DSL!
Алгоритмы тестирования:
Безопасность обеспечена?
Нет
Исправляем
дыры
Да
ОК, запускаем!!
MyPolicy.dsl
Пример DSL
efm := ExactFileMatching 2 ".test2.db" 0
cat2 : "CAT2" = Category [] NoMatch
cat1 := Category [cat2] NoMatch
cat3 := Category [] NoMatch
cat0 := TopCategory [cat1, cat3] NoMatch
rule := Rule Permit [] | n_of 1 (Evaluate [efm]) [cat0]
policy := Policy DenyOverrides [Audit Deny Medium] [rule]
policySet := PolicySet PermitOverrides [] [policy]
Архитектура DSL <-> XML
Policy.xml
Policy.dsl
Policy AST
HaXmlGenXml
Custom writerParsec
DSL AST
Translator
DSL: AST на ADT
data DslTokenName = DslTokenName String
data DslTokenBody = DslGuardedBody
{ dtbDslAdt :: DslAdt
, dtbDslExpression :: DslExpression }
| DslNonGuardedBody { dtbDslAdt :: DslAdt }
data DslToken = EmptyDslToken
| DslToken
{ dtDslTokenType :: DslTokenType
, dtDslTokenName :: DslTokenName
, dtDslTokenDescr :: DslTokenDescription
, dtDslTokenBody :: DslTokenBody }
Parsec: парсинг DSL
dslToken :: GenParser Char st DslToken
dslToken = do
tName <- dslTokenName
tDescr <- dslTokenDescription
(tBody, tType) <- dslTokenBody
return (DslToken tType tName tDescr tBody)
dslTokenBody :: GenParser Char st
(DslTokenBody, DslTokenType)
dslTokenBody = spaces >> (
try dslGuardedTokenBody
<|> try dslNonGuardedTokenBody
<?> "dslTokenBody")
NonGuardedBody
GuardedBody
Name
Description
Body Type
DslToken
DslTokenBody
Связь с БНФ
lower ::= 'a' | 'b' | ... | 'z'
rest ::= (letters | numbers | '_') + rest | ''
identifier ::= (lower | '_') + rest
tokenName ::= identifier
tokenBody ::= guardedBody | nonGuardedBody
token ::= (tokenType, tokenName, tokenDescr, tokenBody)
identifier :: GenParser Char st String
identifier = do
c <- (lower <|> char '_')
rest <- many (alphaNum <|> char '_')
return (c : rest)
HaXml: парсинг XML
toCondition :: Content i -> Maybe Condition
toCondition e = let
exprCond = head . filterNonTextContent $ children e
expr = fromJust $ recognizeStructure expRecognizers
exprCond
in Just (Condition expr)
-- ............ Здесь много кода
parsePolicy :: String -> PolicySet
parsePolicy content = let
(Document _ _ root _) = xmlParse "error.log" content
in toPolicySet (CElem root noPos)
GenXml: генерация XML
writeDteValue :: DataTypeExt -> Xml Elem
writeDteValue (DteString s) = xtext s
writeDteValue (DteBoolean b) = xtext (map toLower (show b))
writeDteValue (DteInteger i) = xtext (show i)
writeAttributeValue :: AttributeValue -> Xml Elem
writeAttributeValue (AttributeValue dataType val) = let
attrValAttributes = xattr dataTypeN dataType
attrValVals = writeDteValue val
in xelem attributeValueN (attrValAttributes <#> attrValVals)
А теперь - хардкор!!!
lookup :: Eq a => a -> [(a, b)] -> Maybe b
class Eq a where
(==), (/=) :: a -> a -> Bool
instance Eq Char where
c1 == c2 = ... -- compare chars
c1 /= c2 = not (c1 == c2)
instance Eq Int where
i1 == i2 = ... -- compare ints
i1 /= i2 = not (i1 == i2)
Классы типов -
"интерфейсы" на стероидах
data Predicate = NotInList [ByteString]
| InList [ByteString]
| Like [ByteString]
| LengthLess Int
deriving (Eq, Show, Read)
Классы типов -
"интерфейсы" на стероидах
Монада IO:
Безопасный ввод-выод
let rndGen1 = mkStdGen 100
let (val1, rndGen2) = random rndGen1
let (val2, rndGen3) = random rndGen2
Отправной пример:
Random generator
Стратегия связывания, IO
getName :: IO ()
getName = do
putStrLn "What is your name?"
yourName <- getLine
putStr "Hello, "
putStrLn (yourName ++ "!")
let world0 = getWorld
let (val1, world1) = ioAction1 world0
let (val2, world2) = ioAction2 world1
do-нотация
getName :: IO ()
getName = do
putStrLn "What is your name?"
yourName <- getLine
putStr "Hello, "
putStrLn (yourName ++ "!")
getName' =
putStrLn "What is your name?"
>> getLine
>>= yourName -> putStr "Hello, "
>> putStrLn (yourName ++ "!")
Возвращаемое значение
getName :: IO String
getName = do
putStrLn "What is your name?"
yourName <- getLine
putStr "Hello, "
putStrLn (yourName ++ "!")
return yourName
return :: a -> m a
a :: String
m :: IO
Монада State
newtype State s a = State
{
runState :: s -> (a, s)
}
Монада State
myFunc :: State Int Int
myFunc = do
val <- get
put (val - 8)
get
getNumber = evalState myFunc 50
main = print getNumber
Стратегия связывания, State
let state1 = evalState 50
let (val1, state2) = get state1
let ((), state3) = put (val1 - 8) state2
let (val2, state4) = get state3
Monad transformers?
Зачем это надо?
Монада 2Монада 1
Monad transformers?
Зачем это надо?
Монада 2
Монада 1
State + IO
data GS = GS { worldMap :: [Location]
, currentLocation :: Location
, welded :: Bool
, bucketFull :: Bool }
deriving (Show)
newtype GameState a = GameState
{ runGameState :: StateT GS IO a }
State + IO
run :: GameState Result
run = do
t <- get
-- read a command from the user
io . putStr $ "> "
io . hFlush $ stdout
line <- io getLine
result <- case parseCommand line of
Nothing -> write "Invalid command!" >> continue
Just cmd -> do
...........
Named + IO
-- Int-named IO calculations:
intNamedFunc :: NamedT Int IO Int
intNamedFunc = do
name <- getName
return name
testIntNamed :: IO ()
testIntNamed = do
name <- evalNamedT intNamedFunc 1
print name -- You got output: 1
Спасибо за внимание!
Haskell
Александр Гранин
graninas@gmail.com
Haskell
Scala
Clojure
...
Want more?
PLUG
IN!!!
FUNction
It's all Haskell is about
Lambda The Gathering
Lambda Lifter
http://bit.ly/17CyYFj
Haskell Links
http://hackage.haskell.org/packages/hackage.html
Hackage - репозиторий библиотек
Haskell Platform
● Компилятор GHC
● Интерпретатор GHCi
● Базовые библиотеки
● Пакетный менеджер
Cabal
● Документирование -
Haddock
● MinGW (Windows) http://www.haskell.org/platform/
http://habrahabr.
ru/company/selectel/blog/13585
8/
Success Stories
Success Stories
Want
more?
http://www.haskell.org/haskellwiki/Haskell_in_industry
Класс типов Monad
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
Монада Named -
именованные вычисления
data Named n a = Named
{ runNamed :: (n -> a) }
instance Monad (Named n) where
return x = Named (_ -> x)
x >>= f = Named (name -> let
a = runNamed x name
in runNamed (f a) name)

Haskell