joko Mark Seemannin katamorfismi

Katamorfismi jommallekummalle on sen taitoksen yleistys. Katamorfismi mahdollistaa toiminnot, joita ei ole saatavilla Foldin kautta.

tämä artikkeli on osa katamorfismeja käsittelevää artikkelisarjaa. Katamorfismi on universaalinen abstraktio, joka kuvaa, miten tietorakenne sulatetaan mahdollisesti kompaktimmaksi arvoksi.

tässä artikkelissa esitetään katamorfismi jommallekummalle (tunnetaan myös nimellä tulos), sekä miten se voidaan tunnistaa. Tämän artikkelin alussa esitellään C#: n katamorfismi esimerkein. Artikkelin loppuosassa kuvataan, miten katamorfismi voidaan päätellä. Tämä osa artikkelista esittelee työtäni Haskellissa. Lukijat, jotka eivät ole tyytyväisiä Haskelliin, voivat vain lukea ensimmäisen osan ja pitää artikkelin loppuosaa valinnaisena liitteenä.

joko on datasäiliö, joka mallintaa kaksi toisensa poissulkevaa tulosta. Sitä käytetään usein palauttaa arvoja, jotka voivat olla joko oikea (Oikea), tai virheellinen (vasen). Staattisesti tyypitetyssä funktionaalisessa ohjelmoinnissa, jossa suositaan kokonaisfunktioita, joko tarjoaa järkevämmän, järkevämmän tavan mallintaa menestystä ja virhetuloksia kuin heittämällä poikkeuksia.

C# katamorfismi #

tässä artikkelissa käytetään kirkon koodausta joko. Katamorfismi jommallekummalle on Match – menetelmä:

T Match<T>(Func<L, T> onLeft, Func<R, T> onRight);

tähän artikkeliin asti kaikki aiemmat katamorfismit ovat olleet alkuarvosta ja funktiosta muodostettuja pareja. Jompikumpi katamorfismi on yleistys, koska se on funktiopari. Yksi funktio käsittelee tapausta, jossa on vasen arvo, ja toinen funktio käsittelee tapausta, jossa on oikea arvo. Molempien funktioiden on palautettava sama, yhdistävä tyyppi, joka on usein merkkijono tai jotain vastaavaa, joka voidaan näyttää käyttöliittymässä:

> IEither<TimeSpan, int> e = new Left<TimeSpan, int>(TimeSpan.FromMinutes(3));> e.Match(ts => ts.ToString(), i => i.ToString())"00:03:00"> IEither<TimeSpan, int> e = new Right<TimeSpan, int>(42);> e.Match(ts => ts.ToString(), i => i.ToString())"42"

näet usein esimerkkejä, kuten edellä, joka kääntyy sekä vasemmalle ja oikealle tapauksissa merkkijonoja tai jotain, joka voidaan esittää yhdistävä vastaus tyyppi, mutta tämä ei ole millään tavalla vaaditaan. Jos voit keksiä yhdistävän tyypin, voit muuntaa molemmat tapaukset kyseiseen tyyppiin:

> IEither<Guid, string> e = new Left<Guid, string>(Guid.NewGuid());> e.Match(g => g.ToString().Count(c => 'a' <= c), s => s.Length)12> IEither<Guid, string> e = new Right<Guid, string>("foo");> e.Match(g => g.ToString().Count(c => 'a' <= c), s => s.Length)3

kahdessa yllä olevassa esimerkissä käytetään kahta eri funktiota, jotka molemmat pienentävät vastaavasti Guid ja string arvot luvuksi. Funktio, joka muuttaa Guid arvot luvuksi, laskee, kuinka monta Heksadesimaalilukua on suurempi tai yhtä suuri kuin A (10). Toinen funktio yksinkertaisesti palauttaa pituuden string, jos se on olemassa. Tuossa esimerkissä ei ole juurikaan järkeä, mutta Match – metodi ei siitä välitä.

käytännön käytössä jompaakumpaa käytetään usein virheiden käsittelyyn. Jommankumman kirkon koodausta käsittelevässä artikkelissa on esimerkki.

joko F-Algebra #

kuten edellisessä artikkelissa, käytän Fix ja cata kuten Bartosz Milewskin erinomaisessa artikkelissa F-Algebroja selitettiin.

