Skip to content

Latest commit

 

History

History
1052 lines (724 loc) · 38.4 KB

PB006.md

File metadata and controls

1052 lines (724 loc) · 38.4 KB

Zajímavé číselné konstanty ve slajdech

2. Jména, vazba, rozsahy platnosti

Jména

jméno = řetězec znaků, které mají zastupvat jiný objekt

speciální slova

  • klíčové slovo = slovo se speciálním významem v určitém kontextu
  • rezervované slovo = název, který nelze použít jako jméno (Java: const a goto - ale v reálu to není využité)

Proměnné a vazby

Proměnná

= oblast v paměti, která má:

  • jméno
  • hodnota
  • typ
  • doba života (lifetime)
  • rozsah platnosti (scope)

Python: Při přiřazení se přesměruje odkaz.

C: Přiřazení mění hodnotu.

Vazba

= spojení mezi dvěma věcmi

statická vazba = vazba, která vzniká před během programu a během něj už se nemění (např. v C)

dynamická vazba = vazba, která vzniká při běhu programu a může se během něj měnit

(Výčet "Okamžik vzniku vazby" nezkouší.)

Dřívější vazba je výhodnější z hlediska efektivity, pozdní z hlediska pružnosti jazyka.

Aliasing

= více jmen má vazbu na stejný objekt

Python: [Node()] * 3 vytvoří tři uzly ukazující na stejné místo v paměti (tz. když změním jeden, změní se i ostatní) - jsou to aliasy. Případně jednoduše a = [1, 2, 3], a pak a = b - b je alias a.

Paměťová reprezentace objektů

doba života vazby = doba mezi vznikem a zánikem vazby

doba života objektu = doba mezi vznikem a zánikem objektu

Doba života vazby a objektu se může lišit.

Typy objektů

statické objekty = objekty s pevnou adresou, která se během běhu programu nemění

objekty na zásobníku = objekty alokované typicky ve vztahu k rekurzi a volání funkcí

objekty na haldě = objekty, které se mohu (de)alkokovat kdykoli (dražší správa paměti)

  • vhodné pro mutable objekty s předem neznámou délkou
  • objekty na haldě jsou přístupné přes odkazy nebo referenční proměnné
  • problém s fragmentací
  • garbage collection

C: (de)alokace na haldě pomocí malloc a free

Python: vše alokováno na haldě

C#: referenced types se alokují na hadě, value types na zásobníku

(Im)mutable objekty

mutable objekt = objekt, jehož hodnota se může měnit (Python: seznamy)

immutable objekt = objekt, jehož hodnota se měnit nemůže (Python: int, C: klíčové slovo const zakáže objekt měnit)

Haskell: všechno je immutable

Rozsahy platnosti (scope)

= oblast, kde platí konkrétní vazba (napárování jména proměnné a místa v paměti)

= maximální oblast,kde se vazba nemění (C: {...}, Pyton: tělo funkce)

Statický rozsah (static scope, lexical scope)

= hodnoty proměnných záleží na struktuře programu - hledám v úrovni syntakticky výš (Python, C, C++ Java)

  • Statický rozsah je běžně používaný v programovacích jazycích.
  • Rozsahy mají proměnné, ale i názvy funkcí.
  • Vazby jednoduše určím už během přehledu.

Python: Nový rozsah zavádí: modul (globální rozsah), definice třídy, definice funkce. Rozsahem není if else a bloky (odsazení); modifikátory proměnných: global, nonlocal, pravidlo LEGB (Local, Enclosing, Global, Buildins) říká, kde se postupně hledá

C: Nový rozsah zavádí: soubor, definice funkce, blok (příkazy uzavřené v {} - takže i if then větve), funkční prototyp.

Haskell: na vnořené rozsahy narazím pokud vnořuji let

Deklarace a definice

