Haskell vs Perl 6: esimesed muljed

Siiani olen Rakudo Perl 6 abil demonstreerinud lihtsaid krüptograafilisi skeeme. See on kood lühike ja armas ning emotikone täis. Kuna Ouroboros on kirjutatud Haskellis ja selle konsensusalgoritmi uurimine on selle väljaande mõte, arvasin, et peaksin vähemalt varbad sellesse kastma.

Kirjutasin ümber oma eelmise mündi, mis libistasin Haskellis diskreetsete logaritmide koodiga. Perl 6 versioon on siin ja Haskelli versioon siin. Need peaksid olema üksteisega võrreldavad. Kui olete Haskeller, tahaksin, et saaksite mu koodi vaadata ja tervitada ka kõige nitpikk-meelsemaid.

Väärib märkimist, et Perl 6 esimene teostus oli kirjutatud Haskellis. Piisavalt kõva hääle korral saate Perl 6-s mõnda funktsionaalset programmeerimist mõjutada.

Järgnevalt on toodud mõned asjad, mis mind nende kahe vahel tõlkimisel üllatasid või tekitasid segadusse. Perl 6 kood on tähistatud tähega ja Haskell poolestusaja sümboliga λ⃝.

Tüüpe pole tööajas olemas

Perl 6 versioonis võimendan tüüpe tööajas. Kasutasin alamhulkadeklaratsiooni Int alatüüpide tuletamiseks koos käitusaja piirangutega.

# 
# Vahemikud
konstantne $ ℤ𝑝 = ^ 𝑝; # Perl 6 0 .. (𝑝-1);
konstantne $ ℤ𝑞 = ^ 𝑞;
# Vahemikust tuletatud tüübid
Int: D alamhulk D kus $ ℤ𝑝; # suvaline keskmine 0-s. (𝑝-1)
Int: D alamhulk D kus $ ℤ𝑞;
# Korrutuste grupp of (genereeritud 𝑔)
alamhulk 𝔾, kus * .expmod (𝑞, 𝑝) == 1

Nii et ℤ𝑞, ℤ𝑞 ja 𝔾 deklareeritakse kompileerimise ajal, kuid nende piiranguid (kus on deklareeritud) kontrollitakse tööajas.

Haskelli abil ei saa sa sama teha. Tüübid on ainult kompileerimise aja konstruktsioon; te ei saa neile käitusaja piiranguid lisada. Arvestades, et Haskell on funktsionaalne, oli mõistlik kirjutada piiranguid määratlevad funktsioonid:

- λ⃝
andmerühm = ℤ𝑝 | ℤ𝑞 | 𝔾 tuletamine (näitamine, ekv)
ülaosa :: rühm -> täisarv
ülaosa ℤ𝑝 = 𝑝
ülaosa ℤ𝑞 = 𝑞
liige :: Rühm -> täisarv -> Bool
liige 𝔾 x = (liige ℤ𝑝 x) && (expmod x 𝑞 𝑝) == 1
liige g x = x> = 0 && x <(ülaosa g)

Kahe lähenemisviisi seos on selge, kui vaadata 𝔾 määratlusi kõrvuti:

# 
alamhulk 𝔾, kus * .expmod (𝑞, 𝑝) == 1
- λ⃝
liige 𝔾 x = (liige ℤ𝑝 x) && (expmod x 𝑞 𝑝) == 1

Perl 6 on küll kõvem, kuid Haskelli versioon oli kenasti koostatud ilma eriliste märksõnadeta.

Kasutan neid peamise diskreetse logaritmipõhise pühendumisfunktsiooni määratlemiseks:

# 
sub COMMIT (ℤ𝑞 \ 𝑥 -> 𝔾) {expmod (𝑔, 𝑥, 𝑝)}
- λ⃝
pühenduma: täisarv -> täisarv
pühenduma 𝑥 | liige ℤ𝑞 𝑥 = expmod 𝑔 𝑥 𝑝
         | muidu = viga (näita 𝑥 ++ "ei kuulu ℤ𝑞")

Perl 6 alamhulga peamine eelis on see, et te ei pea oma veateateid kirjutama ja allkiri näeb välja nagu diskreetse logaritmi pühendumisfunktsiooni matemaatiline määratlus.

Rekursioon silmuste asemel

Salajase kiire funktsioon võtab argumendina funktsiooni parsimise ja tagastab kasutajalt sisendi pärast selle edukat töötlemist pabersalvestusega. Perl 6 versioon kasutab kuni silmust, Haskelli versioon aga rekursiooni.

