06. kapitulua: Datu-Mota Aljebraikoak (ADTak), Polimorfismoa eta zergatik hemen ez dugun null edo Any nahi
1965ean, Tony Hoarek erreferentzia nulua (null / nil) asmatu zuen. Berrogei urte geroago barkamena eskatu zuen jendaurrean eta "nire mila milioi dolarreko akatsa" deitu zion.
Zergatik? null duten lengoaietan, funtzio batek bere sinaduran hitzematen dizulako Erabiltzailea bat itzuliko dizula... eta gutxien espero duzunean null itzultzen dizulako eta goizeko hiruretan zerbitzaria lurrera botatzen dizulako.
Eta beste horrenbeste gertatzen da objektuen herentziarekin eta Any (edo Object) motarekin: zenbaki bat eta testu bat [1, "bi"] zerrenda batean sartzen dituzu, lengoaiak sorbaldak jasotzen ditu eta esaten du "ados, ba orain hau List[Any] bat da, zorte on barruan zer dagoen asmatzen zeharkatzen duzunean".
Ridgunen bi arazo horiek errotik mozten ditugu Datu-Mota Aljebraikoekin (ADTak) eta Polimorfismo Parametrikoarekin. Ikasgai gogorreko izena duela dirudi, baina bost minutuan ikusiko duzu datuak modelatzeko munduko modurik naturalena dela.
6.1. Biderkadura-Motak (Erregistroak): Betiko zure struct-ak, baina superbotereekin
Entitate berari dagozkion hainbat datu elkartu nahi dituzunean (adibidez, zerbitzari bat bere izenarekin, bere IParekin eta bere atakarekin), type bat definitzen duzu bere eremuekin:
type Zerbitzaria = ostalaria: String, ataka: Int, tls_aktibo: Bool
Lerro hori idazte hutsarekin, Ridgunek lau gauza oparitzen dizkizu automatikoki:
1. Zerbitzaria eraikitzailea
nire nodoa => Zerbitzaria
nire nodoa = Zerbitzaria "api.adibidea.eus" 443 True
2. Getter funtzio puru bat eremu bakoitzerako (ostalaria, ataka, tls_aktibo)
Ez duzu s.getOstalaria() edo antzekorik idatzi behar; eremu-izen bakoitza zuzenean datu hori ateratzen duen funtzio bat da:
konexio urla Zerbitzaria => String
konexio urla zrb = "Konektatu hona: " + (ostalaria zrb) + " {ataka zrb} atakan"
3. Erregistroen eguneratze funtzionala (with eremua: balioa)
Imajinatu 12 edo 15 eremuko konfigurazio-erregistro bat (ls komando baten edo web-zerbitzari baten aukerak bezala) eta eremu bat edo bi bakarrik aldatu nahi dituzula gainerakoak ukitu gabe. Erregistroa 15 eremuak eskuz pasatuz berreraiki beharrean, with eta bi puntu : erabiltzen dituzu (type-aren definizioan bezalaxe):
aktibatu garapen modua Zerbitzaria => Zerbitzaria
aktibatu garapen modua zrb = zrb with ataka: 8080, tls_aktibo: False
Bi munduetako onena:
- Zure kodean % 100ean aldaezina eta segurua da (konpiladoreak egiaztatzen du
atakaetatls_aktiboZerbitzaria-n existitzen direla eta mota zuzena dutela). - Azpitik,
zrbbeste inorekin partekatuta ez badago, exekutagarri natiboak bere memoria in-place berrerabiltzen du (Arc::try_unwrap) gainerako eremuak kopiatu gabe.
4. Pattern matching osoa erregistroa erditik ireki nahi baduzu
ataka segurua da Zerbitzaria => Bool
ataka segurua da (Zerbitzaria _ 443 True) = True
ataka segurua da _ = False
6.2. Batuketa-Motak (| bidezko aldaerak): Nola erretiratu null, salbuespenak eta herentzia
Orain dator onena.
Erregistro arrunt batek ostalaria ETA ataka ETA tls_aktibo ditu.
Baina mundu errealean askotan hau esan behar duzu: "Ordainketa-metodo bat EDO* bere zenbakia duen txartel bat da, EDO bere IBANa duen transferentzia bat da, EDO eskudirua da (datu gehigarririk ez daramana)"*.
Objektuetara bideratutako programazioan OrdainketaMetodoa interfaze bat, hiru azpiklase hiru fitxategi desberdinetan, eraikitzaileak, instanceof... sortu beharko zenituzke. Ridgunen lerro bakarra da:
type OrdainketaMetodoa = Txartela String Int | Transferentzia String | Eskudirua
Eta begira zer gertatzen den funtzio batean OrdainketaMetodoa bat prozesatzen duzunean:
deskribatu ordainketa OrdainketaMetodoa => String
deskribatu ordainketa (Txartela zenb _) = "{zenb}-rekin amaitutako txartel bidezko ordainketa"
deskribatu ordainketa (Transferentzia ibana) = "Transferentzia {ibana} kontutik"
deskribatu ordainketa Eskudirua = "Eskudirutan ordaintzea leihatilan"
Aldaera bakoitzak (Txartela, Transferentzia, Eskudirua) berarentzat zentzua duten datuak soilik daramatza itsatsita. Ezinezkoa da hutsez ibana bat zintzilik duen Eskudirua ordainketa bat eraikitzea.
6.3. "Eta nola sartzen dut [1, "bi"] zerrenda batean Any erabili gabe?"
Itzul gaitezen milioiaren galderara. Imajinatu CSV edo JSON fitxategi bat irakurtzen ari zarela, non gelaxka bat zenbaki osoko bat, testu bat edo hutsa izan daitekeen.
Any mota bat bazenu, [1, "bi"] sartuko zenuke eta konpiladorearen laguntza guztia galduko zenuke: une horretatik aurrera zerrendaren gaineko edozein eragiketa begiak estalita ibiltzea litzateke.
Ridgunen (Haskell edo Rusten bezalaxe), bi segundotan batuketa-mota bat sortzen duzu, zerrenda horren barruan zein gauza bizi daitezkeen zehatz-mehatz azaltzen duena:
type GelaxkaBalioa = Osokoa Int | Testua String | Hutsa
formateatu gelaxka GelaxkaBalioa => String
formateatu gelaxka (Osokoa n) = "Zenbakia: {n}"
formateatu gelaxka (Testua s) = "Testua: {s}"
formateatu gelaxka Hutsa = "(hutsik)"
nire errenkada => [GelaxkaBalioa]
nire errenkada = [Osokoa 1, Testua "bi", Hutsa]
Ikusten duzu aldea?
Any-rekin, konpiladoreak ez daki zerrendan osokoak, kateak ala USB bidez konektatutako kafe-makina bat dauden.[GelaxkaBalioa]-rekin, zenbakiak eta testuak zerrenda berean nahas ditzakezu, baina konpiladoreak % 100ean daki hiru aukera horiek baino ez direla existitzen eta kasu bakoitza (Osokoa n,Testua s,Hutsa) erabili aurretik despaketatzera behartzen zaitu. Segurtasun osoa malgutasun izpi bat ere galdu gabe.
6.4. Option eta Result: Zerbait ez egon daitekeenean edo huts egin dezakeenean
Ridgunen null existitzen ez denez eta airean lehertzen diren salbuespenik (throw new Exception()) existitzen ez denez, lengoaiak bi batuketa-mota unibertsal integratuta dakartza:
Option: edoSome balioaduzu, edoNone(ezer ez) duzu.Result: edo eragiketa ondo atera da (Ok balioa), edo arrazoi zehatz bategatik huts egin du (Err errorea).
bilatu zatiketa Int Int => Result
bilatu zatiketa _ 0 = Err "Ei! Ezin da zeroz zatitu"
bilatu zatiketa a b = Ok (a / b)
inprimatu em Result => ()
inprimatu em (Ok n) = echo "Emaitza {n} da"
inprimatu em (Err msg) = echo "Hutsegite kontrolatua: {msg}"
Inork ezin du errore bat deskuiduz alde batera utzi, Result motak berak barruan Ok bat ala Err bat dagoen begiratzera behartzen zaituelako.
6.5. Polimorfismo Parametrikoa: Funtzio generikoak A, B, T...-rekin
Demagun (x, y) bikote bat alderantzikatzen duen edo zerrenda bateko lehen elementua ateratzen duen funtzio bat idazten duzula. Benetan axola du zerrenda Int, String ala Zerbitzaria motakoa den? Inola ere ez! Algoritmoa berdin-berdina da.
Ridgunen, mota-sinadura bateko edozein letra larri banakok (A-tik Z-ra) automatikoki komodin-mota polimorfiko (mota-aldagai) gisa funtzionatzen du:
buru segurua [A] A => A
buru segurua [] lehenetsia = lehenetsia
buru segurua (x:_) _ = x
biratu bikotea (A, B) => (B, A)
biratu bikotea (x, y) = (y, x)
Erreparatu buru segurua [A] A => A-ren magiari:
- Ez da kontrolik gabeko
Anybat:[A]zerrendaren motak,lehenetsiabalioaren motak (A) eta itzulitako motak (A) bera izan behar dutela dio. buru segurua [10, 20] 0deitzen baduzu, konpiladoreak hariak lotzen ditu (A = Int) etaIntbat itzultzen dizu.buru segurua ["a", "b"] "bat_ere_ez"deitzen baduzu, hariak lotzen ditu (A = String) etaStringbat itzultzen dizu.- Baina tranpa egiten saiatzen bazara eta
buru segurua [10, 20] "bat_ere_ez"deitzen baduzu, zas! Konpiladoreak lehorrean gelditzen zaitu, lehen argumentuakAIntdela dioelako eta bigarrenakAStringdela.
6.6. Sortu zure eragile propioak (op ::): Bektoreetatik neurrira egindako esaldietara
Zergatik zenbaki natiboek bakarrik izan behar zuten + edo * erabiltzeko eskubidea? Eta zergatik konformatu betiko eragileekin zure arazorako sintaxi ezin hobea asma dezakezunean?
op ::-rekin (edo zuzenean aurrizkirik gabe eragile-ereduak erabiltzen badituzu) bi trebezia egin ditzakezu:
1. +, -, *, /, % eragileei zure mota propioekin eragiketak egiten irakastea
type Konplexua = erreala: Int, irudikaria: Int
op :: Konplexua + Konplexua => Konplexua
a + b = Konplexua ((erreala a) + (erreala b)) ((irudikaria a) + (irudikaria b))
2. Mixfix eragileak sortzea (esaldi natural gisa irakurtzen direnak, type eta with barne)
Eragile-sinadura batean, hasieran letra larria duten hitzak (Int, String, A..Z) edo kortxete/parentesi artekoak ([Int], (A, B)) argumentuetarako hutsuneak dira, eta letra xehez idatzitako hitzak eragilearen esaldiaren parte dira (baita type edo with bezalako hitz ohikoak ere, Ridgunen testuingurukoak direnak eta inoiz blokeatzen ez zaituztenak):
op :: baldin Bool orduan A bestela A => A
baldin True orduan v bestela _ = v
baldin False orduan _ bestela f = f
op :: format String type String with Int => String
format label type kind with w = "{label} ({kind}, width={w})"
main => ()
main =
mezua = baldin (10 > 5) orduan "Hamarrak irabazten du!" bestela "Ezinezkoa"
echo mezua
echo (format "panela" type "ertzduna" with 80)
Zure eragile hirutar polimorfiko propioa eta formateatzeko DSL bat fabrikatu berri dituzu Ridguneko 3 lerrotan.
6.7. Eraikitzaile partekatuak, mota-aldaera zuzenak, expand type eta zuriune naturalak fn ::-n
Ridgunek datu-mota aljebraikoak eta funtzioak are naturalago eta hedagarriago bihurtzen ditu:
- Eraikitzaile-izen partekatuak
typeezberdinen artean: Batura-mota ezberdinek eraikitzaile-izen bera (Error,Var,Lit) berrerabil dezakete aritate berarekin edo ezberdinarekin; mota-egiaztatzaileak sinadurako zutabearen arabera bereizten ditu. - Mota-izen zuzeneko aldaerak errepikapenik gabe (
type Something = String | Int | MyStruct Float Int): Aldaera bat oinarrizko mota bat denean (String,Int,Float/Real,Bool,Char), ez duzuInt IntedoString Stringerrepikatu behar. Balio zuzenak ("kaixo",42) edo eraikitzaile esplizituak (String "kaixo",Int 42) pasa ditzakezu, eta ereduak parekatu bai literalekin ("admin",0) bai eredu tipatuekin ((String s),(Int n)). - Mota-hedapen irekia (
expand type) etafn ::klausulen bateratze automatikoa:expand typeerabiliz aldaera berriak gehitu diezazkiokezu batura-mota bati edo eremu berriak erregistro bati. Funtzio bererakofn ::berriro deklaratzean, klausula berriak automatikoki bateratzen dira edozein_komodin-klausularen aurretik (expand fnidatzi beharrik gabe). - Zuriune naturalak
" "_-ren ordezfn ::-n, funtzio natiboetan eta fitxategi-izenetan:op ::-n bezala, hitz anitzekofn ::izenak, funtzio natiboen deiak (assert equals,str len,read file) eta.rgfitxategi-izenak (test core language.rg) zuriune naturalekin idatz daitezke:
type Something = String | Int | MyStruct Float Int
fn :: deskribatu zerbait Something => String
deskribatu zerbait "admin" = "admin_berezia"
deskribatu zerbait 0 = "zero"
deskribatu zerbait (String s) = "testua:{s}"
deskribatu zerbait (Int n) = "osoa:{n}"
deskribatu zerbait (MyStruct ratio cnt) = "egitura:{ratio}:{cnt}"
deskribatu zerbait _ = "bestea"
expand type Something = Bool
fn :: deskribatu zerbait Something => String
deskribatu zerbait (Bool b) = "boolearra:{b}"
5. Mota generiko propioak (type Result T E, type Page T) eta moduluen izen-espazioak (import ... as, mod.Mota)
Funtzio polimorfikoez gain, zure batura-mota eta erregistro generiko propioak deklara ditzakezu mota-parametroekin (A..Z), baita izen bereko motak, eraikitzaileak, eremuak eta funtzioak modulu ezberdinetan talkarik gabe antolatu ere (module ::, import db as d, http.Config, d.Config). Modulu bakoitzak 3 mailako inportazio-ikusgarritasun zorrotza du: aurrizkirik gabeko izenak lehenik (1) bere deklarazioetan bilatzen ditu (module :: izena partekatzen duten fitxategi guztiak barne), ondoren (2) bere import zuzenen artean eta azkenik (3) inportazio iragankorretan, inportatu gabeko moduluek edo aplikazio nagusiak liburutegi bat inoiz kutsa ez dezaten:
type Emaitza T E = Arrakasta T | Hutsegitea E
type Orrialdea T = elementuak: [T], guztira: Int
fn :: atera arrakasta (Emaitza A String) A => A
atera arrakasta (Arrakasta v) _ = v
atera arrakasta (Hutsegitea _) d = d
07. kapituluan zure prozesadorearen nukleo guztiak aldi berean lanean jarriko ditugu izerdi tanta bat ere bota gabe: goi-mailako funtzioak (map, ||map, filter, ||filter, fold), itxitura lexikoak ((x => expr)), || grafo paraleloak -> geziekin eta kanal konkurrenteak.