Линзы - комбинаторная манипуляция данными (dev2dev)
TRANSCRIPT
О себе
● Haskell, C++, немного C#● Организатор сообщества LambdaNsk● Доклады:
o Haskell. DevDay@2GISo ФП вчера и сегодня. TechTalks@NSUo Функционально-декларативный дизайн на С++o Идиоматичный ФП-код. LambdaNsk
2
● Очень кратко о ФП
● Линзы в Haskell
● Сценарии с использованием линз
● Линзы в других языках
План доклада
3
Функциональное программирование
● Функции высших порядков● Лямбды● Замыкания● Иммутабельность● Рекурсия
4
Функциональное программирование
● Функции высших порядков● Лямбды● Замыкания● Иммутабельность● Рекурсия● Математические абстракции
5
data Credentials = Credentials { credentialsLogin :: String, credentialsPassword :: String }
CredentialsLogin
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 }
CredentialsLogin
Password
data Credentials = Credentials { credentialsLogin :: String, credentialsPassword :: String }
data Person = Person { personName :: String, personSurname :: String, personCredentials :: Credentials }
8
Более сложный АТД
Person
CredentialsLogin
Password
CredentialsLogin
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
CredentialsLogin
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 subscribernewSubscriber = subscriber { subscriberPerson = person }
credentials = personCredentials personnewPerson = person { personCredentials = credentials }
password = credentialsPassword credentialsnewCreds = credentials { credentialsPassword = password }
b = getB aa = setB b a
13
getPerson subscriber = subscriberPerson subscribersetPerson subscriber person = subscriber { subscriberPerson = person }
getCredentials person = personCredentials personsetCredentials person creds = person { personCredentials = creds }
getPassword creds = credentialsPassword credssetPassword creds pass = creds { credentialsPassword = pass }
b = getB aa = 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 parentset (_, 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
Линзы в Haskell: библиотека lensАвтор - Edward Kmett
20
data ContactType = VK | FB | Twitter | Emaildata Skill = Cpp | CSharp | Haskell | Java | FSharp | Python
data Person = Person { _name :: String, _surname :: String, _contacts :: [(ContactType, String)], _skills :: [Skill]}
АТД Person
PersonNameSurname
Contacts
Skills
21
type Presentation = Stringdata Attendee =
Speaker { _person :: Person, _visited :: [Presentation],
_presentationTitle :: String } | Attendee { _person :: Person, _visited :: [Presentation] }
АТД Attendee
Attendee
PersonNameSurname
Contacts
Skills
22
-- Template Haskell rocks:makeLenses ''PersonmakeLenses ''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
Сценарии с использованием линз
29
Для тех, кому еще мало
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] AttendeeonlyListeners 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] AttendeeonlyListeners ls = traversed . filtered (\att -> att ^. person `elem` ls)
39