# 
alamsaladus-kiire (ja parsige) {
    kuni on määratletud minu $ valid = parsida (read-line) {
        öelda ("Vale väärtus. Proovige uuesti.")
    }
    tagastama $ kehtiv;
}
- λ⃝
secretPrompt :: (String -> Võib-olla a) -> IO a
secretPrompt parse = tegema
  rida <- readLine
  juhtum (parseliin)
       Ei midagi -> putStrLn "Vigane väärtus. Proovige uuesti." >>
                  secretPrompt pars
       Lihtsalt x -> tagasta x

Kohustuslik versioon on minu jaoks endiselt loomulikum. Arvan, et silmuste ja muutujate asemel funktsioonide ja rekursiooni mõtestamine võtab lihtsalt natuke harjumist. Ma pole kindel, kas Haskelli versioon on lühike, nagu see võiks olla.

Mis on lahe Haskelli secretPromptist, on see, kui selgelt ja lühidalt määratlete parsimisargumendi. Perli 6 puhul võin öelda, et see peab olema & sigiliga helistatav. Haskellis määratlen allkirja (String -> Võib-olla a) -> IO a, mis tõlgib järgmiselt: “Kõigi SecretPrompt'i kõnede argumendifunktsioon peab võtma stringi ja tagastama Võib-olla mis tahes tüüpi a. secreptPrompt tagastab seejärel tüübi AO. Muidugi ilma selle funktsioonita ei saaks te seda Haskellis kompileerida, kuid arvan, et see on ikka päris tore.

Redigeerimine: tegelikult on helistatava allkirja kontrollimine Perl 6-s käitusajal. Näiteks võite teha järgmist:

alamsaladuse viip (ja parsige: (struktuur -> suvaline)) {
    kuni on määratletud minu $ valid = parsida (read-line) {
        öelda ("Vale väärtus. Proovige uuesti.")
    }
    tagastama $ kehtiv;
}

See pole nii võimas kui Haskelli versioon, mis võimaldab määratleda funktsiooni tagastamise tüübi kompileerimise ajal selle põhjal, milline on argumentfunktsiooni tagasitulekutüüp. Tänu Wenzel Peppmeyerile, kes selle funktsiooni mulle kommentaarides osutas.

Kui Haskell muutub tüütuks

Haskelli kompilaatori (muidu tuntud kui selgesti õige koodi kirjutamine) rahuldamine võib olla tõesti keeruline. Ma leidsin, et toitsin seda palju korduvaid avaldusi, et see õnnelik oleks. Paistab, et mõnikord on see nii tüütu, et Haskelli arendajad lisavad koormuse kergendamiseks kompilaatorisse keelelaiendid. Nii osutus eksistentsiaalseks kvantifitseerimiseks üsna käepärane.

Mündi flipimise mängu iga sammu kohta saadab Alice või Rob teisele võtmete ja väärtuste sõnumi, näiteks:

# 
 ⟹ (pühendumus => 𝑐, liikuge => 𝑚);

Haskellis tööle saada oli tõesti keeruline, kuna 𝑐 on täisarv ja 𝑚 on münt. Kui mu objektorienteeritud müts oli peal, arvasin, et peaksin tegema ainult sellise klassi nagu MessageValue, mis määratleks, kuidas igat tüüpi asjad välja trükkida:

- λ⃝
klass MessageValue a kus
  gist :: a -> keelpill
näiteks MessageValue String kus
  gist = id
näiteks MessageValue täisarv kus
  sisu x = "0x" ++ showHex x ""
näiteks MessageValue Münt kus
  gist = näita

Ja seejärel pange signature allkiri võtma (String, MessageValue) paaride loend järgmiselt:

- λ⃝
(⟹) :: (MessageValue a) => Mängija -> [(String, a)] -> IO ()

Ja nimetage seda nii:

- λ⃝
() ⟹ [(„koli“, 𝑚), („pühendumine“, 𝑐)]

Kuna nii 𝑚 kui ka types tüüp on mõlemadMessageValue eksemplarid, peaks see sobima, eks? Vale! Pole tähtis, et nad mõlemad on klassi MessageValue eksemplarid, nad pole samad eksemplarid. Arrrrg.

Niisiis, kuidas ma saan neist sama tüüpi? Tegin uut tüüpi, mis mähivad nii täisarvu kui ka mündi nii:

