Przewodnik oprowadza szybko ,,zwiedzającego'' po składni i znaczeniu najbardziej użytecznych konstrukcji języka Scheme. Nie jest oficjalną dokumentacją języka ani żadnej jego implementacji. Formalną definicję języka Scheme podaje Revised5 Report on the Algorithmic Language Scheme.
W dalszej części używać będziemy następujących konwencji notacyjnych:
maszynowym
odpowiada fragmentom
programów.
Scheme jest językiem programowania opartym na wyrażeniach. Wyrażenia
nazywają wartości, np. 3.14
jest nazwą przybliżenia powszechnie
znanej liczby.
Istnieje wiele rodzajów wyrażeń Scheme:
Część wyrażeń to wyrażenia elementarne, nazywające wartości. Należą do nich
"Napis"
).
#\nazwa-znaku
.
Dla znaków drukowalnych nazwą jest po prostu pojedynczy znak
(np. #\a
, #\A
), inne znaki mają umowne nazwy
(np. #\space
).
3+4i
) oraz
specjalne (np. +inf.0
lub -inf.0
). W Scheme
zakres liczb całkowitych nie jest ograniczony (chyba, że przez konkretną
implementację).
quote
lub poprzedzając identyfikator
apostrofem, zob. dalej). Podobne do typów wyliczeniowych.
Wyrażeniami prostymi są również identyfikatory. W nazwach identyfikatorów można używać liter, cyfr i znaków
= - > < / * ? ! +
Znak ?
stawia się zwykle na końcu nazwy predykatu, zaś znakiem
!
kończy się nazwy funkcji powodujących efekty uboczne ---
modyfikacje stanu obiektów. Znaków ->
używa się wewnątrz nazw
funkcji konwersji typów (np. number->string
), zaś znaku
:
dla symboli z pakietów/modułów do oddzielenia nazwy pakietu
od właściwej nazwy (np. primes:fast-prime?
).
> (+ 1 2.14) 3.14 > (+ 1 (* 2 1.07)) 3.14Jak widać, oba wyrażenia nazywaja tę samą wartość. W powyższym przykładzie symbole
+
i *
oznaczały procedury dodawania i mnożenia.
Formalnie wywołanie procedury ma postać
(operator parametr-1 parametr-n)
Argumenty procedur są przekazywane przez wartość, podobnie zwracane są wyniki. Jednakże większość obiektów Scheme (poza liczbami, wartościami boolowskimi itp.) to kontenery, co pozwala zmieniać takie argumenty. Z perspektywy więc programisty w C argumenty typów złożonych są zawsze przekazywane przez referencję.
Niektóre procedury mają ustaloną liczbę parametrów, inne mają
pewną wymaganą liczbę parametrów, ale akceptują więcej.
Działanie procedury może zależeć od liczby argumentów wywołania, na przykład
procedura /
dla dwóch argumentów zwraca iloraz, natomiast
dla jednego odwrotność (tak jak gdyby pierszy ukryty argument był równy 1.
Konstrukcji define
używa się do związania nazwy z pewnym
obiektem Scheme, na przykład
(define pi 3.14159265)
Zdefiniowanej nazwy można używać jako identyfikatora w wyrażeniach
> pi 3.14159265 > (* 4 pi) 12.5663706Ogólnie konstrukcja
define
ma następującą postać
(define nazwa wyrażenie)
Nazwa nie jest obliczana i powinna być identyfikatorem (ściślej symbolem).
Konstrukcji define
można również używać w definicji funkcji,
treści wyrażenia let
i podobnych kontekstach, ale tylko na
początku bloku. Odpowiada ona wtedy wyrażeniu letrec
.
Do definiowania funkcji używa się najczęściej specjalnej postaci konstrukcji
define
(define (nazwa parameter ...) wyrażenie ...)
(nazwane funkcje można również definiować używając ogólnej postaci
define
i funkcji anonimowych).
Zdefiniujemy funkcję podnoszenia do kwadratu
(define (kwadrat x) (* x x))Tak zdefiniowaną funkcję możemy wywołać tak samo jak funkcje systemowe
> (kwadrat 4) 16
Definicje funkcji mogą zawierać inne, lokalne definicje funkcji, które z kolei mogą zawierać kolejne definicje itd.
W Scheme funkcje są normalnymi obiektami danych, dlatego można je przekazywać jako argumenty i zwracać jako wartości innych funkcji. Nie muszą być nazywane, funkcje anonimowe można tworzyć konstrukcją
(lambda (parametr ...) wyrażenie ...)
Wartością tego wyrażenia jest procedura o podanych parametrach,
która po wywołaniu oblicza kolejno wyrażenia i zwraca wartość
ostatniego. Na przykład wartością
(lambda (x) (* x x))jest jednoargumentowa procedura, zwracająca kwadrat argumentu. Aby użyć lambda wyrażenia w obliczeniu zwykle umieszcza się je w pozycji operatora kombinacji, na przykład podnoszenie liczby 4 do kwadratu zapisać można jako
> ((lambda (x) (* x x)) 4) 16Ogólnie konstrukcji tej można używać wszędzie tam, gdzie może wystąpić nazwa funkcji.
Składanie funkcji (na razie jednoargumentowych) można zdefiniować jako funkcję działającą na funkcjach (,,funkcjonał'')
(define (compose f g) (lambda (x) (f (g x))))używając jej potem następująco
> ((compose kwadrat sin) 2) 0.826821810431806 > (kwadrat (sin 2)) 0.826821810431806
Program w Scheme jest ciągiem definicji i obliczanych wyrażeń. Definicje i wyrażenia mogą występować w dowolnej kolejności, byleby żadna zmienna nie była używana przed jej zdefiniowaniem. Programy mogą być zapisywane na plikach i uruchamiane wsadowo jako skrypty. W trybie wsadowym definicje zwykle poprzedzają obliczane wyrażenia.
Inna możliwość to praca interakcyjna pod nadzorem interpretera. W tym trybie programista podaje kolejno definicje i wyrażenia do wyliczenia, używając debuggera do poprawiania wykrytych błędów.
W programach mogą (i powinny ;-) być umieszczane komentarze. Komentarz jest to dowolny ciąg znaków rozpoczynający się co najmniej jednym średnikiem i ciągnący do końca wiersza. Komentarzy rozpoczynających się kilkoma średnikami używa się konwencjonalnie do wskazywania komentarzy nagłówkowych, odnoszących się do większych fragmentów kodu, np. poprzedzając definicję funkcji opisującym ją komentarze.
Część implementacji akceptuje komentarze blokowe (wielowierszowe),
rozpoczynające się znakami `#|
' i kończące znakami
`|#
'. Można je zagnieżdżać.
Aby wykomentować pojedyncze wyrażenie, można je poprzedzić przez
#;
.
Wartość prawdy jest oznaczana jako #t
, zaś fałsz
jako #f
. Ponadto w wielu kontekstach, np. w wyrażeniach
warunkowych dowolny obiekt różny od #f
również oznacza
prawdę. Nie należy tego nadużywać bez potrzeby.
W Scheme są zdefiniowane predykaty dla wszystkich typów danych. Ich nazwy
zakończone są znakiem ?
. Predykaty te
zwracają #t
lub #f
.
Dla żadnego obiektu nie jest równocześnie prawdziwy więcej niż jeden
spośród następujących predykatów: boolean?
, pair?
,
symbol?
, number?
, char?
,
string?
, vector?
, procedure?
.
Najprostszym wyrażeniem warunkowym jest
(if warunek konsekwencja [alternatywa])Jego wykonanie polega na obliczeniu wartości warunku i zależnie od tego, czy jest on spełniony, obliczenie konsekwencj lub alternatywy. Należy pamiętać, że dowolna wartość różna od
#f
oznacza prawdę, a więc wartością (if 0 1 2)
jest
1
.
Jeśli alternatywa została pominięta, a warunek nie
jest spełniony, to wartość wyrażenia if
nie jest określona.
Takiej postaci wyrażenia if
używa się dla warunkowych efektów
ubocznych, dla czytelności lepiej jednak skorzystać wtedy z (nieco
mocniejszych) wyrażeń when
lub unless
.
Wyrażenie when
ma postać
(when warunek wyrażenie wyrażenie ...)
Jeśli warunek jest spełniony, to oblicza się kolejno
wyrażenia i zwraca wartość ostatniego z nich, w przeciwnym razie
wartość wyrażenia nie jest określona.
Wyrażenie
(unless warunek wyrażenie wyrażenie ...)
jest przeciwieństwem when
. Wyrażenia są obliczane
tylko wtedy, gdy warunek nie jest spełniony.
Najogólniejszym (i najstarszym) wyrażeniem warunkowym jest
(cond (warunek wyrażenie ...) (warunek wyrażenie ...) ... [(else wyrażenie ...)])
Oblicza się kolejno warunki tak długo, aż któryś z nich będzie spełniony. Jeśli któryś warunek będzie spełniony, to oblicza się kolejno odpowiadające mu wyrażenia i zwraca ostatnio obliczoną wartość (czyli jeśli po warunku nie następują żadne wyrażenia, to zwraca się po prostu wartość warunku).
Jeśli żaden warunek nie był spełniony, to wartość
wyrażenia cond
nie jest określona. Jako ostatniego warunku
można opcjonalnie użyć słowa kluczowego else
, oznacza ono
warunek zawsze spełniony (jest to typowy lukier składniowy, ponieważ tę
samą rolę doskonale spełniłby warunek #t
, a także jakakolwiek
inna stała różna od #f
).
Wyrażenie
(case klucz ((wartość wartość ...) wyrażenie ...) ((wartość wartość ...) wyrażenie ...) ... [(else wyrażenie ...)])stanowi uproszczoną wersję wyrażenia
cond
. Oblicza się
wartość klucza i kolejno porównuje z każdą
z wartościami z kolejnych klauzul. Gdy są równe, oblicza się
kolejno wyrażenia tej klauzuli i zwraca wartość ostatniego.
Jeśli nie znaleziono pasującej wartości, to wartość całego
wyrażenia nie jest określona. Aby tego uniknąć zamiast
listy wartości można w ostatniej klauzuli użyć stałej
składniowej else
, pasujacej do każdego klucza.
Poza wyrażeniami warunkowymi inne konstrukcje syntaktyczne to:
(quote obiekt) lub 'obiekt
Oba powyższe wyrażenia zwracają po prostu obiekt (bez obliczania jego wartości). Pozwala to zapobiegać obliczeniu stałych wyglądających jak wyrażenia, np. wyrażenie'(1 2 3)
zwraca listę(1 2 3)
.
(and wyrażenie ...)
Oblicza kolejno wyrażenia do momentu, gdy wartością któregoś
będzie fałsz i wtedy zwraca #f
. W przeciwnym razie zwraca
wartość ostatniego wyrażenia.
(or wyrażenie ...)
Oblicza kolejno wyrażenia do momentu, gdy wartością któregoś
nie będzie fałsz i wtedy zwraca tę wartość. W przeciwnym razie (tzn.
gdy wszystkie obliczenia dały fałsz) zwraca wartość #f
.
(not wyrażenie)
Jeśli wartością wyrażenia jest#f
, to zwraca#t
, w przeciwnym razie zwraca#f
(not
jest więc zwykłą funkcją, ale dobrze pasuje w tym miejscu ;-).
> (not #t) #f > (not 3) #f > (not (list 3)) #f > (not #f) #t
(let ((zmienna wyrażenie-inicjujące) ...) wyrażenie ...)
Służy do wprowadzenia bloku ze zmiennymi lokalnymi. Oblicza wyrażenia-inicjujące i związuje z odpowiednimi zmiennymi. W tak utworzonym środowisku lokalnym oblicza kolejno wyrażenia i zwraca wartość ostatniego z nich.
(let* ((zmienna wyrażenie-inicjujące) (zmienna wyrażenie-inicjujące) ...) wyrażenie ...)
Odmiana let
, w której ale
obliczanie wyrażeń-inicjujacych odbywa się po kolei, przy czym
obliczanie każdego wyrażenia-inicjującego odbywa się w
środowisku, w którym poprzednie zmienne są są już zainicjowane.
(letrec ((nazwa wyrażenie-definiujące) ...) wyrażenie ...)
Umożliwia tworzenie lokalnych funkcji (wzajemnie) rekurencyjnych. Oblicza wyrażenia-definiujące w środowisku, w którym widoczne są wszystkie (niezainicjowane) nazwy i wiąże je z nazwami w tym środowisku. Następnie oblicza kolejno wyrażenia i zwraca wartość ostatniego.W wyrażeniach-definiujących mogą wystąpić odwołania do nazw. Musi być jednak możliwe ich obliczenie, przy założeniu, że nazwy te nie są związane z żadnymi wartościami. W praktyce oznacza to, że wartościami wyrażeń-definiujących powinny być procedury lub zbudowane z nich struktury danych (najczęściej są to po prostu lambda-wyrażenia).
(set! zmienna wyrażenie)
Przypisuje zmiennej wartość wyrażenia.
(begin wyrażenie wyrażenia ...)
Oblicza kolejno wyrażenia i zwraca wartość ostatniego. Przydatne do umieszczenia ciągu wyrażeń wszędzie tam, gdzie dozwolone jest tylko pojedyncze wyrażenie. Oczywiście wszystkie wyrażenia poza ostatnim powinny wywoływać efekty uboczne, w przeciwnym razie ich obliczanie nie ma sensu.
(delay wyrażenie)
Służy do leniwego obliczania. Tworzy i zwraca zamrożone wyrażenie.
Obiekt taki może być argumentem force
, co spowoduje obliczenie
wyrażenia.
(force zamrożone-wyrażenie)
Jeśli zamrożone-wyrażenie nie było jeszcze obliczane, to jest ono obliczane, a otrzymana wartość zapamiętywana w nim i zwracana. W przeciwnym razie zwracana jest po prostu zapamiętana wartość.
Lepiej używać define
niż define-syntax
(lub define-macro
) do definiowania operacji.
Jeśli użyje się define-syntax
(lub define-macro
),
to nie będzie można używać tej nazwy jako parametru funkcyjnego
(define-syntax nazwa (syntax-rules (klucz ...) ((wzorzec rozwinięcie) ...)))
Definiuje rozszerzenie składniowe rozpoczynające się podaną nazwą. Klucze mogą wystąpić we wzorcach, gdzie traktowane są jako stałe. Pozostałe symbole występujące we wzorcach pełnią rolę zmiennych składniowych.
> (define-syntax decr! (syntax-rules () ((decr! var) (decr! var 1)) ((decr! var val) (set! var (- var val)))) decr! > (define n 10) 10 > (decr! n) 9 > (decr! n 2) 7 > n 7
(eq? x y)
Zwraca #t
jeśli wartością x i y jest
ten sam obiekt.
(equal? x y)
Zwraca #t
jeśli wartości x i y są tak
samo zbudowane, co w zasadzie oznacza, że są tak samo wypisywane.
(= x y)
Tylko do porównywania liczb.
Mały przykład użycia funkcji wielowartościowej.
Użyjemy standardowych konstrukcji values
i
call-with-values
:
> (call-with-values (lambda () (values 6 7)) *) 42Jak widać, pierwszy argument
call-with-values
to procedura,
która zwraca wiele wartości, a drugi to procedura, którą można wywołać
z tymi wartościami.
Pamięć dla dowolnych obiektów tworzonych w programie jest przydzielana i zwalniana automatycznie (służy do tego mechanizm odśmiecania. Standard Scheme określa następujące typy danych, gwarantując ich rozłączność:
Poznaliśmy już wartości logiczne #t
i #f
,
służace do reprezentowania prawdy i fałszu. Omówimy teraz pokrótce inne
typy danych występujące w Scheme.
Znaki obejmują w zasadzie lokalny zbiór znaków (na przykład ASCII). Znak nie równoważny jednoznakowemu napisowi.
Napisy to ciągi 0 lub więcej znaków. Nie ma ograniczenia na długość napisu.
(string-ci< napis1 napis2 [start1 end1 start22 end22])
Porównują wycinki napisów, ignorując rozróżnienie duże/małe litery. Jeśli pierwszy wycinek jest krótszy niż drugi i jest jego prefiksem, tostring-ci<
zwraca#t
.
Procedury w Scheme to obliczeniowe realizacje funkcji. Scheme zawiera wiele wbudowanych procedur (standardowych i bibliotecznych), mogą one też być definiowane przez użytkownika.
Jest jednak trzecia możliwość: procedury mogą być dynamicznie tworzone w trakcie obliczania jako wartośći obliczonych wyrażeń. Takie skonstruowane procedury mogą być natychmiasy wywołane lub zapisane w jakiejś strukturze danych.
Porty to interfejsy do wczytywania i wypisywania danych, zwykle powiązane z plikami.
Standard Scheme obejmuje też specjalne obiekty końca pliku (eof-objects) zwracane przez operacje czytające z portu, gdy nie ma już więcej danych.
car
i cdr
, nazwane tak od funkcji dostępu do nich.
Reprezentacja zewnętrzna par używa notacji ,,kropkowej''
(c1 . c2)
,
gdzie c1 to wartość składowej car
,
zaś c2 składowej cdr
, np. parę
zawierającą liczby 3 i 5 zapisuje się jako
(3 . 5)
Uwaga: jest to zewnętrzna reprezentacja pary, a nie wyrażenie, którego wartością jest para.
Lista jest ciągiem elementów, zapisywanym
(1 4 9 16 25)
Wyróżnionym rodzajem listy jest lista pusta, zapisywana jako
()
Inne listy buduje się z par w taki sposób, że car
każdej pary
zawiera kolejny element listy, zaś cdr
wskazuje na następną parę
tworzącą listę (czyli na resztę listy). Jako cdr
pary
zawierającej ostatni element listy umieszcza się listę pustą. Tak
więc listę (2 3 5 7)
można również zapisać jako
(2 . (3 . (5 . (7 . ()))))
Tak więc lista jest w rzeczywistości albo parą albo listą pustą.
(cons ob1 ob2)
Tworzy i zwraca parę której pierwszym elementem jest wartość ob1, zaś drugim wartość ob2. Jeśli ob2 jest listą, dołącza ob1 na początek listy ob2.
(cons 'a '()) ==> (a) (cons '(a) '(b c d)) ==> ((a) b c d) (cons "a" '(b c)) ==> ("a" b c) (cons 'a 3) ==> (a . 3) (cons '(a b) 'c) ==> ((a b) . c)
(car para)
Zwraca pierwszy element pary (czyli także pierwszy element listy rozpoczynającej się tą parą).
(car '(a b c)) ==> a (car '((a) b c d)) ==> (a) (car '(1 . 2)) ==> 1 (car '()) ==> error
(cdr l)
Jeśli l jest parą, to zwraca jej drugi element. Dla listy (niepustej) zwraca resztę listy (tzn. listę bez pierwszego elementu).
(null? x)
Zwraca#t
jeśli x jest listą pustą, w przeciwnym razie#f
.
(list element ...)
Tworzy listę z podanych elementów.
(list 'a (+ 3 4) 'c) ==> (a 7 c) (list) ==> ()
(list-ref l k)
Zwraca k-ty element listy l.
(length l)
Zwraca długość (liczbę elementów) listy l.
(append lista ...)
Tworzy nową listę powstałą ze złączenia podanych list.
(reverse l)
Zwraca nową listę zawierającą elementy listy l w odwrotnej kolejności.
(member obiekt l)
Przegląda listę l w poszukiwaniu elementu równego (equal?
) obiektowi. Zwraca podlistę (,,ogon l''), rozpoczynającą się od znalezionego elementu. Jeśli takiego elementu nie ma zwraca#f
.
(memq obiekt l)
Podobna domember
, ale używa funkcjieq?
do porównywania.
(set-car! para obiekt)
Przypisuje (destrukcyjnie) pierwszemu elementowi pary obiekt. W przypadku listy zmienia pierwszy element listy.
(set-cdr! para obiekt)
Przypisuje (destrukcyjnie) obiekt drugiemu elementowi pary. W przypadku listy zmienia resztę listy. Jeśli obiekt nie jest listą, to para przestanie być listą!
(append! lista ...)
Destrukcyjnie łączy ze sobą listy, tzn. zamieniacdr
ostatniej pary tworzącej listę (gdzie poprzednio było()
) na dowiązanie do pierwszej pary następnejlisty
. Zwraca pierwszą listę (oczywiście trwale zmodyfikowaną).
(klucz . wartość)
(assoc klucz a-lista)
Znajduje pierwszą pozycję o podanym kluczu (używającequal?
do porównywania) i zwraca ją. Jeśli takiej pozycji nie było, to zwraca#f
.
(assq klucz a-lista)
Podobna doassoc
, ale używa funkcjieq?
do porównywania.
Symbole są to obiekty bez żadnej struktury wewnętrznej, odróżniane na
podstawie unikalnej nazwy (przypominają nieco elementy typów
wyliczeniowych). Z każdym symbolem może być związana jego wartość.
Wartość globalną nadajemy symbolowi używając define
.
Liczby dzielą się na 4 podtypy, każdy zawarty w następnym: całkowite, wymierne, rzeczywiste i zespolone.
Typ całkowity różni się od spotykanego w innych językach (na przykład
int
w C tym, że nie ma ograniczeń na minimalną ani maksymalną
wartość.
Liczby rzeczywiste są zwykle równoważne lokalnej wersji typu
double
z C, zgodnego ze standardem IEEE.
Scheme rozróżnia liczby dokładne (exact) o gwarantowanej jakości matematycznej oraz liczby przybliżone (inexact), których wewnętrzna reprezentacja jest tylko aproksymacją. Teoretycznie cecha ta nie jest związana z typem liczby. W praktyce typy całkowite i wymierne są dokładne, zaś rzeczywiste i zespolone przybliżone.
(= x y) (< x y) (> x y) (<= x y) (>= x y)
Klasyczne predykaty arytmetyczne.
Porównując liczby reprezentowane dokładnie (exact) i niedokładnie (inexact) należy pamiętać, że czasem wynik porównania zależy od implementacji
> (= 1 1.0) #t > (= 1/2 0.5) #t > (= 1/10 0.1) #fA oto przyczyna nieszczęścia
> (inexact->exact 0.1) 3602879701896397/36028797018963968
(max x ...) (min x ...)
Zwracają maksimum i minimum z podanych liczb.
(gcd x ...) (lcm x ...)
Zwracają największy wspólny dzielnik oraz najmniejszą wspólną wielokrotność podanych liczb x. Wynik jest zawsze nieujemny.
(+ x ...) (* x ...) (- x ...)
Klasyczne operacje arytmetyczne
(/ x ...)
Dzielenie, wynik zależy od typów argumentów, np. dla liczb całkowitych będzie to liczba wymierna lub całkowita.
(- x) (/ x)
Szczególne przypadki poprzednich: negacja i odwrotność.
(expt x y)
Potęgowanie, podnosi x do potęgi y.
(quotient n m)
Dzielenie całkowite.
(remainder dzielna dzielnik)
Reszta z dzielenia całkowitego, ma znak dzielnej.
(modulo dzielna dzielnik)
Reszta z dzielenia całkowitego, ma znak dzielnika.
(zero? x) (positive? x) (negative? x)
Sprawdzają, czy x jest zerem, liczbą dodatnią, liczbą ujemną.
(even? x) (odd? x)
Sprawdzają czy x jest liczbą parzystą lub nieparzystą.
(1+ x) (1- x)
Skrótowy zapis dla(+ x 1)
oraz(- x 1)
(abs x)
Wartośc bezwzględna liczby x.
(floor x)
Najbliższa liczbie x liczba całkowita i taka, że |i| <= |x|.
(ceiling x)
Najbliższa liczbie x liczba całkowita i taka, że |i| >= |x|.
(round x)
Najbliższa liczbie x liczba całkowita, w przypadku ,,remisu'' wybiera liczbę parzystą.
(sqrt x)
Pierwiastek kwadratowy z x.
(sin x) (cos x) (tan x)
Funkcje trygonometryczne.
(asin x) (acos x) (atan x)
Odwrotne funkcji trygonometryczne.
(exp x)
Liczba niewymierna e
podniesiona do potęgi x.
(log x)
Logarytm naturalny z x.(log 0)
zwraca-inf.0
.
Wektory są to tablice jednowymiarowe, indeksowane zawsze od zera. Pojedynczy wektor może zawierać obiekty różnych typów.
(make-vector n [wartość-początkowa])
Tworzy nowy wektor składający się z n elementów. Jeśli podano wartość-początkową, to wszystkie elementy są nią zainicjowane, w przeciwym razie wartości początkowe elementów nie są określone.
(vector element ...)
Tworzy i zwraca wektor składający się z podanych elementów.
(vector-length wektor)
Zwraca długość wektora (liczbę jego elementów, czyli jego rozmiar).
(vector-ref wektor i)
Zwraca i-ty element vektora.
(vector-set! wektor i w)
Przypisuje i-temu elementowi wektora wartość w.
(apply procedura argumenty) (apply procedura arg1 ... argumenty)
Wywołuje procedurę od podanej listy argumentów.
W pierwszej postaci wywołuje procedurę dla argumentów zawartych
w argumenty. Druga postać to uogólnienie pierwszej:
procedura dostaje listę argumentów postaci
(append (list arg1 ...) argumenty)
.
(apply + (list 3 4)) ==> 7 (define compose (lambda (f g) (lambda args (f (apply g args))))) ((compose sqrt *) 12 75) ==> 30
(for-each procedura lista lista ...)
Podobna do map
, ale nie zwraca żadnego wyniku. Ponieważ jest
używana dla efektów ubocznych, więc wykonuje obliczenia w ustalonej
kolejności.
(filter predykat l)
Zwraca listę zawierającą tylko te elementy listy l, dla których predykat był spełniony.
(find predykat list)
Zwraca pierwszy element listy spełniający predykat.
(every predykat l ...)
Zwraca #t
jeśli predykat jest spełniony dla
wszystkich elementów list l. Gdy jest więcej niż jedna
lista, kończymy po wyczerpaniu najkrótszej.
(append-map procedura lista)
Działa jakmap
, ale konkatenuje wyniki (używającappend
).
> (append-map (lambda (x) (list (+ x 1) (- x 1))) '(1 2 3)) (2 0 3 1 4 2)
(map procedura lista lista ...)
Wywołuje się procedurę kolejno od pierwszych elementów list, drugich elementów list itd. Otrzymane wynki zwraca się w postaci listy. Listy nie muszą być tej samej długości, ale ma ich być tyle, ilu argumentów oczekuje procedura. Uwaga: kolejność obliczeń nie jest określona.
(any predykat l ...)
Wywołuje predykat od kolejnych elementów l i zwraca pierwszy wynik różny od#f
. Gdy nie ma takiego elementu zwraca#f
.
(write wyrażenie [port]) (display wyrażenie [port])
Obliczają wyrażenie i wypisuje wynik na podany port (domyślnie bieżący).display
wypisuje w ładny sposób, natomiastwrite
w taki sposób, aby wypisaną wartość można było z powrotem wczytać.
(close-input-port port)
Zamyka port wejściowy port. Od tego momentu nie jest możliwe pobieranie zeń dalszych znaków.
(close-output-port port)
Zamyka port wyjściowy port. Od tego momentu nie jest możliwe wypisywanie nań żadnych znaków.
(newline [port])
Powoduje przejście do nowej linii podanym porcie wejściowym.
(force-output [port])
Wypisuje na na wskazany port wejściowy wszystkie znaki czekające w buforze. Zwykle nie ma sensu dla plików, lecz jedynie dla portów ,,interakcyjnych''.
Strumienie podobnie jak listy służą do reprezentowania ciągów elementów. W odróżnieniu od list mogą być (potencjalnie) nieskończone, ponieważ obliczenie reszty strumienia (,,ogona'') jest odraczane. Poszczególne części są tworzone dopiero przt próbie dostępu.
W Guile wymagają użycia
(use-modules (ice-9 streams))
Przykład użycia:
(define (integers-from n) (make-stream (lambda (state) (cons state (+ state 1))) n)) (define (print-stream-with-limit stream n) (unless (or (= n 0) (stream-null? stream)) (display (stream-car stream)) (newline) (print-stream-with-limit (stream-cdr stream) (- n 1)))) > (print-stream-with-limit (integers-from 14) 6) 14 15 16 17 18 19
(make-stream procedura x)
Zwraca strumień o pierwszym elemencie x, a wywołanie procedury od bieżącego stanu powinno zwrócić parę z tym i następnym stanem. Przed skonstruowaniem strumienia oblicza x, natomiast dalsze obliczenie jest odraczane.
(stream-car s)
Zwraca pierwszy element strumienia s.
(stream-cdr s)
Wymusza częściowe obliczenie reszty strumienia s i zwraca wynik.
(stream-null? s)
Sprawdza, czy strumień s jest pusty.
(list->stream lista)
Zwraca strumień równoważny podanej liście.
W standardzie nowa konstrukcja case-lambda
. Polecam.
lambda*
, define*
: ze SRFI-89.
Jest w Guile, brak w Gauche i Gambit.
Ogólne uwagi o mocnych stronach niektórych implementacji
Implementacja Scheme z MIT, miejsca powstania języka. Obejmuje edytor podobny do Emacsa o nazwie edwin. MIT-Scheme można ściągnąć stąd.
Gambit to implementacja autorstwa Marca Feeley z University of Montreal. Ściągamy go stąd.
Wersja na Macintosha zawiera własne środowisko deweloperskie, na innych platformach trzeba mieć osobno edytor (najlepiej Emacs -- ma specjalny tryb dla Gambita).
Interpreter Gambit (nazywa się gsi) domyślnie nie akceptuje symboli o nazwach zakończonych dwukropkiem. Można to zmienić pisząc
(set-keywords-allowed! #f)(na przykład umieścić w pliku inicjalnym
~/.gambcini
).
DrScheme powstało na Rice University, ale jest rozwijane przez całą grupę PLT. Obejmuje system modułów, FFI i wspaniałe środowisko programistyczne. Więcej informacji tutaj.
System modułów jest prawidłowo zintegrowany z makrami. Moduły mogą być pisane w różnych językach, na przykład w różnych dialektach Scheme. Pobieranie elementów innych modułów jest bardzo elestyczne, na przykład jeśli chcemy załadować wszystkie eksporty z biblioteki z wyjątkiem a, b i c, piszemy:
(require (all-except (lib "lib.ss" "collection") a b c))
Biblioteka "class.ss" z zestawu MzLib dostarcza podystem klas.
Pliki inicjalne to "~/.mzschemerc" dla MzScheme oraz "~/.mredrc" dla MrEd.
Kolekcja parser-tools
wspiera generowanie lekserów i
parserów dla gramatyk LALR(1). W odróżnieniu od klasyczneg systemu
z Lex i Yacc, cała specyfikacja gramatyki i dodatkowego kodu jest
w pojedynczym pliku. Jako przykład dołączono translator dla Algolu 60.
Scheme 48 to mała, przenośna implementacja Scheme autorstwa Jonathana Rees i Richarda Kelsey. Jest oparta na maszynie wirtualnej (nie kompiluje do kodu binarnego) napisanej w PreScheme. Strona domowa znajduje się tu
Dużo dodatkowego kodu (np. tablice haszujące) znajduje się w module Big Scheme.
Scsh to shell Unixowy używający Scheme jako języka skryptowego, jak też duża liczba funkcji do programowania systemowego zanurzonych w tym języku. Autorami są Olin Shivers i Brian Carlstrom, użyto implementacji Scheme 48. Dostępny jest tu.
Ma służyć głownie do pisania skryptów, choć oczywiści praca interakcyjna jest możliwa.
Specjalna wysokopoziomowa notacja dla procesów: obejmuje uruchamianie programów, tworzenie potoków i przekierowanie strumieni I/O redirection. Na przykład potok z klasycznego shella
gunzip < paper.tex.gz | detex | spell | lpr -Ppulp &zapisuje się w Scsh tak
(& (| (gunzip) (detex) (spell) (lpr -Ppulp)) ;Background a pipeline (< paper.tex.gz)) ;with this redirection
Ta notacja jest poprzez makra zintegrowana ze standardowym kodem w Scheme.
Scsh obejmuje niskopoziomowy dostęp do usług systemu operacyjnego, zwykkle spotykany w języku C. Jest dostępny cały interfejs Posixa plus wiele rozszerzeń: gniazdka, fork, exec, wait, read, write, open i close, seek i tell, stat, chmod/chgrp/chown, symlink, FIFO, dostęp do katalogów, obsługa tty i pty, blokady na plikach, potoki, select, dopasowywanie nazw plików, obsługa czasu i dat, zmienne środowiskowe itd.
Jest wiele dobrych książek o Scheme, klasyka to "Structure and Interpretation of Computer Programs" by Harold Abelson and Gerald Jay Sussman, MIT Press, 1985 .
Lżejsza lektura to "Scheme and the Art of Programming" by George Springer and Daniel P. Friedman, MIT Press, 1989.
Dybvig. ``The Scheme Programming Language''. Pełen tekst na www.scheme.com/tspl2d