GSoC 2019: Iċċekkjar tal-grafiċi għal transformers bipartiteness u monad

Is-sajf li għadda ħadt sehem fih Google tas-Sajf tal-Kodiċi - programm għall-istudenti minn Google. Kull sena, l-organizzaturi jagħżlu diversi proġetti Open Source, inkluż minn organizzazzjonijiet magħrufa bħal Boost.org и Il-Fondazzjoni Linux. Google tistieden studenti minn madwar id-dinja biex jaħdmu fuq dawn il-proġetti. 

Bħala parteċipant fil-Google Summer of Code 2019, għamilt proġett fil-librerija Alka mal-organizzazzjoni Haskell.org, li qed tiżviluppa l-lingwa Haskell - waħda mill-lingwi ta 'programmar funzjonali l-aktar famużi. Alga hija librerija li tirrappreżenta tip sigur rappreżentazzjoni għal graffs f'Haskell. Jintuża, pereżempju, fi semantiku — librerija Github li tibni siġar semantiċi, graffs tas-sejħiet u tad-dipendenza bbażati fuq kodiċi u tista’ tqabbelhom. Il-proġett tiegħi kien li żżid rappreżentazzjoni tat-tip sigur għal graffs u algoritmi bipartiti għal dik ir-rappreżentazzjoni. 

F'din il-kariga ser nitkellem dwar l-implimentazzjoni tiegħi ta 'algoritmu għall-iċċekkjar ta' graff għall-bipartiteness f'Haskell. Anke jekk l-algoritmu huwa wieħed mill-aktar bażiċi, l-implimentazzjoni tiegħu sabiħa fi stil funzjonali ħaditli diversi iterazzjonijiet u kienet teħtieġ ħafna xogħol. Bħala riżultat, ikkonċentejt fuq implimentazzjoni bi transformers monad. 

GSoC 2019: Iċċekkjar tal-grafiċi għal transformers bipartiteness u monad

Dwar lili nnifsi

Jisimni Vasily Alferov, jien student tar-raba' sena f'San Pietruburgu HSE. Aktar kmieni fil-blog ktibt dwar il-proġett tiegħi dwar algoritmi parametrizzati и dwar il-vjaġġ lejn ZuriHac. Bħalissa ninsab fuq apprendistat fi Università ta' Bergen fin-Norveġja, fejn qed naħdem fuq approċċi għall-problema Lista Kulur. L-interessi tiegħi jinkludu algoritmi parametrizzati u programmazzjoni funzjonali.

Dwar l-implimentazzjoni tal-algoritmu

Daħla

L-istudenti li qed jipparteċipaw fil-programm huma mħeġġa bil-qawwa biex jagħmlu blog. Huma pprovdewni pjattaforma għall-blog Sajf ta' Haskell. Dan l-artikolu huwa traduzzjoni oġġetti, miktub minni hemmhekk f'Lulju bl-Ingliż, bi prefazju qasir. 

Tista 'tinstab Pull Request bil-kodiċi inkwistjoni hawn.

Tista' taqra dwar ir-riżultati tax-xogħol tiegħi (bl-Ingliż) hawn.

Din il-kariga tassumi li l-qarrej huwa familjari mal-kunċetti bażiċi fl-ipprogrammar funzjonali, għalkemm ser nipprova nfakkar it-termini kollha użati meta jasal iż-żmien.

Iċċekkjar graphs għal bipartiteness 

Algoritmu għall-iċċekkjar ta 'graff għal bipartiteness normalment jingħata f'kors dwar algoritmi bħala wieħed mill-algoritmi tal-graff l-aktar sempliċi. L-idea tiegħu hija sempliċi: l-ewwel aħna b'xi mod inpoġġu vertiċi fis-sehem tax-xellug jew tal-lemin, u meta jinstab tarf konfliġġenti, aħna nasserixxu li l-graff mhix bipartita.

Ftit aktar dettall: l-ewwel inpoġġu xi vertiċi fis-sehem tax-xellug. Ovvjament, il-ġirien kollha ta 'dan il-vertiċi għandhom ikunu fil-lobu tal-lemin. Barra minn hekk, il-ġirien kollha tal-ġirien ta 'dan il-vertiċi għandhom ikunu fil-lobu tax-xellug, eċċ. Aħna nkomplu jassenjaw ishma għal vertiċi sakemm għad hemm vertiċi fil-komponent konness tal-vertiċi li bdejna bih li ma nkunux assenjati l-ġirien. Imbagħad nirrepetu din l-azzjoni għall-komponenti kollha konnessi.