vaikka rekursiivisille tietorakenteille käytetään enimmäkseen F-Algebroja ja kiinteitä pisteitä, voidaan F-Algebra määritellä myös ei-rekursiiviselle tietorakenteelle. Näitte jo esimerkin siitä artikkeleissa Boolen katamorfismista ja ehkä katamorfismista. Ero esim. ehkä-arvojen ja jommankumman välillä on se, että molemmissa tapauksissa jommallakummalla on arvo. Voit mallintaa tämän arvoksi Functor sekä kantoaaltotyypin että kahden tyypin argumentin avulla tiedoille, jotka joko voivat sisältää:

data EitherF l r c = LeftF l | RightF r deriving (Show, Eq, Read) instance Functor (EitherF l r) where fmap _ (LeftF l) = LeftF l fmap _ (RightF r) = RightF r

valitsin nimeksi ”tietotyypit” l (vasen) ja r (oikea) sekä kantotyypin c (oikea). Kuten myös BoolF ja MaybeF, Functor instanssi jättää karttafunktion huomiotta, koska kantajatyyppi puuttuu sekä LeftF tapauksesta että RightF tapauksesta. Kuten Functor esiintymät BoolF ja MaybeF, näyttäisi siltä, että mitään ei tapahdu, mutta tyyppitasolla tämä on edelleen käännös EitherF l r cEitherF l r c1. Ei kummoinen funktio, ehkä, mutta ehdottomasti endofunktor.

kuten myös ehkä-ja Listakatamorfismeja päätellessä, Haskell ei ole kovin tyytyväinen instanssien määrittelyyn tyypille, kuten Fix (EitherF l r). Ongelman ratkaisemiseksi voit ottaa käyttöön newtype – kääreen:

newtype EitherFix l r = EitherFix { unEitherFix :: Fix (EitherF l r) } deriving (Show, Eq, Read)

voit määritellä Functor, Applicative, Monad, jne. instanssit tämän tyyppinen turvautumatta mitään funky GHC laajennuksia. Muista, että loppujen lopuksi, tarkoitus kaikki tämä koodi on vain selvittää, mitä katamorfismi näyttää. Tätä koodia ei ole tarkoitettu varsinaiseen käyttöön.

auttava funktiopari helpottaa EitherFix arvojen määrittelyä:

leftF :: l -> EitherFix l rleftF = EitherFix . Fix . LeftF rightF :: r -> EitherFix l rrightF = EitherFix . Fix . RightF

näillä funktioilla voidaan luoda EitherFix arvoja:

Prelude Data.UUID Data.UUID.V4 Fix Either> leftF <$> nextRandomEitherFix {unEitherFix = Fix (LeftF e65378c2-0d6e-47e0-8bcb-7cc29d185fad)}Prelude Data.UUID Data.UUID.V4 Fix Either> rightF "foo"EitherFix {unEitherFix = Fix (RightF "foo")}

muuta katamorfismin tunnistamiseen ei tarvita.

Haskellin katamorfismi #

tässä vaiheessa on kaksi kolmesta F-algebran alkiosta. Sinulla on endofunctori (EitherF l r) ja objekti c, mutta sinun täytyy silti löytää morfismi EitherF l r c -> c. Huomaa, että algebra, joka sinun täytyy löytää, on funktio, joka pelkistää functorin kantotyypilleen c, ei ”tietotyypit” l ja r. Tähän tottuminen vie aikaa, mutta niin katamorfismit toimivat. Tämä ei kuitenkaan tarkoita, etteikö l ja r saisi ohittaa, kuten nähdään.

kuten edellisissä artikkeleissa, aloita kirjoittamalla funktio, joka muuttuu katamorfismiksi, perustuen cata:

eitherF = cata alg . unEitherFix where alg (LeftF l) = undefined alg (RightF r) = undefined

vaikka tämä koostaa, sen undefined toteutukset, se ei ilmeisesti tee mitään hyödyllistä. Huomaan kuitenkin, että se auttaa minua ajattelemaan. Miten LeftF tapauksesta voi palauttaa arvon, jonka tyyppi on c? Voit siirtää argumentin funktiolle eitherF :

