Mathematica — definiranje vlastitih funkcija

Delayed (odgođena) evaluacija

Odgođena pravila

Rekli smo da je u Mathematici svaka evaluacija match nekog patterna na expression, i rewrite zamjenom onog što je match-nuto onim što je na desnoj strani pravila (označimo tu desnu stranu s D). No kako je D ponovo expression, postavlja se pitanje kad se on evaluira: prije uvrštavanja u početni expression, ili poslije? Ako se evaluira prije, možemo ga slobodno evaluirati čim zadamo pravilo:

_->1+1

Time smo zadali pravilo koje svaki expression zamjenjuje s vrijednošću od 1+1. Odnosno, s 2. S druge strane, možemo propisati da se D evaluira tek prilikom rewrite faze: to činimo upotrebom operatora :> umjesto -> za pravilo:

_:>1+1

Ovo je drugačije pravilo, i kaže da se svaki expression zamjenjuje s čitavim expressionom 1+1. Vidimo da desna strana ovaj put nije evaluirana, i bit će evaluirana tek kad se pravilo primijeni na nešto:

nekiizraz/.%

Naravno, ako je D aritmetički izraz poput 1+1, praktički nam je svejedno hoćemo li ga evaluirati prije ili poslije. No ponekad nam nije svejedno:

{x,x}/.x->RandomReal[]

{x,x}/.x:>RandomReal[]

Vidimo razliku: ako se RandomReal[] evaluira čim se zada pravilo, pravilo će biti naprimjer x->0.53, i oba x-a zamijenit će se istom vrijednošću. No ako upotrijebimo odgođeno pravilo, RandomReal[] se evaluira tek kad se pravilo primjenjuje, dakle dvaput, i naravno dobivamo različite vrijednosti.

Naravno, nisu slučajni brojevi jedino mjesto gdje odgođena pravila donose razliku:

7/.x_->Length[x+5]

7/.x_:>Length[x+5]

Objašnjenje: x+5, odnosno Plus[x, 5] je expression duljine 2, dakle prvo pravilo je jednostavno da se bilo što zamijeni s 2. Primijetimo da je potpuno nebitno što se "bilo što" zove x ovdje — čak nam Mathematica syntax coloring pomaže, jer vidimo da x s desne strane pravila uopće nije iste boje kao s lijeve. Drugo pravilo pak prvo zamjenjuje 7 izrazom Length[7+5], što se onda evaluira kao Length@12, što je 0 jer je 12 atom (broj).

Pridruživanje varijabli

Kad napišemo "varijabla=vrijednost", zapravo možemo misliti da na popis Mathematicinih pravila koja se automatski primjenjuju na svaki izraz dodajemo i pravilo "varijabla->vrijednost". To nije potpuno točno, jer postoje izrazi koji mogu "zalediti" određene dijelove od evaluacije, ali za većinu primjera je dovoljno dobro. Ono što je bitno je da pridruživanjem možemo postići da je takvo pravilo odgođeno, samo upotrijebimo ":=" umjesto "=" — na taj način dodajemo pravilo "varijabla:>vrijednost", i vrijednost će se evaluirati tek nakon rewrite faze.

slucajnibroj=RandomReal[]

Table[slucajnibroj,{5}]

slucajnibroj:=RandomReal[]

Table[slucajnibroj,{5}]

Primijetimo da u slučaju pridruživanja s =, output je ono što je pridruženo (kao u C-u), dakle neki konkretni slučajni broj poput 0.42; dok u slučaju odgođenog pridruživanja (s :=), outputa uopće nema, jer se ništa ne evaluira dok se slucajnibroj ne "pozove".

Definiranje funkcija

Sada je jasno da na lijevoj strani pridruživanja, baš kao i na lijevoj strani pravila, ne mora stajati samo varijabla, već proizvoljni (ne baš, ali skoro) pattern koji može u sebi imati blankove.

konstanta@_=17

konstanta

konstanta@5

