Zmienne i typy danych
Działanie komputera bardzo dobrze wyjaśnia idea systemu iteracyjnego. Strukturę systemu iteracyjnego (a co za tym idzie – strukturę komputera) można postrzegać jako złożoną funkcję logiczną1 (pamięć jako zbiór wartości logicznych), jako kalkulator przetwarzający liczby albo jako automat przetwarzający informacje (pamięć jako zbiór bitów informacji). Należy przy tym podkreślić, że te interpretacje nie mają wpływu na działanie maszyny. Są to tylko nasze wyobrażenia. Nie są one jednak bez znaczenia, gdy chcemy budować struktury maszyn (programować je).
Programista dzieli pamięć komputera na komórki zawierające określone dane (dane logiczne, liczby czy inne informacje). Komórkom tym nadaje najczęściej nazwy (identyfikatory), by móc do nich się odwoływać. W ten sposób rodzi się pojęcie zmiennej, czyli nazwanej komórki pamięci.
Najczęściej zakłada się, że zmienna będzie zawierać dane o określonych właściwościach. Mówimy, że właściwości te są związane z typem danych. W najprostszym ujęciu typ to pewien ustalony zbiór wartości (w domyśle: które mogą być przyjmowane przez zmienne). W praktyce z każdym typem związany jest zbiór operacji, które można wykonywać na wartościach z tego typu. Tak postawiona definicja bliska jest klasycznemu pojęciu zbioru — z konieczności skończonego, gdyż pamięć mamy skończoną. [Paradygmaty_programowania]
Przykładami typów danych są liczby wymierne (float) lub całkowite (int)2.
Definicja zmiennej obejmuje zatem miejsce w pamięci (adres), identyfikator (nazwę), typ danych oraz wielkość (ilość bitów pamięci). Dla programisty adres bywa nieistotny (o rozmieszczenie zmiennych w pamięci dba system), a wielkość wynika z typu danych.
Pojęcie zmiennej występuje także w matematyce. Wydaje się ono tak proste, że nie wymaga objaśnień. Naprawdę jednak istnieją osoby, które bez problemu rozwiążą zadanie „2+3 = ?”, ale nie są w stanie zrozumieć zadania „2+x=5”. Gdy próbujemy to zagadnienie objaśnić (zob. definicja w Wikipedii) pojawia się dylemat: czy należy zmienną traktować jedynie jako symbol – czyli element języka matematyki, czy też brać pod uwagę zastosowania tego symbolu3. Pełna formalizacja (zmienna wyłącznie jako symbol) jest uciążliwa, a rozbudowywanie pojęcia zmiennej przez rozważanie kontekstów (np. zmienne wolne i związane), może utrudniać rozumienie elementarnych pojęć. Odwołanie do przykładów z programowania może pomóc w zrozumieniu idei zmiennej. Zmienna to symbol (identyfikator) wskazujący na element z pewnego zbioru (zakres zmiennej / typ danych). W matematyce – tak jak w informatyce – badamy skutki nadania zmiennej określonych wartości (np. badamy wyniki działania dla wszystkich wartości ze zbioru), albo poszukujemy wartości zmiennej (wyniku) przy której spełnione są warunki zadania.
Funkcje
Pojęcie funkcji jest wieloznaczne. Zazwyczaj słowo “funkcja” określa zależność między pewnymi obiektami: coś/ktoś realizuje jakąś funkcję i dostarcza (zwraca) określony wynik. Tak je rozumiemy w informatyce. Wspomniana „funkcja przejścia” na podstawie stanu pamięci zwraca nowy jej stan.
W matematyce funkcja (oznaczana często literą f) to relacja, która wiąże ze sobą w sposób jednoznaczny dwa zbiory danych4:
-
zbiór nazywany dziedziną funkcji (oznaczmy zbiór literą R, a elementy zbioru x)
-
zbiór wartości nazywany też obrazem lub przeciwdziedziną funkcji (oznaczenie: f(x), element zbioru można oznaczać literą y) .
Możemy spotkać się z zapisami: f(x)→y albo y=f(x)
Określenie „w sposób jednoznaczny” oznacza, że dla każdego x ze zbioru R istnieje dokładnie jedna wartość f(x).
Przykład: wartością funkcji kwadratowej (dla R = zbiór liczb rzeczywistych) dla liczby x jest jej kwadrat (kwadrat liczby to liczba pomnożona przez samą siebie). Piszemy: f(x) = x*x
W informatyce zamiast o dziedzinie funkcji mówimy o danych wejściowych lub parametrach funkcji, a zamiast o zbiorze wartości funkcji mówimy o jej wynikach. Dziedzinę funkcji można utożsamiać z typem danych.
Funkcję kwadratową możemy w języku Python zdefiniować następująco:
def kwadrat(x):
return x*x
Słowo return oznacza zwracany wynik funkcji. W powyższej definicji x to oznaczenie parametru funkcji. Przykład jej użycia:
print(kwadrat(123))
W programach parametry i wyniki funkcji mogą być dowolnego typu (liczba, napis, obiekt etc…). Funkcja może zwracać wyniki (wartości) dla wielu parametrów. Na przykład:
def iloczyn(x,y):
return x*y
W matematyce spotyka się podobne zapisy, choć ściśle rzecz biorąc, należałoby w tej sytuacji mówić, że dziedziną funkcji są pary liczb.
Czy różnica między informatyką a matematyką sprowadza się jedynie do terminologii i stosowanych metod zapisu? Nie. Najbardziej fundamentalną różnicą jest rozumienie tego, czym funkcja jest w swej istocie. Pytanie o istotę funkcji to dla matematyka pytanie o definicję: jak ta funkcja jest zdefiniowana (jakie relacje wiążą elementy dziedziny i wartości funkcji). Dla informatyka jest to pytanie o sposób wyliczenia wartości: jakie wyniki zwraca funkcja dla danych wejściowych. Widać więc różnicę między statyczną definicją a dynamicznym obliczaniem.
Jak już wspomniano - w matematyce wynik funkcji musi być jednoznaczny: dla takich samych parametrów dostajemy taki sam (jeden) efekt. W informatyce ta zasada wynika wprost z determinizmu obliczeń maszyn cyfrowych. Dopuszcza się jednak brak wyniku / wynik pusty (void). Skoro bowiem w miejsce definicji pojawiają się obliczenia, może nam zależeć na samym wykonaniu szeroko rozumianych obliczeń (w ich trakcie mogą pojawić się na przykład efekty w postaci wydruków), a nie na wyniku!
Rozumienie istoty funkcji rzutuje na sposób jej definiowania i używania. Na przykład matematyk nie ma takich możliwości „przetestowania” funkcji (sprawdzenia jak działa dla wybranych wartości parametrów) jak informatyk. Z drugiej strony – takie testy nie są niezbędne, gdyż matematyka jest zbudowana w oparciu o formalne dowody, które dają absolutną pewność poprawności. Poprawności wszystkich tworzonych w informatyce funkcji dowieść nie sposób5. Uzyskujemy większą złożoność i dynamiczny rozwój kosztem utraty pewności.
Ten brak pewności sprawia, że niektórzy ludzie uważają, że w swej istocie komputer różni się od złożonego wyrażenia matematycznego.
Czym różnią się te trzy zapisy:
1) wynik=123
2) wynik=100+2*10+3
3)
wynik=0
for cyfra in [1,2,3]:
wynik=10*wynik+cyfra
Wszystkie trzy są zapisem faktu, że zmienna wynik ma wartość 123.
Czyli program komputerowy to tylko inny zapis deterministycznego w swej istocie wyrażenia. Podważanie tego faktu jest kontynuacją pewnej tradycji „intelektualnej” dążącej do deprecjonowania personalistycznej wartości człowieka. Nie udało się sprowadzić człowieka do maszyny, to może uda się uczłowieczyć komputer? O inteligencji maszyny ma świadczyć działanie w sposób, jakiego nie przewidzieli twórcy6. A przecież tak działa każdy bardziej złożony program. Nikt nie wie ile zawartych w nim funkcji zawiera błędy.
Struktury danych, obiekty
Funkcję przejścia możemy zapisać jako zbiór reguł (instrukcji) wykonywanych przez procesor. Nic nie stoi na przeszkodzie, by te reguły były także zapisane w pamięci – tak jak informacje o stanie systemu. Rozróżniamy zatem dane (informacje zapisane w pamięci) oraz programy (reguły funkcji przejścia).
Jeśli wybierzemy pewną część (fragment) danych i zidentyfikujemy programy, które odczytują lub zmieniają te dane – uzyskujemy całość zwaną obiektami. Obiekt to dane wraz z metodami (programami) operującymi na tych danych.
Tworząc programy komputerowe możemy definiować obiekty, wzorując się na obserwowanych obiektach realnych. Na przykład obiekt „Osoba” może mieć właściwości „Imię” i „Nazwisko”.
Możemy tworzyć dowolną (skończoną) ilość obiektów o identycznych właściwościach, ale różniących się wartością tych właściwości. Na przykład reprezentujących wiele osób o właściwości „Imię” i „Nazwisko”, które jednak mają różne imiona i nazwiska. Wzorzec takich obiektów nazywa się klasą, a pojedyncze wystąpienie klasy (obiekt tej klasy) – instancją.
W praktyce klasy i obiekty mogą pojawiać się w programach na różne sposoby. Klasy obiektów mogą być zdefiniowane przez twórców języka programowania, albo definiowane przez programistę. Możliwe jest także tworzenie złożonych klas obiektów, które generują obiekty (tak zwane „fabryki”), albo wykonują na nich działania.
Najprostszym jest przypadek, gdy typ danych jest klasą obiektów. Tworząc zmienną tego typu uzyskujemy instancję (wystąpienie) obiektu.
Przykład w języku Python
class Osoba():
def __init__(self,imie, nazwisko):
self.nazwisko=nazwisko
self.imie=imie
def ImieNazwisko(self):
return self.imie+' '+self.nazwisko
osoba=Osoba('Jan', 'Kowalski')
osoba.ImieNazwisko()
Mamy w tym przykładzie zdefiniowaną klasę Osoba i obiekt (instancję) osoba. Słowo „def” rozpoczyna definicję funkcji / metody wewnątrz klasy. Z kolei „return” wskazuje na wynik tej funkcji. Własność (zmienna) self odnosi się do instancji obiektu. Możemy na przykład zapisać:
osoba.imie='Jurek'
osoba.ImieNazwisko()
Otrzymamy:
'Jurek Kowalski'
Zamiast tego – możemy w definicji klasy stworzyć funkcję ustawImie:
def ustawImie(self,imie):
self.imie=imie
i wywołać:
osoba.ImieNazwisko()
1Zob. J. Wawro, „Nauczanie programowania w szkole”
2Szersze wyjaśnienie znajdziemy na stronie „Ważniaka” http://wazniak.mimuw.edu.pl
3Zob. J Konior, „O pojęciu zmiennej w nauczaniu szkolnym matematyki” 1996, https://yadda.icm.edu.pl/yadda/element/bwmeta1.element.ojs-doi-10_14708_dm_v18i01_6780/c/6780-6228.pdf
4Dobre formalne wprowadzenie znajdziesz w publikacji: Marian Mrozek „Wstęp do Analizy Matematycznej z elementami Logiki i Teorii Mnogości” https://ww2.ii.uj.edu.pl/~zgliczyn/dydaktyka/2014-15/analiza-mat-mk/analiza-matematyczna-I.pdf
5Próby formalnego dowodzenia poprawności programów nie zakończyły się znaczącymi sukcesami.