Jekk ikun hemm tarf bejn vertiċi li jaqgħu fl-istess partizzjoni, mhuwiex diffiċli li ssib ċiklu fard fil-graff, li huwa magħruf ħafna (u pjuttost ovvjament) impossibbli f'graff bipartite. Inkella, għandna partizzjoni korretta, li jfisser li l-graff huwa bipartite.

Tipikament, dan l-algoritmu jiġi implimentat bl-użu wisa' l-ewwel tfittxija jew fond l-ewwel tfittxija. F'lingwi imperattivi, it-tfittxija l-ewwel fil-fond normalment tintuża peress li hija kemxejn aktar sempliċi u ma teħtieġx strutturi ta 'dejta addizzjonali. Għażilt ukoll it-tfittxija fil-fond peress li hija aktar tradizzjonali.

Għalhekk, wasalna għall-iskema li ġejja. Aħna jaqsmu l-vertiċi tal-graff bl-użu ta 'tfittxija fil-fond u nassenjaw ishma lilhom, billi nbiddlu n-numru tas-sehem hekk kif nimxu tul it-tarf. Jekk nippruvaw nassenjaw sehem lil vertiċi li diġà għandu sehem assenjat, nistgħu ngħidu b'mod sikur li l-graff mhix bipartita. Fil-mument li l-vertiċi kollha jiġu assenjati sehem u ħaresna lejn it-truf kollha, għandna partizzjoni tajba.

Purità tal-kalkoli

F'Haskell nassumu li l-kalkoli kollha huma nadif. Madankollu, kieku dan kien tassew il-każ, ma jkollna l-ebda mod kif nipprintjaw xejn fuq l-iskrin. Fuq kollox, nadif il-kalkoli huma tant għażżien li m'hemmx wieħed nadif raġunijiet biex tikkalkula xi ħaġa. Il-kalkoli kollha li jseħħu fil-programm huma b'xi mod sfurzati "mhux nadif" monad IO.

Monads huma mod biex tirrappreżenta l-kalkoli effetti f'Haskell. Li jispjega kif jaħdmu huwa lil hinn mill-ambitu ta 'din il-kariga. Deskrizzjoni tajba u ċara tista’ tinqara bl-Ingliż hawn.

Hawnhekk irrid nirrimarka li filwaqt li xi monadi, bħal IO, huma implimentati permezz tal-maġija tal-kompilatur, kważi l-oħrajn kollha huma implimentati f'softwer u l-kalkoli kollha fihom huma puri.

Hemm ħafna effetti u kull wieħed għandu l-monada tiegħu. Din hija teorija qawwija ħafna u sabiħa: il-monadi kollha jimplimentaw l-istess interface. Se nitkellmu dwar it-tliet monadi li ġejjin:

  • Jew ea huwa kalkolu li jirritorna valur tat-tip a jew jitfa' eċċezzjoni tat-tip e. L-imġieba ta 'din il-monada hija simili ħafna għall-immaniġġjar tal-eċċezzjoni f'lingwi imperattivi: żbalji jistgħu jinqabdu jew jgħaddu. Id-differenza ewlenija hija li l-monad hija kompletament loġikament implimentata fil-librerija standard f'Haskell, filwaqt li l-lingwi imperattivi normalment jużaw mekkaniżmi tas-sistema operattiva.
  • L-Istat sa huwa kalkolu li jirritorna valur tat-tip a u għandu aċċess għal stat mutabbli tat-tip s.
  • Forsi a. Il-monade Forsi tesprimi komputazzjoni li tista 'tiġi interrotta fi kwalunkwe ħin billi tirritorna Xejn. Madankollu, se nitkellmu dwar l-implimentazzjoni tal-klassi MonadPlus għat-tip Forsi, li jesprimi l-effett oppost: huwa kalkolu li jista 'jiġi interrott fi kwalunkwe ħin billi jirritorna valur speċifiku.

Implimentazzjoni tal-algoritmu

Għandna żewġ tipi ta' dejta, Graph a u Bigraph ab, l-ewwel wieħed jirrappreżenta graphs b'vertiċi ttikkettjati b'valuri tat-tip a, u t-tieni jirrappreżenta graphs bipartite b'vertiċi tan-naħa tax-xellug ittikkettjati b'valuri tat-tip a u tal-lemin. -vertiċi tal-ġenb ttikkettjati b'valuri tat-tip b.