Vidimo da se sam izraz konstanta ne evaluira, jer ne match-a lijevu stranu konstanta@_; ali konstanta@5 jest tog oblika, i zato se zamjenjuje desnom stranom 17. Tako možemo reći da smo definirali "funkciju" koja svakom izrazu pridružuje 17. Naravno, obično želimo da nam vrijednost funkcije ovisi o argumentu, te moramo koristiti imenovane blankove. Iz onog što smo gore vidjeli, (x koji nije zelen ako pravilo nije odgođeno), obično je dobro u takvim slučajevima koristiti odgođeno pridruživanje:

kvadrat@x_:=(x^2)

kvadrat@I

hipotenuza[a_,b_]=(Sqrt[kvadrat@a+kvadrat@b])

hipotenuza[5,12]

Opet, ako je funkcijska vrijednost samo neki obični aritmetički izraz, svejedno je koristimo li odgođeno ili obično pridruživanje; ali ako se radi o kompliciranijim stvarima, dobro je uvijek koristiti :=. Naprimjer, Expand služi za zapisivanje polinoma u kanonskom obliku (linearna kombinacija umnožaka potencija varijabli):

raspisikvadrat@x_=Expand@kvadrat@x

raspisikvadrat[a+b]

raspisikvadrat@x_:=Expand@kvadrat@x

raspisikvadrat[a+b]

Vidimo da prva definicija ne radi, jer je kvadrat@x već u Expand-anom obliku, i Expand na njemu ne napravi ništa. Ono što želimo je uvrstiti vrijednost za x, kvadrirati, i Expand-ati tako dobiveni rezultat — to postižemo upotrebom odgođenog pridruživanja (jer Expand želimo pozvati tek kad znamo točno što je x — dakle, prilikom poziva funkcije raspisikvadrat, a ne prilikom njene definicije).

Poništavanje pridruživanja

Ako smo nekoj varijabli pridružili vrijednost (bilo pomoću običnog, bilo pomoću odgođenog pridruživanja), a ubuduće bismo tu varijablu htjeli koristiti simbolički, možemo maknuti to pridruživanje pomoću operatora "=.":

x=5

∫x\[DifferentialD]x

x=.

∫x\[DifferentialD]x

Ako se ne radi o vrijednosti varijable, već nekog kompliciranijeg patterna, moramo paziti na na čitav pattern primijenimo "=.".

??konstanta

konstanta=.

konstanta@2

konstanta@_=.

konstanta@3

hipotenuza[_,_]=.

Zadnja naredba ne radi jer Mathematica razlikuje imenovane i neimenovane blankove. Puno je jednostavnije umjesto hrpe =.-ova i razmišljanja kako smo što definirali, za undefiniranje funkcija koristiti Clear:

Clear@hipotenuza

hipotenuza[3,5]

(Clear možemo koristiti i za varijable, ali =. je kraće.:) Ako želimo obrisati neki simbol u potpunosti, koristimo Remove.

Vezanost pridruživanja na simbole

Automatsko vezanje na head

Rekli smo da je pridruživanje otprilike kao dodavanje novih pravila na popis svih pravila koje Mathematica automatski primjenjuje. No treba primijetiti: takvih pravila ima jako puno. Proći kroz lijeve strane svih njih da bi se vidjelo postoji li match s nekim dijelom unesenog expressiona, bilo bi presporo. Zato Mathematica razdvaja automatska pravila u grupe tako da ih veže uz određene simbole koji se u njima pojavljuju. Ako ne kažemo drugačije prilikom pridruživanja, pravilo dobiveno pridruživanjem veže se na head lijeve strane (ili čitavu lijevu stranu ako je ona atom). Dakle, na primjer, definicija kvadrat@x_:=(x^2) veže se na simbol kvadrat. To znači da će se pravilo pokušati match-ati samo ako se unutar expressiona negdje nalazi simbol kvadrat. To je očita optimizacija, ali loša strana je što ne možemo baš svaki pattern pretvoriti u pridruživanje. Na primjer, želimo napraviti objekt neZnam, koji će prilikom primjene bilo koje unarne funkcije ponovo davati odgovor neZnam. Pomoću eksplicitnih pravila to je vrlo lako:

