Skip to content
Ridgun
English

Ridgun

The language itself: a compiler that compiles itself, the stable project tool, the specification and the book

05-listor-tupler-och-sekvenser.md167 lines · 7.9 KBRaw
1# Kapitel 05: Listor, tupler och ninjatricket med sekvenser
2
3Ett program som bara flyttar ett tal eller en text i taget gör inte mycket nytta. I verkliga livet hanterar du fillistor, rader från en databas, nätverkspaket eller koordinater.
4
5För det ger Ridgun dig tre olika behållare, var och en utformad för en mycket specifik uppgift:
61. **Listor (`[T]`)**: *"Jag har många element av samma typ (eller inga alls) och vill gå igenom dem"*.
72. **Tupler (`(A, B)`)**: *"Jag vill returnera ett par eller en trio av olika saker tillsammans utan att skapa en ny typ"*.
83. **Sekvenser och masker (`s(_ + _)`, `(+ _ _ +)`)**: *"Jag vill filtrera eller stuva om argument i farten som om jag hade ett matematiskt durkslag"*.
9
10Vi tar dem en i taget.
11
12---
13
14## 5.1. Listor (`[T]`): Vårt dagliga bröd
15
16Om du i Ridgun sätter hakparenteser runt en typ har du en lista av den typen:
17- `[Int]` är en lista med heltal: `[10, 20, 30]`.
18- `[String]` är en lista med texter: `["apple", "paron"]`.
19- `[]` är den tomma listan (jokertecknet som passar till varje `[T]`).
20
21> *"Hallå, kan jag stoppa in ett tal och en text tillsammans i samma lista, typ `[1, "tva"]`?"*
22> Som den står, **nej** (och tacka kompilatorn för att den hindrar dig, för på så sätt kommer en summering i produktion aldrig att explodera när den stöter på en oväntad text). I Kapitel 06 ska vi se hur man löser fallet `[1, "tva"]` med fullständig elegans med hjälp av en algebraisk datatyp (precis som i Haskell).
23
24### Hur man delar upp en lista utan index `i`, `j`, `k`: `[]` och `(x:xs)`
25I imperativa språk innebär det oftast `for (int i = 0; i < lista.size(); i++)` att gå igenom en lista, med den klassiska risken att räkna ett steg fel (*off-by-one*).
26
27I Ridgun räknar du inte index; **du plockar isär listan från huvudet**:
28- `[]`: matchar om listan är tom.
29- `(x:xs)`: matchar om listan har minst ett element. Den lägger det första elementet i `x` (**huvudet**) och resten av listan i `xs` (**svansen**).
30
31Titta så smidigt det är att summera en lista eller räkna hur många element den har:
32
33```ridgun
34summera lista [Int] => Int
35summera lista [] = 0
36summera lista (x:xs) = x + (summera lista xs)
37
38rakna element [A] => Int
39rakna element [] = 0
40rakna element (_:xs) = 1 + (rakna element xs)
41```
42
43Och en teknisk detalj som kommer att göra dig glad: i Ridgun **kopierar inte `(x:xs)` resten av listan i minnet** när svansen `xs` plockas ut. Internt är det en vy över samma vektor som bara flyttar fram startpekaren med `1` (omedelbart i $O(1)$).
44
45### Du kan plocka fram flera element i ett svep
46Mönstret `:` är otroligt användbart när du tolkar kommandon eller protokoll:
47
48```ridgun
49tolka kommando [String] => String
50tolka kommando [] = "Du har inte skrivit något"
51tolka kommando ("hjalp":_) = "Visar hjälpen..."
52tolka kommando ("kopiera":kalla:mal:[]) = "Kopierar från {kalla} till {mal}"
53tolka kommando (cmd:_) = "Okänt kommando: {cmd}"
54```
55
56Titta på tredje raden: `("kopiera":kalla:mal:[])` kontrollerar i ett enda slag att listan har **exakt 3 element**, att det första är `"kopiera"`, och serverar det andra och tredje på silverfat i `kalla` och `mal`. Försök skriva det i C eller Java så får du se hur många rader med `args.length == 3 && args[0].equals("kopiera")` det blir.
57
58### Slå ihop listor med dubbla kolon (`::`)
59När du befinner dig till höger om `=` (i ett uttryck) och vill foga ihop två listor använder du **`::`**:
60
61```ridgun
62foga listor [Int] [Int] => [Int]
63foga listor a b = a :: b
64
65lagg till sist [Int] Int => [Int]
66lagg till sist xs x = xs :: [x]
67```
68
69*(Snabb minnesregel: ett enkelt `:` i mönster plockar isär listan; dubbla `::` i uttryck fogar ihop två listor).*
70
71Och om du verkligen vill hämta element nummer 5 direkt utan att stega igenom något? Använd hakparenteser direkt efter namnet: `min_lista[5]`.
72
73---
74
75## 5.2. Variadiska funktioner (`T...`): "Skicka in hur många du vill!"
76
77Ibland vill du att den som anropar din funktion bekvämt ska kunna skriva `max av 4 19 2 88 7`, utan att tvingas linda in talen i hakparenteser `[4, 19, 2, 88, 7]`.
78
79Sätt tre punkter **`...`** efter den sista typen i din signatur, så får du inuti funktionen automatiskt in argumenten paketerade som en lista:
80
81```ridgun
82summera flera Int... => Int
83summera flera tal = summera lista tal
84
85main => ()
86main =
87 echo (summera flera 10 20 30) -- Skriver ut 60
88 echo (summera flera 1 2 3 4 5 6 7) -- Skriver ut 28
89```
90
91---
92
93## 5.3. Tupler: När en funktion vill returnera mer än en sak
94
95I många språk är det en plåga att returnera två saker från en funktion: antingen skapar du en fånig hjälpklass `ResultatMedPosition`, eller så skickar du med utdatapekare via referens.
96
97I Ridgun returnerar du en **tupel** `(A, B)`:
98
99```ridgun
100kvot och rest Int Int => (Int, Int)
101kvot och rest a b = (a / b, a % b)
102```
103
104Du kan till och med spara in parenteserna på signaturraden om du tycker att det ser renare ut:
105`kvot och rest Int Int => Int Int`.
106
107### Hur man öppnar en tupel
108Precis som du skapar den med `(a, b)` öppnar du den i argumenten till vilken funktion som helst med `(kvot, rest)`:
109
110```ridgun
111forklara division (Int, Int) => String
112forklara division (kvot, rest) = "Det går {kvot} gånger och blir {rest} över"
113```
114
115### Stjärndetaljen: Automatisk uppackning mellan funktioner
116Anta att du har `kvot och rest` (som returnerar en tupel med 2 heltal) och en annan funktion `summera par Int Int => Int` (som förväntar sig 2 fristående heltal). I Ridgun behöver du inte packa upp tupeln för hand i en mellanliggande variabel; du kan koppla in den ena funktionen rakt i den andra:
117
118```ridgun
119summera par Int Int => Int
120summera par x y = x + y
121
122main => ()
123main =
124 -- kvot och rest 17 5 returnerar (3, 2), och summera par tar emot 3 och 2 direkt:
125 echo (summera par (kvot och rest 17 5)) -- Skriver ut 5
126```
127
128---
129
130## 5.4. Ninjatricket: Sekvenser och positionsmasker (`s(_ + _)`, `(+ _ _ +)`)
131
132Här kommer en av Ridguns mest originella juveler, ärvd rakt från vektorspråk som J och APL.
133
134Föreställ dig att du tar emot en rad argument med sekvensmönstret **`(s)`** och bara vill behålla några och kasta andra utan att deklarera fem variabler med påhittade namn (`_a`, `b`, `_c`, `d`...).
135
136Då lägger du på en **tupelmask med `+` (behåll) och `_` (kasta)** på `s`:
137
138- **`s(_ + _)`**: av 3 element, kasta det första (`_`), behåll det andra (`+`) och kasta det tredje (`_`).
139- **`s(+ _ +)`**: behåll det första och det tredje; returnera sekvensen med dessa två.
140- **`s(_ _ +...)`**: kasta de två första (`_ _`) och tillämpa `+...` **på alla efterföljande element**, oavsett hur många de är.
141
142Se hur det fungerar i praktiken:
143
144```ridgun
145den i mitten Int Int Int => Int
146den i mitten (s) = s(_ + _)
147
148kasta tva forsta Int... => [Int]
149kasta tva forsta (s) = s(_ _ +...)
150
151main => ()
152main =
153 echo (den i mitten 100 42 999) -- Skriver ut 42
154 echo (kasta tva forsta 1 2 10 20 30) -- Skriver ut [10, 20, 30]
155```
156
157Och det bästa av allt: du kan skjuta in en positionsmask **`(+ _ _ +)`** **rakt in i en idiomatisk `->`-pipeline**! Om en funktion returnerar en tupel med flera värden och nästa funktion bara behöver två av dem, filtrerar du tupeln i farten och Ridgun packar upp de överlevande elementen direkt som separata argument till nästa steg:
158
159```ridgun
160-- tolka paket returnerar 4 värden: (Int, Int, [String String], String)
161-- (+ _ _ +) behåller det 1:a (code) och det 4:e (msg), och skickar dem till kombinera kod:
162tolka paket "v0.9.9" -> (+ _ _ +) -> kombinera kod -> sammanfattning
163```
164
165Och om du dessutom använder kommaoperatorn `,` utanför parenteser (`lista1 , lista2`), plattar Ridgun ut båda samlingarna till en enda sammanhängande sekvens.
166
167I **Kapitel 06** ska vi ta steget som förvandlar en funktionell programmerare till en förstklassig arkitekt: **att skapa dina egna algebraiska datatyper (ADT), skriva polymorfa funktioner med `A`..`Z` och skräddarsy dina egna operatorer**.