deklarace = zavedení jména a indikace rozsahu

  • na začátku bloku/procedury (Lisp, C89)
  • kdekoli (C99, C++, Java, Python, C#)

Rozsah deklarace může být:

  • ode místa deklarace (C99, C++, Java)
  • od začátku bloku, ve kterém je deklarace (Python, C#) - použití nesmí předcházet deklaraci ve stejném bloku!

Následující kód spadne:

c = 42
def foo():
    print(c)
    c = 13
foo()

definice = kompletní informace, včetně implementace

C: Rozsah je až do definice dál. Nastal by problém s dvěmi navzájem volajícími se funkcemi, proto vzniklo oddělení definice a deklarace.

JavaScript: (= zlo); překlep v proměnné - nový název bez var mi může vytvořit novou proměnněnnou, bez vyhození chyby - variable hoisting. V JS je možné použít use strict, které zakáže vytváření nových proměnných bez let.

"Přepiš všechny var na let a uvidíš" - může pomoct

Poznámka k poslednímu slajdu: scaled_score bude počítat s max_score z procedury foo.

Dynamický rozsah

= hodnoty proměnných záleží na pořadí volání, použiji hodnotu proměnné, kterou jsem použila naposledy

  • používám zásobník volání (dřív Lisp, TeX, Bash, PowerShell)
  • vazba nemůže být rozhodnuta v době překladu
  • je možné je nahradit globálními proměnnými nebo implicitními parametry

2. Datové typy

= množina hodnot spolu s předdefinovanými operacemi na těchto hodnotách

  • detekce chyb
  • lepší čitelnost
  • podpora modularizace (kontext pro operace: "a" + "b" -> "ab", 1 + 2 -> 3)

Typový systém

  • množina vestavěných typů
  • mechanimy pro definici nových typů
  • množina pravidel pro>
    • rovnost typů
    • kompatibilitu typů (dokážeme sečíst int a real)
    • odvozování typů

hodnotové typy = proměnná obsahuje přímo data (C: vše; Java: primitivní typy - numerické + bool, C#: bool, int, enum, struct,... ale pozor, jsou to jen synonyma pro typové třídy, mají implicitní konstruktory bez parametru ale nemusím volat konstruktor explicitně)

odkazové typy = odkaz na místo, kde jsou uložena data (Python: vše, C# class string, object,..., Java: vše krom numerických a bool)

Primitivní datové typy

= základní stavební kameny typového systému (int, bool, float)

Definice primitivního typu záleží na jazyku.

C: string je to složený typ.

Python: string je to primitivní typ.

Numerické typy

  • reálná čísla
    • double - typ s dvojtou přesností
    • float
  • celá čísla
    • C: char (číslený typ!), short, int, long, long long + unsigned varianty
    • Python: int - nekonečný
    • C#: byte (1 byte), short (2 byty), int (4 byty), long (8 bytů) + unsigned varianty
  • decimální
    • C#: decimal pevný počet decimálních míst (C#: decimal)

Boolean

Python: podtyp int

C: dřív nebyl (int 0/1), teď _Bool

C#: má bool a nelze automaticky konvertovat na int

Znakový typ

  • typ pro uložení jediného znaku

C: 'a' je typu int

Python: char chybí

C#: char odpovídá UTF-16 a je na 16 bitech

Výčtový typ

= komplení výčet hodnot, většinou implementovaný pomocí integerů

C: podtyp int

C#: enum a volím, jakého typu jsou hodnoty enum nazev typHodnot {nazev1, nazev2 = hodnota2}

Python: třída enum.Enum

Řetězce

C: omezeně dynamická délka, složený datový typ, modifikovatelné, ukončeno 0

C++: dynamická délka, modifikovatelné

C#: pevná délka, předdefinovaná třída objektů, nemodifikovatelné

Python: pevná délka, primitivní datový typ, nemodifikovatelné

Java - String: pevná délka, předdefinovaná třída objektů, nemodifikovatelné

Java - StringBuffer: dynamická délka, předdefinovaná třída objektů, modifikovatelné

JS: dynamická délka

Typová kontrola

= proces ověření a vynucování typových pravidel a omezení

Programovací jazyky dělíme na:

  • silně typované = zamezují situaci, kdy by se operace měla provést na objektu, pro který není definována
  • slabě typované = ty, co nejsou silně typované
  • staticky typované = kontrola proběhne již během překladu
  • dynamicky typované = kontrola probíhá za běhu
slabě typované silně typované
staticky typované C, C++ Java, Haskell, C# (moderní jazyky)
dynamicky typované Perl, PHP, JavaScript Python, Ruby

Rovnost (ekvivalence)

= kdy jsou dva typy stejné?

jmenná ekvivalence (Pascal, Ada, Java C#) = typy jsou stejné, pokud byly definovány ve stejné deklaraci, nebo v deklaracích používajících stejné jméno typu

  • striktní jmenná ekvivalence = různé typy (to, že je to stejného typu v programovacím jazyce neznamená, že to má i stejný význam, tz. nelze to zaměňovat)
  • volná jmenná ekvivalence = stejné typy (Haskell, C, Pascal)

strukturální ekvivalence (Algol, Modula, ML) = jejich typy jsou identické struktury (konkrétní výklad ještě záleží na programovacím jazyku)

alias = možnost vytvořit synonymum pro nějaký typ

"Jazyk může být silně typovany i když je možné všude vrazit var."

Typová konverze

======= * striktní jmenná ekvivalence = různé typy (to, že je to stejného typu v programovacím jazyce neznamená, že to má i stejný význam, tz. nelze to zaměňovat) * volná jmenná ekvivalence = stejné typy (Haskell, C, Pascal)

strukturální ekvivalence (Algol, Modula, ML) = jejich typy jsou identické struktury (konkrétní výklad ještě záleží na programovacím jazyku)

alias = možnost vytvořit synonymum pro nějaký typ

"Jazyk může být silně typovany i když môžem všude vrazit var."

Explicitní typová konverze

= programátorem vynucená změna jednoho typu na druhý

  • Můžu a nemusím ztratit nějakou informaci.

slajd 26 - řešení: result = 2.0, result1 = 2.5, result2 = 2.0

Nekonvertující přetypování

= ke konverzi nedochází, hodnotu interpretujeme jako by byla jiného typu (např. float čtu, jako by to byl int)

  • na slajdu: nedefinované chování, nikdo nám nezaručí, že to bude fungovat
  • využití (dříve):
    • vypočítání 1/sqrt(x) (dnes už je na to instrukce v procesoru)
    • rychlé porovnání dvou floatů - porovnám je jako dva unsigned inty

Kompatibilita typů

  • definice kompatibility se liší mezi různými jazyky

koerce = implicitní typová konverze pro kompatibilní typy

  • Předpokládám, že překladač ví, jak se změnou typu naložit.
  • Představuje zásadní oslabení typového systému! Protože mě to nehlídá a můžu se nevšimnout, že něco přiřadím do něčeho s nižší přesností a ztratím nějakou informaci.

C: integer promotion = když mohou být všechny hodnoty reprezentovány rozsahem intu, bude to int, když ne, tak to bude unsigned int

Python: complex, float, int - pokud je jeden complex, převede se i druhý na komplex, obdobně s floatem, jinak jsou oba inty

Složené datové typy

= typy vznikající kombinací primitních datových typů

Záznam (struct, record)

  • položky (members, fields)
  • přístup přes tečkovou notaci
  • je možné je vnořovat

C: struct C++: class

Python: není, místo toho se používá class

Java: není, místo toho se použije třída

C#: hodnotvý typ (bool, int a double jsou taky struct)

Union

  • umožňují různým proměnným sdílet stejnou paměť
  • sloužili k šetření paměti (dnes je paměti dost, takže už se moc nepoužívají)
  • type-unsafe - přiřadím int, pak přečtu float a to už bude jiné číslo

Python, C#, Java: nejsou

discriminated unions (tagged unions) = před přiřazením použiju tag, který řekne, co to je

N-tice (Tuple)

= záznam s pevným počtem nepojmenovaných položek

  • použití: funkce vracející více položek

Python: lze převádět na pole tam a zpět

Haskell: speciální přístup pro dvojice - fst a snd

Pole

= zobrazení nazev_pole(index) -> prvek

  • pole jsou obvykle homogenní = všechny prvky stejného typu
  • indexace: většinou pomocí int
  • kontrola mezí byla dřív drahá (C), dnes to není problém
  • většinou se indexuje od 0

Python: pole není, místo toho seznam, který je implementován jako pole proměnné délky

"Fortran vypadá jako komentovaný Assembler."

Vícerozměrná pole

= pole polí (C), nebo pole odkazů (C#)

  • tvary polí:
    • obdélníkové pole
    • schodovité pole (délka řádků se může lišit)
  • uložení v paměti:
    • souvislý blok - snadná adresace
    • pole odkazů na řádky - možnost mít schodovitá pole

řez (slice) = vytváří nové pole (a kazí složitost)

Typy polí

  • statické = při překladu vím, ve kterém bloku paměti bude umístěno
  • dynamické = alokace během zavádění do paměti
    • s proměnnou délkou:
      • stack-dynamic - elaborace
      • heap-dynamic - vazba za běhu
    • s fixní délkou:
      • fixed stack-dynamic - elaborace, ale meze jsou statické
      • fixed heap-dynamic - vazba za běhu

C: staticky alokované pole (static) , fixed-heap dynamix (malloc, free), fixed stack-dynamic

Java: fixed heap-dynamic

C#: fixed heap-dynamix, heap-dynamic (List class)

Seznamy

= uspořádaná sekvence prvků

  • induktivní definice:
    • prázdný seznam
    • hlava seznamu (head) + zbytek seznamu (tail)

homogenní seznam = všechny prvky stejného typu (Haskell, ML)

heterogenní seznam = prvky různých typů (Python, List)

list comprehensions = vytvoření seznamu ze seznamu

LISP má moc závorek. (slajd 56 spíš pro srovnání, ale když to uvidíme, měli bychom se v tom orientovat)

Typové odvozování

  • zpřehledňuje program
  • jsme líní
  • podpora abstrakce (dostaneme nejobecnější řešení)

Python: automatické typové odvození

C#: musím psát typy, odvození lze vynutit pomocí var

Typová kontrola

  • přímá = dostanu chybu
  • nepřímá = zjistím typy a vidím, že to není to, co chci

Průběh typového odvození (viz příklad ve slajdech)

  1. Každému výrazu a podvýrazu se přiřadí nový typ (vhodné rozkreslit pomocí stomu, kořen podstromu je vždy výsledek)
  2. Generování typových rovností - aplikace typových pravidel (když vidím 0, vím, že to je int; když vidím seznam intů - je to seznam čísel apod.; podle toho, co je parametren funkcí apod.)
  3. Vzniklou soustavu rovností vyřešíme pomocí unifikace typů.
    • unifikace = nalezení substitucí za typové proměnné tak, aby várazy byly po substituci idnetické
    • unifikátor = výsledná množina unifikací
    • substituce = přiřazení typových výrazů jednotlivým proměnným
    • Robinsonův unifikační algoritmus (slajd 81)
  4. Výsledek typového odvození a. existuje právě jedno řešení b. soustavu nelze řešit c. existuje více řešení - polymorfismus (např. kvůlu identitě) - nejednoznačnost (např. + v F#, protože může být jak pro inty, tak pro seznamy)

Generování typových rovností

Typový výraz se skládá z:

  • primitivní typy (Int, Bool,...)
  • typové proměnné
  • typový konstruktor pole = vezme typ T a vrátí [T]
  • typový konstruktor n-tice = vezme typ a vrátí
  • funkční typ = T1 -> T2

typový konstruktor = vezme typ a vrátí jiný typ

(minule na zkoušece: co to je typová proměnná, typový výraz)

Typ ukazatel

= paměťová adresa

  • známe z C (přiřazení p = &a, dereference *p)
  • využití:
    • nepřímá adresace
    • správa dynamicky alokohované paměti (na haldě)

visící ukazatele = ukazatel ukazující tam, kde nic není (proměnná byla dealokována); v případě, že je na adresu alokována nová proměnná, může vzniknout kolize se správcem paměti

ztracené proměnné = proměnné na haldě, ke kterým není jak přistoupit; důsledkem je memory leak (pokud nemám garbage collectioin); vzniká, když do ukazatele přiřadím jinou adresu a obsah té původní nesmažu

C/C++: mohu ukovazovat kamkoli, ukazatelová aritmetika

Typ odkaz (reference)

= hodnota, která umožňuje programu nepřímo přistoupit k datům = (odkaz je ukazatel, na který si uživatel nemůže sáhnout)

  • nelze s nimi dělat pointerovou aritmetiku
  • často se implementuje pomocí ukazatelů, ale není to to stejné!

Java: má odkazy i ukazatele

Tok řízení

Řídící příkazy

= příkaz, který mění tok řízení v programu

  • nepodmíněný skok (goto)
  • podmíněný příkaz
  • cykly

Přečíst: http://www.u.arizona.edu/~rubinson/copyright_violations/Go_To_Considered_Harmful.html

Výrazy

= základní prostředek pro vyjádření výpočtu

  • skládají se z:
    • jednoduchých objektů (kontanty, proměnné)
    • aplikací funkcí/operátorů na jiné výrazy
    • závorek

operátor = vestavěná funkce se speciální syntaxí

operand = argument operátoru

Pořadí vyhodnocení operátorů

  • priorita = přednost vyhodnocení výrazu
  • asociativita = pravidla pro sdružování operátorů (nikoli operandů!) se stejnou prioritou (8/2/2 -> ((8/2)/2) nebo ( 8/(2/2))
int a = 10, b = 3, c = 2, d = 4, result

a + a * - b / c % d + c * d

10 + 10 * -3 / 2 % 4 + 2 * 4
10 + (10 * -3 / 2 % 4) + (2 * 4)
10 + (((10 * (-3)) / 2) % 4) + (2 * 4)
10 + ((-30 / 2) % 4) + 8
10 + ((-15) % 4) + 8
10 + (-15 % 4) + 8
10 + (-3) + 8
10 - 3 + 8
7 + 8

= 15

x + x >= y * 2 || y % 2 && z++ % 2 ?

--(p++) (p++)++

(bude na zkoušce :))

Haskell: mám možnost definovat si vlastní prioritu a asociativitu

  • chytré jazyky, když to nemají definované, tak vyhodnocují zleva doprava
  • závorky nikdy neškodí

Líné vyhodnocování

= možnost vyhodnocení vvýrazu bez vyhodnocení všech jeho částí

  • C, ML, F#, Python

Přetěžování operátorů

přetížený operátor = jeden operátor značí víc operací

  • např. * bude fungovat s čísli, ale i s maticemi

Přiřazení

podmíněné cíle přiřazení (ani po nás nebude chtít)

Složené přiřazení

a = a + b -> a += b

Unární přiřazení

= inkrementace: ++, dekrementace: --

  • možnosti zápisu:
    • jako prefix ++a - hodnota výrazu je a + 1 (je to pouze syntaktický cukr, nepřidává to žádnou novou funkcionalitu oproti x += 1)
    • jako postfix a-- - hodnota výrazu je a
  • (neplést se složeným přiřezním)

Vícenásobné přiřazení

a, b = b, a
a, b = foo(x)

Přiřazení jako výraz

= přiřazení + vrácení naráz (b + (a = 42))

Vedlejší efekty

= Funkce či výraz mimo vrácení hodnoty (main effect) má také pozorovantelný vliv mimo své lokální prostředí.

referential transparency = libovolný výraoz mohu nahradit jeho hodnotou bez vlivu na chování programu

Podmíněné větvení

= vybírání mezi dvěma možnostmi

  • možný problém: syntaktické vnořování (když nejsi Pyton, nevíš, ke kterému ifu patří else)

Switch

C: nemá implicitní skok na konci větvě

C#: musím na konci každé větve explicitně říct, jestli končím (break), nebo pokračuji

Python: chybí

Cykly (loops)

  • podle typu podmínky
    • smyčky s čítačem (nesprávně se jim říká forcykly)
    • smyčky s logickou podmínkou
    • kombinované cykly - používají logickou podmínku, ale zároveň mohu snadno enumerovat
  • podle umístěním testu
    • na začátku
    • na konci
  • break, continue

Iterátory

= zobecnění pricipu iterace přes prvky aritmetické posloupnosti

  • vrací postupně jednotlivé prvky z nějaké množiny
  • má historii, takže při dalším zavolání vrátí prvek následující po tom, který vrátil v předchozím volání
  • implementováno pomocí odděleného vlákna řízení

Python: range není iterátor, ale je iterable (něco, přes co mohu iterovat), iterátor vytvořím pomocí iter(), prvky získávám pomocí next(), na konci vyhodí výjimku StopIteration

C#: IEnumerable, IEnumerable<T>, IEnumerator, IEnumerator<T> + yield return, rozhraní IEnumerable

generátor, generátorová funkce = výpočet - yield (na chvíli se zastaví) - při dalším zavolání pokračuje tam, kde skončila, je to obecnější, lze je použít na výrobu iterátor

Podprogramy

= procedura, funkce (mnoho definicí)

  • zlepšení čitenosti
  • podpora abstrakce

hlavička podprogramu = typ + jméno + seznam parametrů

profil parametrů = počet, pořadí a typ formálních parametrů

protkol = profil parametrů + typ návratové hodnoty

procedura = podprogram bez návratové hodnoty

funkce = podprogram v návratovou hodnotou (ta ale může být i void)

Návratové hodnoty funkcí

Python: lze vrátit cokoli

C: pole a funkce lze vrátit pouze prostřednictím ukazatelů

C#: metody vracet nelze

Parametry

formální parametry = a a b pro foo(a, b)

aktuální parametry (argumenty) = 55 a 6 pro foo(55, 6) při volání funkce

poziční parametry = vazba aktuálních parametrů na formální parametry je určena pořadím

pojmenované parametry = parametry s explicitně uvedeným jménem

parametry s implicitní hodnotou = foo(a, b, c=0), v případě, že nedodám hodnotu parametru, použije se výchozí hodnota

proměnný počet parametrů = funkce bere předem neznámý počet parametrů

Předávání parametrů

  • vstupní parametry
  • výstupní parametry
  • vstupně-výstupní parametry
  • Předávání vícerozměrných polí je problematické, protože potřebuji znát rozměry daného pole.
    • C: rozměry ve formálním parametru
    • Ada: jsou k dispozici informace o rozsahu

Předávání hodnotou

= v okamžiku volání překopíruju hodnotu do formálního parametru

C: všechno hodnotou - můžu předat hodnotu ukazatele, čímž můžu nasimulovat předávání odkazem

C#: hodnotové typy

Předávání odkazem

= předávám odkaz na aktuální parametr, formální parametr bude odkazovat na stejné místo v paměti

  • efektivní na čas i paměť
  • vracení hodnot prostřednictvím aktuálních parametrů

C#: ref, in nelze modifikovat (C# >=7.2), out

  • Riziko vzniku kolize:
    • mezi aktuálními parametry
    • při použití polí
    • formálních a globálních proměnných

read-only parametry = jejich hodnota se nesmí uvnitř funkce měnit -> vyšší bezpečnost, rychlost (C#: in, C: const)

Předávání výsledkem

= funkci pošlu proměnnou (bez hodnoty), do které se v průběhu funkce zapíše výsledek

  • možné problémy:
    • kolize hodnot
    • volba okamžiku vazby

C#: out

Předávání výsledkem-hodnotou

= nakopíruji hodnotu aktuálního parametru do formálního parametru, následně s tím normálně pracuji jako by to bylo předávání hodnotou a na konci nakopíruji výsledek zpátky do hodnoty argumentu

Předávání sdílením

= pokud je aktuální parametr odkaz (ne hodnota), tak sdílíme s volanou funkcí tento odkaz (hodnotu na něm), zároveň není možné přesměrovat tento odkaz někam jinam (lze pouze měnit hodnotu)

C#: reference types (ale říká se tomu taky předávání hodnotou)

Python: taky to používá

Předáváním jménem

= předává se to, co bylo v době volání jako jméno proměnné (např. u foo(x) - foo(s[i]) se vždycky při změně x podívám, co je za hodnotu v i - řídím se tím jménem)

C: makra

Haskell: podobné call-by need

Přetížené a generické podprogramy

přetížený podprogram = existuje jiný podprogram se stejným jménem (musí se lišit protokolem - typem a počtem parametrů)

  • použití: konstruktory
  • problémy s přetíženými podprogramy:
    • koerce = možnost shody s více profily (např. int může být i double) - je nutné určit prioritu
    • implicitní hodnoty - mění možný počet parametrů

Python: jde to, ale defaultní chování je, že se bere jen poslední definice funkce

Polymorfismus

= mnohotvarost; více tvarů toho samého, musím nějaký vybrat

Ad-hoc polymorfimus

  • přetěžování
  • při aktivaci se vybere ta definice programu, která podpovídá aktuálním parametrům

Parametrický polymorfimus

  • podrogram má obecné typy formálních parametrů
  • jedna definice, která je nezávislá na typu
  • Haskell - např sort seznamu libovolného typu

Generické funkce (generika)

= explicitní parametrický polymorfismus

  • (implicitní parametrický polymorfismus (Haskell))
  • nějaký typ <T>, za který se při konkrétním volání dosadí něco konkrétního

C++: template function

Java: generické parametry musí být třídy, mohu je navíc omezit pomocí nějakého rozhraní

C#: podobné jako Java, ale umí pracovat i s primitivními typy

Podtypový polymorfismus

  • u OOP

Duck typing = Jestli to vypadá jako kachna, plave to jako kachna, kváká to jako kachna, tak je to pravděpodobně kachna. (používá např. Python)

Koprogramy (coroutine)

= speciální typ programů na stejné úrovni, které se střídají v běhu (bez řídící autority) a pamatují si mezi tím svůj stav

-Hraju karty, vynesu kartu, předám řízení dalšímu h ráči, atd. (třeba Kvarteto)

  • použití: simulace systémů (lišky a zajíci), pipelines, event loops
  • Asynchronní funkce nejsou koprogramy (mají nějaké omezení).
  • pseudosouběžnost

Python: nelze předávat řízení libovolně, ale jen volajícímu (jsou to skoro koprogramy, ne úplně), použiju generátorovou funkci (yield. next(), .send(), .close()), nejedná se o čisté koprogramy!

Abstraktní datové typy (ADT)

abstrakce = pohled na entitu, které ponechává pouze nejdůležitější atributy

  • procesová abstakce - podprogramy (viz předchozí přednáška)
  • datová abstrakce

UHP = Univerzální Hnědý Pták abstraktní datový typ = matematický model datové struktury, která je definována svými hodnotami a operacemi na nich

uživatelsky definovaný ADT = datový typ se skrytou reprezentací, která má deklarované rozhraní, které není závislé na implementaci (např. zásobník)

Funkce nad ADT

  • creators
  • producers = operace na typu, která vrátí nový objekt stejného typu
  • observers
  • mutators

mutable ADT - např. zásobník

immutable ADR - string v Pythonu

Skrývání informace

= určité části znepřístupníme klientskému kódu

  • spolehlivost
  • zjednodušení pro programátora
  • menší riziko konfliktu jmen
  • možnost změnit vnitřní reprezentaci

Zapouzdření

= jazyková konstrukce, která umožňuje spojit pod jedním jménem spolu související data a operace na nich (často zahrnuje i skrývání informace)

třída zásobníku
funkce, která pracuje s objetem zásobníku

^ to není zapouzdřené

třída zásobníku
    metoda této třídy

^ to je zapouzdření

Implementace ADT

třída = šablona, podle které se vyrábí objekty a definuje typ, udává formát dat a metody pro přísutp k těmto datům, může obsahovat i vlastní data

objekt = instance třídy

člen třídy = atributy (datové členy) a metody (funkční členy)

Python

  • zapouzdření: metody ve třídě - metody jsou propojeny s datovou strukturou
  • information hiding
    • metody, které by neměly být používány mají začínat _
    • private metody začínají dvěmi podtržítky __, a pak je obtížnější je volat (je potřeba před to dát i název struktury: _JmenoTridy__jmenoMetody)
  • class
  • __init__ není konstruktor
  • přístup k sobě samému: self

C#

  • zapouzdření:
    • class = referenční typ ukládaný na haldě, lze dědit
    • struct = hodnotový typ ukládaný na zásobníku, nelze dědit - jednodušší a menší reprezentace (např. nepotřebuji virtuální tabulku metod)
  • information hiding: modifikátory
    • public = přísutpné komukoli
    • private = přístupné pouze z kódu ze stejné třídy (implicitní)
  • přístup k sobě samému: this + tečková notace (ale jen když to potřebuji, např. při kolizi jmen)

getters/setters = metody sloužící k získávání a nastavování atributů, mohu je použít pro validaci

Properties

= atributy s výhodami metod

Auto-implemented properties

class Person {
    public string name { get; set; } = "N/A";
    public int age { private get; set; } = 0;
}

Person me = new Person();
me.name = "Dominika";
me.age = 1;
  • {get; set} -> public get i set
  • {private get; set} -> private get, public set
  • {get} -> public get, set jen v rámci konstruktoru
  • mohu mít dvě properties, které používají jeden atribut

Generická třída

= třída, která funguje s různými datovými typy (podobné jako generická metoda)

  • např. class Stack<T>

Konstruktory

= speciální funkce, která je volána při vytváření instance třídy

  • jmenuje se stejně jako třída
  • neuvádí se návratový typ
  • C#: bez definování je vytvořen default constructor
  • může jich být víc různých (přetěžování)

Destruktory

= uvolňuje alokovanou paměť, zavírá soubory apod.

  • volán na konci života objektu
  • omezená použitelnost v jazycích s garbage collection
  • v C# finalizers - nemůže jej zavolat programátor, ten může použít Dispoze

Statický člen tříd

= existuje pouze v jedné kopii, nezávisle na počtu instancí

  • statický atribut = je společný pro všechny objekty třídy
  • statická metoda = nezávisí na konkrétním objektu - může přistupovat pouze ke statickým atributům!

C

  • využívám oddělený překlad
  • skrývání informace: pomocí odděleného hlavičkového souboru
  • zapoudření: všechno je spolu v tom oddělém souboru

Haskell

  • pomocí definice modulu
  • skrývání informace: naven exportuji jen metody, ke kterým chci, aby měl přístup uživatel

OOP

objekt

  • má data a funkce
  • implementace bývá skrytá
  • objekty komunikují skrze rozhraní (zasíláním zpráv)

Principy

  • zapouzdření
  • abstrakce
  • dědičnost
  • dynamická vazba/polymorfismus

encapsulaiton = zapouzdření + abstrakce

  • class-based OOP = každý objekt je instancí třídy
  • prototype-based OOP = nemají třídy, jen objekty
  • skoro čistě objektový programovací jazyk je SmallTalk

Dědičnost

= Umožňuje definovat novou podtřídu na základě existující rodičovské třídy. Vytváří hierarchii tříd.

Liskov Substitution Principle (LSP) = Objekt typu T může být, bez negativních důsledků, kdekoliv nahrazen objektem typu S, kde S je podtřídou T. (Původně definováno pro abstraktní datové typy.)

Terminologie

  • potomek/podtřída
  • odvozaná tříd
  • rodič
  • dědí
  • rozšiřuje
  • tranzitivní dědičnost

Jak se může lišit třída od svého rodiče?

  1. podtřída může přidat atributy/metody k těm zděděným z rodičovské třídy
  2. podtřída může mít znepřístupněné atributy/metody z rodičovské třídy
  3. podtřída může modifikovat chování zděděných metod; dochází k tzv. překrytí (override) metody

C#: public - členy přístupné kdekoli, private - jen v aktuální třídě, protected - v aktuální třídě a jejích podtřídách

Konstruktory

Když vytvářím objekt podtřídy, implicitně nejdřív zavolám konstruktor rodičovské třídy, a pak si tam mohu doplnit další věci v konstruktoru podtřídy.

C#: base() - zavolání konstruktoru rodičovské třídy

Destruktory jsou volány v opačném pořadí než konstruktory (od dítěte k rodičům).

Dynamická (pozdní) vazba

Pokud má potomek i rodičovská třída metodu ze stejným jménem.

Device d = new SoundDevice();
d.PrintStatus();

Časná (statická) vazba

= Použije se deklarovaný typ proměnné (za překladu)

  • je rychlejší

C#: implicitní, ale používá se klíčové slovo new

Pozdní (dynamická) vazba

= Použije se skutečný typ objektu (za běhu). Překrývání metod.

C#: musím označit virtual - metoda v rodičovské třídě, override - metoda v odvozené třídě

Tabulka virtuálních metod

= mechanizmus používaný pro podporu dynamického výběru funkcí za běhu

  • obsahuje odkazy na metody
  • tabulka pro každou třídu, objekty v ní ukazují na stejnou tabulku

Abstraktní třídy

= třída, kterou nikdy neinstanciuji, pouze z ní dědím (např. třída Expression s podtřídami Addition nebo Multiplication)

  • mohou obsahovat metody s implementací nebo abstraktní metody bez implementace

C#: klíčové slovo abstract, abstraktní metody jsou implicitně virtuální a nemají implementaci

Vícenásobná dědičnost

= Případ, kdy jedna třída dědí z více rodičovských tříd.

diamond problem Diamond problem

C#: zakázaná vícenásobná dědičnost

Python: dané pořadí deklarací

Rozhraní (Interfaces)

= "abstraktní třída" ve velmi omezené verzi, která definuje, jaká funkcionalita musí být ve tříde, která implementuje toto rozhraní

  • obsahuje jen deklarace metod a properties
  • všechny členy jsou implicitně a nastálo public
  • nelze deklarovat static, abstract, virtual
  • není tam žádná implementace (do verze C# 8.0)
  • všechny členy jsou public, protože rozhraní definuje metody, co budou k dispozici na venek (takže nechceme, aby byly private)
  • nemůžu použít static, abstract, virtual
  • Třída implementuje rozhraní, může implementovat i více rozhraní.
  • S rozhraními lze zacházet jako s typy - mohu vytvářet proměnné typu interface, i parametry mohou být typu interface.

Generická rozhraní

= rozhraní, která je parametrizované nějakým typem <T>

Haskell: rozhraní je podobné typové třídě

C#: např. IComparable<T>, List<T>

Zobáčky znamenají generika.

Dědičnost rozhraní

= Rozhraní od sebe můžou navzájem "dědit" - rozšiřují své rodičovské rozhraní.

  • Protože nekopírujeme žádné implementace, tak nemám diamond problem.
  • Pro každé rozhraní, které implementuji mohu mít vlastní implementaci metody (pomocí tečkové notace) a následně je potřeba instanciovat objekt, který je typu jednoho nebo druhého rozhraní a podle toho se potom vybere, která metoda se použije.

Objektové hierarchie

kořen objektové hierarchie = třída, ze které dědí úplně všechno

C#: třída System.Object

Pokud nevím, jak napsat hash, je doporučené použít xor všech mých objektů.

Práce s typy

C#: is - stejná nebo rodičovská třída, typeof() == GetType() - ekvivalence

casting = objekt mohu přetypovat na typ rodičovské třídy (C#: a as B)

explicitní přetypování = C#: (B) a

Boxing

= proces konverze nějakého hodnotového typu na typ object

  • využití: heterogenní seznam (seznam objektů - např. ArrayList nebo Stack), metoda Concat umí spojovat hodnoty různých typů
int i = 42;
object o = i; // boxing

int j = (int) o; //unboxing

Mixins

= dodatečná funkcionalita, kterou využívám v různých třídách mimo logickou strukturu hierarchie dědičnosti

  • nemusím řešit komplikovanou hierarchii tříd
  • nemusím duplikovat kód
  • přehlednost
  • porušuje principy OOP

Python: když chci funkcionalitu mixinu, použiju výcenásobnou dědičnost

C#: prostřdnictvím rozhraní s implementací (od verze 8.0), při volání je potřeba přetypovat na typ rozhraní

Výjimky

= ošetření chyb, které program vygeneruje za běhu

  • výjimky lze vnořovat
  • čitejnější a čistější kód
  • zjednodušuje zpracování chyb
  • drahá operace

C#: všechny výjimky jsou (nepřímými) potomky třídy Exception

Java: dovoluje použít break, continue nebo return ve finally bloku všechny výjimky jsou podtřídou třídy Throwable

  • kontrolované výjimky = specifikuji v hlavičce metody, jaké výjimky se z ní mohou teoreticky propagovat výše
  • nekontrolované výjimky = výjimky, které se v rámci programu nehlídají

Python: except místo catch, raise pro vyvolání výjimky, else blok pro případ že k výjimce nedošlo

Bloky pro obsluhu výjimek

  • try - blok kódu, kde může potenciálně nastat chyba
  • catch - blok, který specifikuje, jakou výjimku zachytávám a co se má stát, když ji zachytím
    • vybere se první blok, který odpovídá vyhozené výjimce
    • bez parametrů se chová jako catch (Exception e)
  • finally - spustí se vždy, i kdy nastane výjimka, i když žádná výjimka nenastane (např. vždy chci zavřít soubor)

Vznik výjimky

throw new Exception

  • Mohu vytvořit svoji výjimku prostřednictvím třídy, které dědí z Exception.
  • Třída má min. 3 konstruktory: bez argumentů, s jedním argumentem se zprávou, a zprávou a vnitřní výjimkou
  • pokud výjimka není ošetřena, propaguje se do volající funkce

Výjimky v C#

Filtry

= podmínka během odchytu výjimky

try {...}
catch (SomeException e) when (index > 0) {...}
catch (SomeOtherException e)
{...}

Best practises

  • výjimky slouží pouze na výjimečné stavy (ne bežné řízení programu)
  • zbytečně nedefinovat nové výjimky
  • používat co nejkonkrétnější výjimku

Opakované vyhození výjimky

= throw bez paramentru pošle výjimku do nadřezeného bloku

Řetězení výjimky

  • přibalení originální výjimky pro nadřezený blok jako info navíc
try {...}
catch (Exception e) {
    throw new Exception("message", e);
}

Náhodné poznámky

  • C# má odvozování typů
  • C# pokud nechci aby se v řetězci braly v potaz speciální znaky, napíšu před řetězec @
  • C# vyřaduje ve switch explicitní break, aby se vykonala právě jen jedna větev

BONUS: SOLID koncepty

Autor: Uncle Bob

  • SRP Single Responsibility Principle = třída se má starat jen o jednu jednou věc
  • OCP Open/Closed Priciple = mělo by být jednoduché třídě přidat novou funkcionalitu bez toho, aniž bych musela ve třídě něco měnit
  • LSP Liskov Substitution Principle = objekt může být nahrazen instancí podptypu
  • ISP Interface Segregation Principle = lepší více konkrétnějších rozhraní než jedno, které dělá všechno
  • DIP Dependency Inversion Principle = obecné třídy by neměly záviset na specializovaných třídách; je vhodné používat abstraktní třídy

Nežádoucí vlastnosti

rigidita = pro malou změnu funkcionality musíme změnit hodně kódu

fragilita = malé změny mohou rozbít program na mnoha jiných místech

immobilita = kód se dá jen těžko recyklovat