Dawn mhumiex tipi mil-librerija Alga. Alga m'għandhiex rappreżentazzjoni għal graffs bipartiti mhux diretti. Għamilt it-tipi bħal dan għaċ-ċarezza.

Ikollna bżonn ukoll funzjonijiet helper bil-firem li ġejjin:

-- Список соседей данной вершины.
neighbours :: Ord a => a -> Graph a -> [a]

-- Построить двудольный граф по графу и функции, для каждой вершины
-- выдающей её долю и пометку в новой доле, игнорируя конфликтные рёбра.
toBipartiteWith :: (Ord a, Ord b, Ord c) => (a -> Either b c)
                                         -> Graph a
                                         -> Bigraph b c

-- Список вершин в графе
vertexList :: Ord a => Graph a -> [a]
Сигнатура функции, которую мы будем писать, выглядит так:

type OddCycle a = [a]
detectParts :: Ord a => Graph a -> Either (OddCycle a) (Bigraph a a)

Huwa faċli li wieħed jara li jekk matul it-tfittxija fil-fond l-ewwel sibna tarf konfliġġenti, iċ-ċiklu fard jinsab fuq nett tal-munzell ta 'rikors. Għalhekk, biex nirrestawrawha, għandna bżonn naqtgħu kollox mill-munzell ta 'rikorsi sal-ewwel okkorrenza tal-aħħar vertiċi.

Aħna nimplimentaw tfittxija fil-fond billi nżommu firxa assoċjattiva ta 'numri ta' ishma għal kull vertiċi. Il-munzell ta 'rikorsjoni se jinżamm awtomatikament permezz tal-implimentazzjoni tal-klassi Functor tal-monada li għażilna: ser ikollna bżonn biss li npoġġu l-vertiċi kollha mill-mogħdija fir-riżultat ritornat mill-funzjoni rikorsiva.

L-ewwel idea tiegħi kienet li nuża l-Either monad, li tidher li timplimenta eżattament l-effetti li għandna bżonn. L-ewwel implimentazzjoni li ktibt kienet viċin ħafna għal din l-għażla. Fil-fatt, kelli ħames implimentazzjonijiet differenti f'punt wieħed u eventwalment issetilja fuq ieħor.

L-ewwel, irridu nżommu firxa assoċjattiva ta 'identifikaturi ta' ishma - din hija xi ħaġa dwar l-Istat. It-tieni, jeħtieġ li nkunu kapaċi nieqfu meta jinstab kunflitt. Dan jista 'jkun jew Monad għal Jew, jew MonadPlus għal Forsi. Id-differenza ewlenija hija li Jew jista 'jirritorna valur jekk il-kalkolu ma jkunx twaqqaf, u Forsi jirritorna biss informazzjoni dwar dan f'dan il-każ. Peress li m'għandniex bżonn valur separat għas-suċċess (diġà huwa maħżun fl-Istat), nagħżlu Forsi. U fil-mument meta għandna bżonn ngħaqqdu l-effetti ta 'żewġ monadi, joħorġu transformers monad, li preċiżament jgħaqqdu dawn l-effetti.

Għaliex għażilt tip daqshekk kumpless? Żewġ raġunijiet. L-ewwelnett, l-implimentazzjoni tirriżulta li hija simili ħafna għal imperattiva. It-tieni, irridu nimanipulaw il-valur tar-ritorn f'każ ta 'kunflitt meta nirritornaw lura minn rikorsi biex nirrestawraw il-linja fard, li hija ħafna aktar faċli li tagħmel fil-Monade Forsi.

Għalhekk niksbu din l-implimentazzjoni.