eitherF fl = cata alg . unEitherFix where alg (LeftF l) = fl l alg (RightF r) = undefined

vaikka voisit teknisesti siirtää c – tyypin argumentin eitherF: lle ja palauttaa tämän arvon LeftF tapauksesta, se tarkoittaisi, että jättäisit l: n arvon huomiotta. Tämä olisi virheellinen, joten sen sijaan tehdään argumentista funktio ja kutsutaan sitä merkillä l. Samoin RightF tapausta voi käsitellä samalla tavalla:

eitherF :: (l -> c) -> (r -> c) -> EitherFix l r -> ceitherF fl fr = cata alg . unEitherFix where alg (LeftF l) = fl l alg (RightF r) = fr r

tämä toimii. Koska cata on tyyppi Functor f => (f a -> a) -> Fix f -> a, se tarkoittaa, että alg on tyyppi f a -> a. EitherF tapauksessa kääntäjä päättelee, että alg funktiolla on tyyppi EitherF l r c -> c, joka on juuri sitä mitä tarvitset!

voit nyt nähdä, mihin kantotyyppi c on tarkoitettu. Se on tyyppi, jonka algebra poistaa, ja siten tyyppi, jonka katamorfismi palauttaa.

tämä on siis katamorfismi jommallekummalle. Kuten on ollut johdonmukainen toistaiseksi, se on pari, mutta nyt tehty kaksi toimintoa. Kuten olet toistuvasti nähnyt, tämä ei ole ainoa mahdollinen katamorfismi, sillä voit esimerkiksi triviaalisesti kääntää argumentit arvoon eitherF.

olen valinnut tässä esitetyn esityksen, koska se on isomorfinen either funktion kanssa Haskellin sisäänrakennetusta Data.Either-moduulista, joka kutsuu funktiota ”Either – tyypin Tapausanalyysiksi”. Molemmat näistä funktioista (eitherF ja either) vastaavat edellä mainittua C# Match – menetelmää.

Basis #

voit toteuttaa useimmat muut hyödylliset toiminnot eitherF. Tässä on Bifunctor instanssi:

instance Bifunctor EitherFix where bimap f s = eitherF (leftF . f) (rightF . s)

tästä instanssista Functor instanssi seuraa triviaalisesti:

instance Functor (EitherFix l) where fmap = second

päälle Functor voit lisätä Applicative:

instance Applicative (EitherFix l) where pure = rightF f <*> x = eitherF leftF (<$> x) f

ilmoitus, että <*> toteutus on samankaltainen kuin <*> toteutus MaybeFix. Sama pätee Monad esiintymään:

instance Monad (EitherFix l) where x >>= f = eitherF leftF f x

ei vain ole EitherFix Foldable, se on Bifoldable:

instance Bifoldable EitherFix where bifoldMap = eitherF

huomaa mielenkiintoista, että bifoldMap on sama kuin eitherF.

Bifoldable instanssi mahdollistaa Foldable instanssin triviaalisen toteutuksen:

instance Foldable (EitherFix l) where foldMap = bifoldMap mempty

mempty esiintyminen voi tuntua hämmentävältä, sillä bifoldMap (tai eitherF; ne ovat identtisiä) ottaa argumentteina kaksi funktiota. Onko mempty funktio?

Kyllä, mempty voi olla funktio. Tässä se on. On olemassa Monoid instanssi mille tahansa funktiolle a -> m, missä m on Monoid instanssi, ja mempty on kyseisen monoidin identiteetti. Se on tässä käytössä oleva esimerkki.

aivan kuten EitherFix on Bifoldable, on myös Bitraversable:

instance Bitraversable EitherFix where bitraverse fl fr = eitherF (fmap leftF . fl) (fmap rightF . fr)

Traversable instanssin voi toteuttaa mukavasti Bitraversable instanssin pohjalta:

instance Traversable (EitherFix l) where sequenceA = bisequenceA . first pure

lopuksi voidaan toteuttaa muunnoksia standardin Either tyypin kanssa, käyttäen ana duaalina cata:

toEither :: EitherFix l r -> Either l rtoEither = eitherF Left Right fromEither :: Either a b -> EitherFix a bfromEither = EitherFix . ana coalg where coalg (Left l) = LeftF l coalg (Right r) = RightF r

