Линзы - комбинаторная манипуляция данными Александр...
TRANSCRIPT
Линзы
Комбинаторная манипуляция данными
Александр Гранин
1
О себе
● Haskell, C++, немного C#
● Организатор сообщества LambdaNsk
● Доклады:
o Haskell. DevDay@2GIS
o ФП вчера и сегодня. TechTalks@NSU
o Функционально-декларативный дизайн на С++
o Идиоматичный ФП-код. LambdaNsk
2
● Очень кратко о ФП
● Линзы в Haskell
● Сценарии с использованием линз
● Линзы в других языках
План доклада
3
Функциональное программирование
● Функции высших порядков
● Лямбды
● Замыкания
● Иммутабельность
● Рекурсия
4
Функциональное программирование
● Функции высших порядков
● Лямбды
● Замыкания
● Иммутабельность
● Рекурсия
● Математические абстракции
5
data Credentials = Credentials {
credentialsLogin :: String,
credentialsPassword :: String }
Credentials
Login
Password
6
Простой алгебраический тип данных
updatePassword credentials newPassword = let
(Credentials login _) = credentials
in (Credentials login newPassword)
updatePassword2 credentials newPassword =
credentials { credentialsPassword = newPassword }
Изменение значения простого АТД
7
data Credentials = Credentials {
credentialsLogin :: String,
credentialsPassword :: String }
Credentials
Login
Password
data Credentials = Credentials {
credentialsLogin :: String,
credentialsPassword :: String }
data Person = Person {
personName :: String,
personSurname :: String,
personCredentials :: Credentials }
8
Более сложный АТД
Person
Credentials
Login
Password
Credentials
Login
Password
Credentials
Name
Surname
updatePersonPassword person newPass = let
credentials = personCredentials person
newCreds = credentials { credentialsPassword = newPass }
newPerson = person { personCredentials = newCreds }
in newPerson
9
Изменение более сложного АТД
data Credentials = Credentials {
credentialsLogin :: String,
credentialsPassword :: String }
data Person = Person {
personName :: String,
personSurname :: String,
personCredentials :: Credentials }
data Subscriber = Subscriber {
subscriberPerson :: Person,
subscriberTariffId :: Int }
Person
Subscriber
Credentials
Login
Password
10
А что, если?..
updateSubscriberPassword subscriber newPass = let
person = subscriberPerson subscriber
credentials = personCredentials person
newCreds = credentials { credentialsPassword = newPass }
newPerson = person { personCredentials = newCreds }
newSubscriber = subscriber { subscriberPerson = newPerson }
in newSubscriber
11
updateSubscriberPassword subscriber newPass = let
person = subscriberPerson subscriber
credentials = personCredentials person
newCreds = credentials { credentialsPassword = newPass }
newPerson = person { personCredentials = newCreds }
newSubscriber = subscriber { subscriberPerson = newPerson }
in newSubscriber
Getters
Setters
12
person = subscriberPerson subscriber
newSubscriber = subscriber { subscriberPerson = person }
credentials = personCredentials person
newPerson = person { personCredentials = credentials }
password = credentialsPassword credentials
newCreds = credentials { credentialsPassword = password }
b = getB a
a = setB b a
13
getPerson subscriber = subscriberPerson subscriber
setPerson subscriber person = subscriber { subscriberPerson = person }
getCredentials person = personCredentials person
setCredentials person creds = person { personCredentials = creds }
getPassword creds = credentialsPassword creds
setPassword creds pass = creds { credentialsPassword = pass }
b = getB a
a = setB b a
14
getter :: A -> B
setter :: A -> B -> A
Линза = Геттер + Сеттер
lens :: (getter :: A -> B,
setter :: A -> B -> A)
passwordLens :: (Credentials -> String,
Credentials -> String -> Credentials)
passwordLens = (getPass, setPass)
where
getPass creds = credentialsPassword creds
setPass creds newPass = creds {credentialsPassword = newPass}
15
Простые операторы view и setview (getter, _) parent = getter parent
set (_, setter) parent value = setter parent value
> let myCredentials = Credentials "login" "pass"
> view passwordLens myCredentials
"pass"
> set passwordLens myCredentials "qwerty"
Credentials "login" "qwerty"
16
credentialsLens = (getCreds, setCreds)
where
getCreds person = personCredentials person
setCreds person newCreds = person {personCredentials = newCreds}
personLens = (getPerson, setPerson)
where
getPerson subscr = subscriberPerson subscr
setPerson subscr newPers = subscr {subscriberPerson = newPers}
Еще линзы
17
(getChild, setChild) . (getValue, setValue) = (getter, setter)
where
getter parent = getValue (getChild parent)
setter parent value = let
oldChild = getChild parent
newChild = setValue oldChild value
in setChild parent newChild
Линза . линза = линза
18Композиция!
myCredentials = Credentials "login" "pass"
mePerson = Person "Alex" "Granin" myCredentials
> view (credentialsLens . passwordLens) mePerson
“pass”
> set (credentialsLens . passwordLens) mePerson “qwerty”
Person "Alex" "Granin" (Credentials "login" "qwerty")
Линза . линза = линза
19
data ContactType = VK | FB | Twitter | Email
data Skill = Cpp | CSharp | Haskell | Java | FSharp | Python
data Person = Person {
_name :: String,
_surname :: String,
_contacts :: [(ContactType, String)],
_skills :: [Skill]
}
АТД Person
Person
Name
Surname
Contacts
Skills
21
type Presentation = String
data Attendee =
Speaker {
_person :: Person,
_visited :: [Presentation],
_presentationTitle :: String }
| Attendee {
_person :: Person,
_visited :: [Presentation] }
АТД Attendee
Attendee
Person
Name
Surname
Contacts
Skills
22
-- Template Haskell rocks:
makeLenses ''Person
makeLenses ''Attendee
Создание линз
_name name Person ===> String
_surname surname Person ===> String
_contacts contacts Person ===> Contacts
_person person Attendee ===> Person
_visited visited Attendee ===> [Presentation]
23
pete = Person "Pete" "Howard" [(FB, "pete")] [Java, Python]
peteAttendee = Attendee pete []
> _name (_person peteAttendee) -- Manual
“Pete”
> view (person.name) peteAttendee -- With lenses
“Pete”
> peteAttendee ^. (person.name) -- The same
“Pete”
Оператор view (^.)
24
> peteAttendee ^. person.skills
[Java,Python]
> itoList (peteAttendee ^. person.skills)
[(0,Java),(1,Python)]
> (itoList (peteAttendee ^. person.skills)) ^.. ix 1
[(1,Python)]
Операторы itoList, (^..) и ix
25
setAttendeeName att n = case att of
Speaker pers visit theme -> Speaker (setName pers) visit theme
Attendee pers visit -> Attendee (setName pers) visit
where
setName (Person _ s cts skls) = Person n s cts skls
setAttendeeName’ att n = set (person.name) "Jack" att
Оператор set
26
addContact att contact = case att of
Speaker pers visit theme -> Speaker (appendContact pers) visit theme
Attendee pers visit -> Attendee (appendContact pers) visit
where
appendContact (Person n s cts skls) = Person n s (contact : cts) skls
addContact' att contact =
over (person . contacts) (insert contact) att
Оператор over
27
#%%= **= //= //~ <<%@= <<.|.= <<^~ %= ...
#%%~ **~ <-= <#%= <<%@~ <<.|.~ <<||= %@= .=
#%= *= <-~ <#%~ <<%~ <<.~ <<||~ %@~ .>
#%~ *~ <. <#= <<&&= <<//= <<~ %~ .@=
#= += <.&.= <#~ <<&&~ <<//~ <>= & .@~
#~ +~ <.&.~ <%= <<**= <</>= <>~ &&= .|.=
%%= -= <.= <%@= <<**~ <</>~ <?= &&~ .|.~
%%@= -~ <.> <%@~ <<*= <<<.>= <?~ &~ .~
%%@~ .&.= <.>= <%~ <<*~ <<<.>~ <^= <.|.= <<-=
%%~ .&.~ <.>~ <&&= <<+= <<</>= <^^= <.|.~ <<-~
Тысячи их...
Zen28
data Conference = Conference {
_attendees :: [Attendee],
_currentPresentation :: Presentation,
_conferenceTweets ::
[(Presentation, Person, String)]
}
АТД Conference
Attendee
Person
Conference
currentPresentation
conferenceTweets
30
pete = Person "Pete" "Howard" [facebook "pete"] [Java]
jakob = Person "Jakob" "Brown" [twitter "jab"] [Java, CSharp]
lana = Person "Lana" "Dell" [] [FSharp]
alex = Person "Alexander" "Granin" [vk "graninas"] [Haskell]
guru = Person "Real" "Guru" [] [Cpp, Java, CSharp, Haskell]
confAttendees = [ attendee pete, attendee jakob, attendee lana,
speaker alex "Lenses",
speaker guru “Multilanguage projects”]
conference = Conference confAttendees "" []
31
conferenceScenario :: State Conference ()
conferenceScenario = do
alex `tweeted` "Rush hour, sorry. #dev2dev"
beginPresentation "Multilanguage projects"
setListeners [pete, jakob, lana]
jakob `tweeted` "Great talk! #dev2dev"
lana `tweeted` "So technical. #dev2dev"
pete `tweeted` "#MLP 222 coming soon."
32
Сценарий “Конференция”
tweeted :: Person -> Msg -> State Conference ()
tweeted pers msg = if "#dev2dev" `isInfixOf` msg
then writeTweet pers msg
else return ()
writeTweet :: Person -> Msg -> State Conference ()
writeTweet pers msg = do
presentationName <- use currentPresentation
conferenceTweets %= insert (presentationName, pers, msg)
33
Сценарий “Твит”
beginPresentation :: Presentation -> State Conference ()
beginPresentation title = currentPresentation .= title
34
Сценарий “Начать доклад”
setListeners :: [Person] -> State Conference ()
setListeners persons = setVisited (attendees . onlyListeners persons)
onlyListeners :: Persons -> Traversal' [Attendee] Attendee
onlyListeners persons
= traversed . filtered (\att -> (att ^. person) `elem` persons)
35
Сценарий “Задать слушателей”
conferenceScenario :: State Conference ()
conferenceScenario = do
alex `tweeted` "Rush hour, sorry. #conf"
beginPresentation "Multilanguage projects"
setListeners [pete, jakob, lana]
jakob `tweeted` "Great talk! #conf"
lana `tweeted` "So technical. #conf"
pete `tweeted` "#MLP 222 coming soon."
36
Сценарий “Конференция”
Линзы в других языках
● Haskell
○ Edward Kmett, “Lenses, Folds, and Traversals”
● Scalaz
○ Edward Kmett, “Lenses: A Functional Imperative”
● JavaScript
● Clojure
● C++ (!)
● … your language
37
beginPresentation :: Presentation -> State Conference ()
beginPresentation title = currentPresentation .= title
listeners :: [Person] -> State Conference ()
listeners perss = setVisited (attendees . onlyListeners perss)
where
setVisited atts = do
title <- use currentPresentation
atts.visited %= (insert title)
onlyListeners :: Persons -> Traversal' [Attendee] Attendee
onlyListeners ls = traversed . filtered (\att -> att ^. person `elem` ls)
39