{-# LANGUAGE ExplicitForAll #-}
{-# LANGUAGE ScopedTypeVariables #-}

data Part = LeftPart | RightPart

otherPart :: Part -> Part
otherPart LeftPart  = RightPart
otherPart RightPart = LeftPart

type PartMap a = Map.Map a Part
type OddCycle a = [a]

toEither :: Ord a => PartMap a -> a -> Either a a
toEither m v = case fromJust (v `Map.lookup` m) of
                    LeftPart  -> Left  v
                    RightPart -> Right v

type PartMonad a = MaybeT (State (PartMap a)) [a]

detectParts :: forall a. Ord a => Graph a -> Either (OddCycle a) (Bigraph a a)
detectParts g = case runState (runMaybeT dfs) Map.empty of
                     (Just c, _)  -> Left  $ oddCycle c
                     (Nothing, m) -> Right $ toBipartiteWith (toEither m) g
    where
        inVertex :: Part -> a -> PartMonad a
        inVertex p v = ((:) v) <$> do modify $ Map.insert v p
                                      let q = otherPart p
                                      msum [ onEdge q u | u <- neigbours v g ]

        {-# INLINE onEdge #-}
        onEdge :: Part -> a -> PartMonad a
        onEdge p v = do m <- get
                        case v `Map.lookup` m of
                             Nothing -> inVertex p v
                             Just q  -> do guard (q /= p)
                                           return [v]

        processVertex :: a -> PartMonad a
        processVertex v = do m <- get
                             guard (v `Map.notMember` m)
                             inVertex LeftPart v

        dfs :: PartMonad a
        dfs = msum [ processVertex v | v <- vertexList g ]

        oddCycle :: [a] -> [a]
        oddCycle c = tail (dropWhile ((/=) last c) c)

Il-blokk fejn huwa l-qalba tal-algoritmu. Se nipprova nispjega x'qed jiġri ġewwa fih.

  • inVertex hija l-parti ta 'depth-first search fejn inżuru l-vertiċi għall-ewwel darba. Hawnhekk nassenjaw numru ta 'sehem lill-vertiċi u nħaddmu onEdge fuq il-ġirien kollha. Dan huwa wkoll fejn nirrestawraw il-munzell tas-sejħa: jekk msum irritorna valur, aħna nimbottaw vertex v hemmhekk.
  • onEdge hija l-parti fejn inżuru t-tarf. Huwa msejjaħ darbtejn għal kull tarf. Hawnhekk niċċekkjaw jekk il-vertiċi fuq in-naħa l-oħra nżarux, u nżuruh jekk le. Jekk iżuru, niċċekkjaw jekk it-tarf huwiex konfliġġenti. Jekk hu, nirritornaw il-valur - il-parti ta' fuq nett tal-munzell ta' rikorsi, fejn il-vertiċi l-oħra kollha mbagħad jitqiegħdu mar-ritorn.
  • processVertex jiċċekkja għal kull vertiċi jekk tkunx ġiet miżjura u taħdem inVertex fuqu jekk le.
  • dfs imexxi processVertex fuq il-vertiċi kollha.

Dak kollox.

Storja tal-kelma INLINE

Il-kelma INLINE ma kinitx fl-ewwel implimentazzjoni tal-algoritmu; dehret aktar tard. Meta ppruvajt insib implimentazzjoni aħjar, sibt li l-verżjoni mhux INLINE kienet notevolment aktar bil-mod fuq xi graffs. Meta wieħed iqis li semantikament il-funzjonijiet għandhom jaħdmu l-istess, dan issorprendi ħafna. Saħansitra barrani, fuq magna oħra b'verżjoni differenti ta 'GHC ma kien hemm l-ebda differenza notevoli.

Wara li qattajt ġimgħa naqra l-output tal-GHC Core, stajt nirranġa l-problema b'linja waħda ta 'INLINE espliċita. F'xi punt bejn GHC 8.4.4 u GHC 8.6.5 l-ottimizzatur waqaf jagħmel dan waħdu.

Ma stennejtx li niltaqa 'ma' ħmieġ bħal dan fl-ipprogrammar Haskell. Madankollu, anke llum, l-ottimizzaturi kultant jagħmlu żbalji, u huwa xogħolna li nagħtuhom ħjiel. Pereżempju, hawnhekk nafu li l-funzjoni għandha tkun inlined minħabba li hija inlined fil-verżjoni imperattiva, u din hija raġuni biex tagħti ħjiel lill-kompilatur.

X'ġara wara?

Imbagħad implimentajt l-algoritmu Hopcroft-Karp ma 'monadi oħra, u dak kien it-tmiem tal-programm.

Grazzi għal Google Summer of Code, ksibt esperjenza prattika fl-ipprogrammar funzjonali, li mhux biss għenitni nagħmel apprendistat f'Jane Street is-sajf ta' wara (m'inix ċert kemm hu magħruf dan il-post anke fost l-udjenza infurmata ta' Habr, iżda huwa wieħed tal-ftit fejn tista’ sajf biex tidħol fi programmazzjoni funzjonali), iżda introduċietni wkoll fid-dinja mill-isbaħ tal-applikazzjoni ta’ din il-paradigma fil-prattika, differenti b’mod sinifikanti mill-esperjenza tiegħi fil-lingwi tradizzjonali.

Sors: www.habr.com

Żid kumment