Anatomia funkcji
Ogólny kształt funkcji w Zero:
fun name(param1: Type1, param2: Type2) -> ReturnType {
// ciało
return value
}
Elementy:
fun: słowo kluczowe rozpoczynające funkcję.name: nazwa funkcji.(param1: Type1, ...): lista parametrów. Każdy parametr ma jawny typ.-> ReturnType: typ zwracany.{ ... }: ciało, czyli blok instrukcji.return value: kończy funkcję, zwracającvalue.
Mały konkretny przykład:
fun double(value: i32) -> i32 {
return value * 2
}
To cała funkcja. Przyjmuje jedno i32, zwraca drugie i nie wykonuje I/O. Wewnątrz ciała value jest wiązaniem w stylu let: możesz go używać jak każdej innej zmiennej lokalnej.
Wywoływanie funkcji
Wywołania wyglądają dokładnie tak, jak można się spodziewać:
let result = double(21)
Argument musi pasować do typu parametru. Wynik zostaje związany z result, którego typ kompilator wnioskuje jako i32, bo double zwraca i32.
Przykład łączący funkcję pomocniczą z main. Kliknij Run, aby zobaczyć go w działaniu:
Na stdout powinno pojawić się math works\n.
pub i widoczność
Domyślnie funkcja zadeklarowana w pliku jest prywatna dla tego pliku (lub modułu: reguły widoczności zaostrzają się wraz ze wzrostem projektu). Aby udostępnić funkcję poza jej modułem, poprzedź ją słowem pub:
pub fun greet() -> String {
return "cześć\n"
}
Bez pub kod z innych modułów nie może wywołać greet. Środowisko uruchomieniowe musi wywołać main spoza jakiegokolwiek modułu użytkownika, dlatego main zawsze jest pub.
Warto trzymać się reguły domyślnej prywatności. Oznaczaj tylko to, co ma być interfejsem; reszta zostaje w module.
Typy zwracane
Każda funkcja deklaruje typ zwracany po ->. Typowe typy zwracane:
fun answer() -> i32 { return 42 }
fun ok() -> bool { return true }
fun label() -> String { return "ready\n" }
fun nothing() -> Void { }
Void to typ zwracany funkcji, która wykonuje swoją pracę przez efekty uboczne, a nie przez zwrócenie wartości. Funkcja Void nie potrzebuje jawnego return: wystarczy dojść do końca ciała.
fun log(world: World, message: String) -> Void raises {
check world.out.write(message)
}
Wywołania, które odrzucają wartość
Jeśli funkcja zwraca wartość, która cię nie interesuje, i tak musisz potwierdzić jej zwrócenie. Idiomem jest związanie jej przez let:
ignored to wiązanie, którego reszta funkcji nigdy nie czyta. Konwencja używania nazwy ignored (lub _) sygnalizuje, że odrzucenie jest zamierzone. To więcej tarcia niż ciche porzucenie wartości zwracanej, i o to właśnie chodzi: w języku, w którym agenci czytają i generują kod, nieprzeczytana wartość często jest błędem wartym ujawnienia.
Rola raises
Funkcja, która może się nie powieść, deklaruje to w sygnaturze. Widzieliśmy to przy main:
pub fun main(world: World) -> Void raises {
check world.out.write("cześć\n")
}
Klauzula raises może być ogólna (dowolny błąd) albo konkretna:
fun validate(ok: Bool) -> i32 raises { InvalidInput } {
if ok == false {
raise InvalidInput
}
return 42
}
raises { InvalidInput } oznacza: "ta funkcja może zakończyć się błędem InvalidInput i niczym innym". Kod wywołujący musi użyć check (lub bardziej rozbudowanej formy obsługi), aby przekazać błąd dalej albo go obsłużyć.
Artykuł Raises i Check omawia to dogłębnie, łącznie z tym, co dzieje się przy wielu typach błędów i jak check współpracuje z klauzulą raises wywołującego.
Funkcje generyczne
Gdy funkcja ma działać dla więcej niż jednego typu, zadeklaruj parametry typu w nawiasach ostrych:
fun makePair<T, U>(left: T, right: U) -> Pair<T, U> {
return Pair { left: left, right: right }
}
T i U to parametry typu: decyduje o nich wywołujący. Wywołanie makePair(40, 2_u8) daje Pair<i32, u8>. Pełny opis, łącznie z generycznymi shape i ograniczeniami, znajdziesz w artykule Generyki.
Gdzie żyją funkcje
W małym programie piszesz funkcje bezpośrednio w pliku .0. W pakiecie rozkładasz funkcje na pliki w src/, a kompilator sam rozwiązuje odwołania między plikami. Podstawy pozostają te same (fun, parametry, typ zwracany, ciało), niezależnie od tego, gdzie funkcja fizycznie się znajduje.
Uwagi o stylu
Kilka konwencji, które zobaczysz w oficjalnych przykładach:
- Nazwy funkcji małymi literami, ze słowami złączonymi (
makePair) w stylu camelCase. Biblioteka standardowa skłania się ku camelCase. - Jedna wartość zwracana na funkcję. Jeśli musisz zwrócić kilka rzeczy, zbuduj dla nich mały
shape: to czytelniejsze niż zwracanie krotki par krotek. - Funkcje
Voidwykonują tylko wywołaniacheck; funkcje obliczające wartość w miarę możliwości unikają I/O. Ten podział jest częściowo kulturowy, a częściowo wymuszony: czysto obliczeniowa funkcja nie przyjmujeworld, więc dosłownie nie może wykonywać I/O.
Ostatni punkt warto rozwinąć. Ponieważ I/O kryje się za capability World, a World przekazuje się jawnie, sygnatura funkcji mówi, czy może ona wykonywać I/O. Funkcje, których sygnatury nie wspominają o World, są czyste względem świata zewnętrznego. Na tej właściwości agenci (i ludzie) mogą polegać bez czytania ciała funkcji.
Dalej: if/else
if pojawiało się już mimochodem. Następny artykuł szczegółowo omawia wyrażenia if/else, łącznie z tym, jak współpracują z wiązaniami i czego celowo brakuje (brak rzutowania na wartość logiczną, brak operatora trójargumentowego).
Najczęściej zadawane pytania
Jak zadeklarować funkcję w Zero?
Użyj fun: fun name(param: Type) -> ReturnType { body }. Dodaj pub na początku, aby funkcja była widoczna poza swoim modułem. Dodaj raises po typie zwracanym, jeśli funkcja może się nie powieść. Na przykład: pub fun double(value: i32) -> i32 { return value * 2 }.
Co robi słowo kluczowe pub?
pub?pub oznacza deklarację jako publiczną, czyli widoczną dla kodu spoza bieżącego modułu. Bez pub funkcja jest prywatna w pliku (lub pakiecie), w którym ją zadeklarowano. Zwyczajowy punkt wejścia pub fun main musi być publiczny, aby środowisko uruchomieniowe mogło go znaleźć i wywołać.
Jak zwrócić wartość z funkcji w Zero?
Napisz return value w ciele funkcji. Wyrażenie musi pasować do zadeklarowanego typu zwracanego. Funkcja o typie zwracanym Void nic nie zwraca i nie potrzebuje jawnej instrukcji return: wystarczy dojść do końca ciała.
Czy funkcje w Zero mogą przyjmować wiele parametrów?
Tak. Wymień je w nawiasach, oddzielając przecinkami, każdy z nazwą i typem: fun add(a: i32, b: i32) -> i32 { return a + b }. Każdy parametr jest w ciele funkcji wiązaniem w stylu let. Zero wymaga jawnych typów parametrów: w deklaracjach funkcji nie ma wnioskowania typów parametrów.
Co oznacza raises w sygnaturze funkcji?
raises w sygnaturze funkcji?raises deklaruje, że funkcja może się nie powieść. Samo raises dopuszcza dowolny typ błędu; raises { InvalidInput } ogranicza go do konkretnego nazwanego błędu. Kod wywołujący musi użyć check (lub innej konstrukcji obsługi błędów), aby potwierdzić możliwość porażki: nie może jej po cichu zignorować.