tämä osoittaa, että EitherFix on isomorfinen Either, mikä taas osoittaa, että eitherF ja either ovat ekvivalentteja.

suhteet #

tässä sarjassa on nähty useita esimerkkejä katamorfismeista rakenteissa, joissa ei ole poimuja, katamorfismeista, jotka yhtyvät poimuihin, ja nyt katamorfismista, joka on taitosta yleisempi. Sarjan johdanto sisälsi tämän kaavion:

Katamorfismit ja taitteet sarjoina, eri summatyypeille.

tämä osoittaa, että Boolen arvoilla ja Peanon luvuilla on katamorfismeja, mutta ei poimuja, kun taas Mayben ja Listin kohdalla taitos ja katamorfismi ovat samat. Kummallekaan taitos on kuitenkin katamorfismin erikoistapaus. Kansi joko ”teeskentelee”, että vasemmalla puolella ei ole olemassa. Sen sijaan vasen arvo tulkitaan puuttuvaksi oikeaksi arvoksi. Näin, jotta taita joko arvoja, sinun täytyy antaa ”fallback” arvo, jota käytetään, jos jompikumpi arvo ei ole oikea arvo:

Prelude Fix Either> e = rightF LT :: EitherFix Integer OrderingPrelude Fix Either> foldr (const . show) "" e"LT"Prelude Fix Either> e = leftF 42 :: EitherFix Integer OrderingPrelude Fix Either> foldr (const . show) "" e""

edellä mainitun kaltaisessa GHCi-istunnossa voit luoda kaksi samantyyppistä arvoa. Oikea tapaus on Ordering arvo, kun taas vasen tapaus on Integer arvo.

kun foldr, vasemmanpuoleiseen koteloon ei pääse käsiksi. Vaikka voit käyttää ja muuttaa oikeaa Ordering arvoa, numero 42 yksinkertaisesti ohitetaan taitoksen aikana. Sen sijaan palautetaan oletusarvo "".

vertaa tätä katamorfismiin, joka voi käyttää molempia tapauksia:

Prelude Fix Either> e = rightF LT :: EitherFix Integer OrderingPrelude Fix Either> eitherF show show e"LT"Prelude Fix Either> e = leftF 42 :: EitherFix Integer OrderingPrelude Fix Either> eitherF show show e"42"

tällaisessa istunnossa luodaan samat arvot uudelleen, mutta käyttämällä katamorfismia eitherF voit nyt käyttää ja muuttaa sekä vasenta että oikeaa tapausta. Toisin sanoen, katamorfismin avulla voit suorittaa operaatioita, jotka eivät ole mahdollisia taitoksen kanssa.

on kuitenkin mielenkiintoista huomata, että vaikka taitos on katamorfismin erikoistuminen, bifold on identtinen katamorfismin kanssa.

Yhteenveto #

jommankumman katamorfismi on funktiopari. Yksi funktio muuttaa vasemman tapauksen, kun taas toinen funktio muuttaa oikean tapauksen. Kummallekin arvolle käytetään vain yhtä näistä funktioista.

kun alun perin törmäsin katamorfismin käsitteeseen, minun oli vaikea erottaa katamorfismia ja foldia toisistaan. Ongelmani oli, luulen, että tutorials törmäsin enimmäkseen käytetty linkitetty luettelot osoittaa, miten, siinä tapauksessa, kansi on katamorfismi. Kävi kuitenkin ilmi, että näin ei aina ole. Katamorfismi on yleinen abstraktio. Taitos taas tuntuu minusta liittyvän lähinnä kokoelmiin.

tässä artikkelissa näit ensimmäisen esimerkin katamorfismista, joka voi tehdä muutakin kuin taitoksen. Kummallekin taitos on vain katamorfismin erikoistapaus. Näit kuitenkin myös, miten katamorfismi oli identtinen bifoldin kanssa. Näin ollen ei ole vielä täysin selvää, miten nämä käsitteet liittyvät. Siksi seuraavassa artikkelissa, saat esimerkin säiliö, jossa ei ole bifold, ja jossa katamorfismi On, todellakin, yleistys taitoksen.

Seuraava: Puukatamorfismi.

Vastaa

Sähköpostiosoitettasi ei julkaista.