{neZnam,3+neZnam,Sin@π,Sin@neZnam,Log@neZnam}/._@neZnam->neZnam

Ali ako to pokušamo napisati kao pridruživanje, imamo problem: Mathematica ne zna na koji simbol vezati pravilo, jer je head lijeve strane blank:

_@neZnam=neZnam

Promjena vezanja na ostale dijelove

Gornje pridruživanje se može implementirati u Mathematici, samo moramo koristiti ^= (ili ^:= za odgođeno pridruživanje). Na taj način kažemo Mathematici da pravilo ne veže za head, već za ostale dijelove lijeve strane (na razini 1).

Clear@neZnam

_@neZnam^=neZnam

Sin@neZnam

Sin@(π/3)

Definiranje unarnih Protected funkcija na našim simbolima

To ne moramo koristiti samo kad je head lijeve strane blank — korisno je i kad je to Protected simbol. Na primjer, želimo konstruirati objekt cudnikut koji ima svojstvo Cos@cudnikut==2. Pravila vezana za Cos ne možemo mijenjati, ali zato možemo to pravilo vezati za cudnikut:

Cos@cudnikut=2

Cos@cudnikut^=2

Cos@(π/3)

Cos@cudnikut

??Cos

??cudnikut

Smisleno grupiranje definicija

Još jedna korisna upotreba ^= je kad želimo na jednom mjestu imati sve definicije vezane uz određeni simbol, kad je prirodno taj simbol shvatiti kao argument funkcije:

povrsina@krug@r_^:=(r^2)π

opseg@krug@r_^:=2r π

promjer@krug@r_^:=2r

povrsina@krug@5

(Primijetimo korištenje ^:=, dakle odgođenog pridruživanja, jer imamo parametar r. Nije bilo nužno ga koristiti, jer su desne strane aritmetički izrazi, ali ne škodi.) Da smo koristili obično pridruživanje umjesto ^-pridruživanja gore, imali bismo gornja tri pravila razbacana po raznim simbolima — povrsina, opseg i promjer. Ovako ih imamo sve na jednom mjestu, pod simbolom krug.

??krug

??povrsina

Vezanje na ručno specificirani simbol

Ponekad promjena vezanja sa ^= nije baš ono što nam treba. Na primjer, recimo da želimo definirati broj sss koji je sam svoj sljedbenik:

sss+1=sss

sss+1^=sss

Vidimo da ni ^= nije baš zadovoljan, jer pokušava vezati pravilo na sve dijelove od sss+1. Naravno, 1 ne želi da se na njega vežu neka pravila. ☹

Trebamo način da točno kažemo na koji simbol želimo vezati pravilo generirano pridruživanjem. To postižemo operatorom /: (preciznije, ternarnim operatorom /:=).

sss/:sss+1=sss

sss+1

Naravno, moramo vezati na simbol koji se doista pojavljuje na lijevoj strani, inače nema velike koristi od našeg pridruživanja:

proba/:1+1=3

No nije dovoljno da se simbol na koji vežemo nalazi na lijevoj strani pridruživanja; mora se nalaziti dovoljno plitko (na razini 0 ili 1) da ga Mathematica optimizator matchinga može pronaći:

proba/:Sin@Cos@proba=5

Specijalni slučajevi

Neke funkcije tretiraju pridruživanja na specificiranim argumentima na drugačiji način od gore opisanog. Na primjer, N ili NumericQ.

N[champernowne,n_]:=FromDigits@{Join@@IntegerDigits@Range@n,0}

N[champernowne,100]

??N

??champernowne

Vidimo da je algoritam za računanje približne vrijednosti vezan na simbol champernowne automatski, ne na N (koji je Protected).
NumericQ je još čudniji: možemo pridružiti samo logičke vrijednosti, i informacija o tome nije prikazana nigdje vidljivo.

NumericQ@champernowne

NumericQ@champernowne=True

??champernowne

??NumericQ

NumericQ@(champernowne^2)

NumericQ@x:=x