- λ⃝
andmed MessageWrap = MWS string | MWI täisarv | MWC münt
näiteks teade MessageWrap kus
gist (MWS x) = gist x
gist (MWI x) = gist x
gist (MWC x) = gist x

Ja siis kasutage seda nii:

- λ⃝
() ⟹ [("teisaldamine", MWC 𝑚), ("kohustus", MWI 𝑐)]

Nüüd on meil nimekiri (String, MessageValue) paaridest ja Haskell on jälle õnnelik ning GHC kompileerib mu koodi. Kuid see pole ideaalne, sest iga kord, kui tahan uut tüüpi asju välja printida, pean looma uue MW, olenemata selle väärtusest, ja seejärel lisama veel ühe korduva sisuga (MWw olenemata) kandidaadi.

Õnneks on olemas kompilaatori laiendus nimega ExistentialQuantification, mis tutvustab spetsiaalset forall-lauset, mis võib kompilaatorile öelda, mida ma oma vähemuses avaldustes mõtlen. Ma kasutasin seda nii:

{- # KEEL ExistentialQuantification # -}
data MessageWrap = jätkub a. MessageValue a => MW a
- määratlege sisu, mis mähistab tüübi lahti
näiteks MessageValue MessageWrap kus
  gist (MW m) = gist m

Ja siis sõnumi saatmiseks, mida ma lihtsalt teen:

() ⟹ [("teisaldamine", MW 𝑚), ("kohustus", MW 𝑐)]

Ja kõik töötab! Vinge.

... välja arvatud siis, kui ringi googeldades leidsin, et kellegi sõnul, kes teab, mida nad räägivad, on see anti-muster . @jonathangfischoff arvab, et peaksin lihtsalt tegema:

() ⟹ [("kolima", sisuliselt 𝑚), ("pühenduma", sisuliselt 𝑐)]

Ma pole kindlasti nõus. See tähendab, et kui ma tahan kunagi muuta seda, kuidas ⟹ töötab sisemiselt (nt saata võrgusidet), pean vahetama ka helistaja, mis ei kõla hästi. Tahaksin teada, kas see on antud juhul tõepoolest antiimmuster, kuna see tundub mulle üsna hea.

Võrdluseks laiendasin oma Perli 6 versiooni just sisemise funktsiooni suurendamisega kahe multidisplit-kandidaadiga.

# 
mitme sisuga (keskmine: D $ _) {'0x' ~ .baas (16) .lc}
mitme sisuga (loendus: D $ _) on vaikimisi {.gist}

Tundub, et hind, mida Haskell keerukatele probleemidele elegantsete lahenduste eest maksab, on see, et mõned lihtsad probleemid, näiteks mõne asja printimine, muutuvad palju raskemaks probleemiks.

Kui see kompileeritakse, on see õige

See tabas mind Haskelli kõige rohkem. Pärast seda, kui olete läbinud kannatused selle kompileerimiseks, töötab! Ainus aeg ei olnud see, kus see kompileeriti ja ebaõnnestus käitusel. Mul pole kunagi varem sellist kogemust olnud, isegi teiste staatiliselt kirjutatud keeltega. Puhas funktsionaalne + staatiline kirjutamine + võimsa tüübi järeldus näib olevat keelekujunduse kohalik optimaalsus.

Muidugi kulub selle kompileerimiseks tegelikult veel palju vaeva. Kiire tagasiside saamine väljundi ja vigade kaudu muudab minu arvates dünaamilisi keeli lihtsamaks.

Siiani leidsin, et mul on Haskelliga läbi käinud hambad ja lõbutsemised ning pettumused. Teistes keeltes, mida ma olen õppinud, tunnen end tavaliselt üsna enesekindlalt pärast nädal aega seda jama. Kuid Haskelli puhul on tunne, et ma alles alustan sellega. Arvan, et järgmine kord pean proovima seda kasutada keerukamate algoritmide ja vähem keerukate IO-de abil mõne probleemi rakendamiseks.

Kohustustest loobumine: kogu kood on ette nähtud ainult tutvustamiseks. Ärge kasutage reaalajas koodina matemaatika unicode-tähemärke ja emotikone. Juhuslikke numbreid ei toodeta turvalisel viisil. Ärge võtke Internetis selliseid artikleid nagu rakendussoovitusi. Ära isegi arva, et see on õige! Nagu alati, ärge kunagi veeretage oma krüptograafiat.