42 svobodných a otevřených projektů získalo finanční podporu od NLnet Foundation (Wikipedie).
Americký výrobce čipů Intel plánuje propustit více než 20 procent zaměstnanců. Cílem tohoto kroku je zjednodušit organizační strukturu ve firmě, která se potýká s problémy.
Byla vydána OpenMandriva Lx 6.0 s kódovým názvem Vanadium. Přehled novinek v poznámkách k vydání.
CSIRT.CZ, český národní CERT provozovaný na základě veřejnoprávní správní smlouvy společností CZ.NIC, shrnuje patnáct let svého fungování pod tímto sdružením: CSIRT.CZ – 15 let ve sdružení CZ.NIC.
Commodore OS Vision (Wikipedie) byl vydán v nové verzi 3.0. Jedná se o linuxovou distribuci určenou pro fanoušky značky Commodore. Předinstalována je na počítačích Commodore 64x.
Spolek OpenAlt zve příznivce otevřených řešení a přístupu na 208. brněnský sraz, který proběhne v pátek 25. dubna od 18:00 ve studentském klubu U Kachničky na Fakultě informačních technologií Vysokého učení technického na adrese Božetěchova 2/1.
Ve svém článku Getting Forked by Microsoft popisuje autor programu Spegel svoji nepříjemnou zkušenost s firmou Microsoft. Firma ho kontaktovala a zpočátku to vypadalo, že by mohlo jít o oboustranně prospěšnou spolupráci, autor tedy ochotně odpovídal na jejich otázky ohledně architektury programu a pomáhal jim ho zprovoznit. Následně komunikace ze strany Microsoftu utichla. Autor předpokládal, že zřejmě došlo ke změně priorit a firma
… více »Společnost Notion Labs stojící za softwarovou platformou pro spolupráci Notion (Wikipedia) oficiálně představila (YouTube) poštovního klienta Notion Mail. Aktuálně funguje pouze nad Gmailem.
Byla vydána nová verze 9.12 z Debianu vycházející linuxové distribuce DietPi pro (nejenom) jednodeskové počítače. Přehled novinek v poznámkách k vydání.
Na čem aktuálně pracují vývojáři GNOME a KDE Plasma? Pravidelný přehled novinek v Týden v GNOME a Týden v KDE Plasma.
V dynamických jazycích kontrola probíhá tak, že se za běhu zjistí, zda daný objekt umí požadovanou metodu (rozumí dané zprávě). Pokud ano, pak se zavolá. Pokud ne, vyhodí se výjimka o nepodporované metodě. To, co je důležité, je, že v době překladu (v podstatě jen kontrola syntaxe a převod do bytecode) se toto nezjistí, protože vyhodnocování probíhá až v čase běhu. To je velká změna od psaní ve statických jazycích, kde se tyto věci odhalí v době překladu (popravdě, programátoři většinou spoléhají na to, že toto všechno ošetří 100% překladač). A také velká výtka programátorů ve staticky typovaných jazycích, kteří často berou úspěšnou kompilaci jako důkaz, že je program správně napsaný. U dynamických jazyků je nutné psát testy, které projdou všechny větve a kód spustí, protože vlastní překlad prakticky žádnou kontrolu neprovádí.
Toto chování hezky ilustrují tyto dvě ukázky kódu:
public interface LPrint { public String toString(); } public class cls1 implements LPrint { public cls1() {} public String toString() { return "cls1"; } public String toLongString() { return "### instance of cls1 ###"; } } public class cls2 implements LPrint { public cls2() {} public String toString() { return "cls2"; } } public void longPrint(LPrint x) { System.out.println(x.toLongString); }
class cls1: def __init__(self): pass def __repr__(self): return "cls1" def toLongString(self): return "### instance of cls1 ###" class cls2: def __init__(self): pass def __repr__(self): return "cls2" def longPrint(x): x.longPrint()
Kód v Javě se ani nepřeloží. Překladač zjistí, že typ cls2
neimplementuje metodu longPrint()
a odmítne pokračovat. V Pythonu dojde k chybě až v době běhu a interpret vyhodí výjimku AttributeError
. Překladač v Pythonu pouze zkontroluje tu trochu syntaktických pravidel a vygeneruje bytecode - dokud se kód nespustí, chyba se neprojeví.
Ovšem dynamické jazyky se tak snadno nevzdávají. Následující kód
def longPrint(x): try: x.longPrint() catch (AttributeError e): x.__repr__()
ukazuje sílu toho, že je možné za běhu testovat existenci metody a podle toho reagovat. Od tohoto okamžiku je naše tisková funkce stejně univerzální, jako standardní print
.
Nojo, řekne si javista, jaký je problém napsat něco jako
void longPrint(Object x, Boolean long) { if (long) { LPrint foo = (LPrint) x; System.out.println(foo.longPrint()); } else { System.out.println(x); } }
a za cenu parametru navíc získat stejnou funkčnost? Jistě tušíte, že takové řešení postrádá jistou eleganci a obecnost. Naštěstí Java, díky tomu, že běží v runtime, obsahuje některé dynamické prvky (například operátor instanceof
), takže je možné pomocí java.lang.reflect získat potřebné informace. V případě třeba jazyka C++ bychom byli nahraní (pokud bychom si takový runtime nenapsali sami). Tedy možné to je, ale například autor knihy Hardcore Java tvrdí:
Reflection is one of the least understood aspects of Java, but also one of the most powerful.
Reflexe je jedna z nejméně pochopených částí Javy, ale také jedna z nejsilnějších.
No a aby toho nebylo málo, něco nelichotivého (nejen) o reflexi se dozvíte i od Slavy Pestova, autora jEditu. Ten měl tu "čest" nahlédnout do zdrojáků JVM. Přesto je dobré podotknout, že se pánové od Sunu (ostatní implementace neznám) v posledních verzích docela zlepšili a výkonový propad již není tak markantní. Navíc technologie JavaBean je na existenci reflexe postavená, což je další důvod, proč na zlepšení (žel bohu ne API, ale "jenom" výkonu) reflexe pracovat.
A takto ji řeší dynamické jazyky, kde se k obsahu objektu dostaneme přeci jen snadněji (a navíc neřešíme věci jako rozhraní a podobně). A v těchto jazycích je používání reflexe a introspekce daleko častější než v Javě.
// Javascript for (i in foo) { }
# Python foo.__dict__
" Smalltalk " foo class allSelectors
Obrovská síla dynamických jazyků se projevuje při používání kolekcí. Dejme tomu, že máme seznam čísel. Čísla jsou typu int
a my chceme spočítat (například) jejich aritmetický průměr.
float amean(intList* lst) { unsigned int sum = 0; for (intList::const_iterator i = lst->begin(); i != lst->end(); i++) { sum += *i; } if (lst->size() != 0) { return sum/lst->size(); } else { return 0; } }
Příklad je v C++ a třída intList
je seznam typů int
(ano, znám standardní knihovnu i šablony). Ovšem, jak to tak bývá, je nutné funkci rozšířit o podporu dalších typů jako třeba float
a double
. Díky možnosti přetěžování v C++ (v Javě také) můžeme napsat funkci float amean(floatList* lst)
, nebo float amean(doubleList* lst)
.
A co čert nechtěl, z nějakých důvodů, které jsou mimo nás (změna legislativy, šéfovy nálady, ...), se do algoritmu pro výpočet průměru zavádí změna. Nu a tak musíme přepsat jednu až n funkcí a v žádné neudělat chybu. C++ a Java od verze 1.5 nabízí generické datové typy. Seznam může být obecně libovolného typu, podstatné je, zda daný typ nabízí operátor + (tady je situace v Javě malinko složitější, ta podporuje pouze metody).
template <class T> float amean(list<T;>* lst) { unsigned int sum = 0; for (typename list<T>::const_iterator i = lst->begin(); i != lst->end(); i++) { sum += *i; } // zbytek }
Nyní máme univerzální funkci, která pracuje s libovolnými daty, která se dají sčítat a dělit číslem. Šablony tedy nejsou nic jiného než způsob, jak do statických jazyků přinést prvky těch dynamických. Sémantická kontrola v tom případě nekontroluje typy, ale to, zda existují příslušné operátory (metody). Hezkým důsledkem zavedení šablon je, že se typová kontrola neoslabila. Naopak, použitím šablon se v Javě (i v C++) typový systém naopak posiluje, protože před tím se používal typ Object
(v C++ by se používal void*
). To také ukazuje, že dynamické typování automaticky neznamená slabé typování. Naopak, většina dynamických jazyků je silně typovaná (rozhodně silněji než Java do verze 1.4 se svými Object
y v kolekcích).
def amean(lst): if len(lst) != 0: return reduce(lambda x, y: x + y, lst)/len(lst) else: return 0
To je kód s podobnou funkčností jako ten nahoře. Ovšem s jednou velkou výhodou. Kolekce se nemusí skládat ze stejných, ale různých typů, protože jazyku je to zcela jedno. Důležitá je existence příslušných metod a ne typ proměnné v seznamu. Nebo dokonce typ samotného seznamu. Tento kód v Pythonu je schopný vypočítat průměr z čehokoliv, co lze procházet iterátory (a čeho lze získat délku). V C++ by bylo nutné napsat kód poněkud jinak (stejně jak pracuje standardní knihovna — viz třeba <algorithm>).
template <class L> float amean(L begin, L end) { unsigned int sum = 0; unsigned int cnt = 0; while(begin != end) { sum += *i++; cnt ++; } // zbytek } //zavolani amean(lst.begin(), lst.end());
Taková funkce ovšem trpí tím, že mimo iterátorů z objektu nic víc nemůžeme získat. Já to vyřešil tak, že si délku seznamu spočítám v cyklu. Ovšem toto je v C++ považováno spíše za pokročilou černou magii (stejně jako reflexe v Javě) a podle mých zkušeností málokdo takové věci používá a spíše se tomu většina programátorů snaží (vědomě či nevědomky) vyhýbat. Opět musím upozornit, že v dynamicky typovaných jazycích jsou podobné konstrukce běžné a snadno napsatelné.
To, co se mi osobně na dynamických jazycích líbí nejvíce, je úspornost zápisu. Jádro celého kódu je díky funkcionálnímu reduce
kratší než definice cyklu for
v C++, přičemž je ten kód ve výsledku daleko obecnější. Oproti C++ verzi s iterátory ten kód není tak zatemněný (ovšem jenom pro lidi, kteří znají lambda funkce). A to je Python poměrně ukecaný jazyk, stejný příklad třeba v Ruby by mohl být ještě o něco kratší.
Guido Van Rossum se o tomto zmiňuje v jednom rozhovoru (český překlad naleznete na stánce Jana Švece). Konkrétně v části Productivity and Finger Typing (Produktivita a ruční psaní kódu) říká:
This is all very informal, but I heard someone say a good programmer can reasonably maintain about 20,000 lines of code. Whether that is 20,000 lines of assembler, C, or some high-level language doesn't matter. It's still 20,000 lines. If your language requires fewer lines to express the same ideas, you can spend more time on stuff that otherwise would go beyond those 20,000 lines.
Kdysi jsem slyšel, že dobrý programátor dokáže spravovat 20 000 řádek kódu. Je jedno, jestli 20 000 řádek assembleru, C nebo nějakého vysokoúrovňového jazyka. Stále jde o 20 000 řádek. Pokud váš jazyk vyžaduje méně řádek k vyjádření téhož nápadu, můžete strávit více času na částech, které by se jinak do oněch 20 000 řádek nevešly.
Guido evidentně neslyšel o některých českých géniích, kteří píší tisíce řádků skvěle komentovaného, kvalitního a otestovaného kódu v C++ (nebo v PHP) denně už po několik let :-D
Díky tomu, že u dynamických jazyků je runtime tvořen i jejich interpreterem, je možné používat konstrukce, které vidíme v nadpise. Fakt, že můžeme za běhu konstruovat kód a ten potom nechat provést, je něco, co nám kompilované jazyky nenabízí. Nejlepším příkladem takového přístupu je Lisp, který byl prvním jazykem, který tyto konstrukce zavedl.
(setf a nil) (push 2 a) (push 1 a) (push '+ a) (print a) (+ 1 2) (print (eval a)) 3
V Lispu není rozdíl mezi daty a kódem, takže dynamická konstrukce kódu je pouze plnění seznamu. Generátory kódu pro syntakticky složitější jazyky nejsou tak jednoduché. Na této úrovni je Lisp podobný assembleru, kde není mezi daty a kódem rozdíl. Minimálně to platí pro počítače s von Neumannovou architekturou (a pomineme-li všechny read-only a non-execute příznaky, které beztak slouží jenom k emulaci Harwardské architektury), jejíž zástupce vám pravděpodobně leží na stole.
V jazyce Python není obsažena konstrukce switch-case
. Ovšem v něm také není potřeba (pro zajímavost existuje Python Enhancement Proposal 3103, ve kterém se navrhuje tato konstrukce do Pythonu 3000), protože se dá nahradit za kombinaci slovníku a konstrukcí exec
. Je pravda, že v jiných jazycích (Lisp, Smalltalk) je možné si chybějící příkazy doprogramovat sám, takže se neexistencí nejrůznějších konstrukcí nemusíme vůbec trápit.
case = { 'a' : 'print "a"', 'b' : 'print "b"', 'c' : 'print "c"' } cond = 'b' try: exec(case[cond]) except (KeyError): print "default";
Výhoda dynamického provádění kódu je zřejmá. Je možné za běhu sestavovat kód přesně na míru požadavkům, což kompilované jazyky (snad s výjimkou assembleru) neumožňují. Respektive umožňují, ale za cenu přibalení překladače a linkeru k programu a také za cenu starostí s IPC nebo sdílenou pamětí. Nevýhoda je ta, že se dynamické jazyky nedají kompilovat. Respektive dají... až na všechny výskyty podobných konstrukcí, které musí překládat interpret. Ale protože je u těchto jazyků fáze kompilace a spuštění stejná, není to tak markantní. Jiné jazyky, jako je třeba Java, bohužel v tomto ohledu geniálně
zkombinovaly nevýhody kompilovaných (musí mít speciální překladač) a interpretovaných (musí mít runtime, není to nativní kód) jazyků.
Nakonec bych chtěl ukázat pár věcí, které se mi zdály jako dost zajímavé ukázky možností dynamických jazyků a které staticky typované neumožňují.
Programovací jazyky, které mají jednoduchou syntaxi, jsou nesmírně rozšiřitelné. Všechny základní konstrukce v nich jsou totiž realizovány prostředky samotného jazyka, nikoliv syntaxí. Díky tomu je můžeme používat například ke zkoumání nových konstrukcí, které bychom jinak realizovali dost obtížně. Například podmínka if
je v Lispu pouze makro.
(defmacro if (test then-form &optional else-form) `(cond (,test ,then-form) (t ,else-form)))
Takže je možné vytvořit třeba takovou věc jako vícestavové if
(fuzzy-if) nebo cyklus for
s větví else
(což nějaký jazyk opravdu má).
S využitím speciální metody __getattr__
je možné napsat třídu, jejíž instance umí
všechny metody.
class C: def handlerFunctionClosure(self,name): def handlerFunction(*args,**kwargs): print "call c.%s(%s, %s)" % (name,args,kwargs) return handlerFunction return __getattr__(self,name): return self.handlerFunctionClosure(name)
A příklad volání:
<<<c.f1() call c.f1((), {}) <<<c.f1('a') call c.f2(('a',), {}) <<<c.f1(val='a') call c.f3((), {'val': 'a'})
Tímto způsobem se velice snadno naprogramují různé dekorátory, proxy, nebo jim podobné návrhové vzory.
Srovnání statických a dynamických jazyků je především o srovnání dvou různých přístupů při tvorbě programů. Možná i proto vyvolává mezi programátory tolik vášní. Vyměnit například Pascal za C++ nebo Javu není tak složité. Ale vyměnit ji za odlišný způsob myšlení, který klade nároky na zcela jiné věci, to je asi největší problém. Každý má tendence chválit to svoje, ovšem dynamické jazyky jsou mnohem flexibilnější než ty statické. Na druhou stranu jsou pomalejší a navíc i ty nejtriviálnější kontroly dělají až za běhu programu. Statické poskytují jistý komfort typového systému, kdy musíme překladači přesně deklarovat všechny typy, ale za to máme částečnou jistotu, že je program správně. Skutečně dynamické jazyky s jednoduchou syntaxí (Lisp, Smalltalk nebo Forth) dokonce umožňují programovat samotné jazykové konstrukce (if
, for
, ...) přímo prostředky samotného jazyka. Díky tomu je možné každý z těchto jazyků rozšířit, například pro výzkum nových konstrukcí, což ve většině normálních
jazyků není možné.
Příští díl se bude zabývat tématem operátorů a jejich vyhodnocováním, což s překladači také souvisí.
Nástroje: Tisk bez diskuse
Tiskni
Sdílej:
amean(list<T;>* lst)
tam určitě melo být amean(list<T>* lst)
, že? def amean(l) l.inject(0){|a, x| a+x/l.size} end
# efektivnejsia verzia ? naco ruby dalej zbytocne spomalovat... def amean(l) l.inject(0){|a, x| a+x} / l.size end
def amean(l)
begin
l.inject(0){|a, x| a+x} / l.size
rescue ZeroDivisionError
nil
end
end
Ten [Slava Pestov, autor jEditu] měl tu "čest" nahlédnout do zdrojáků JVM.Čest nahlédnout do zdrojáků JVM má už nějaký ten pátek každý – Java SE Downloads – J2SE 5.0 JDK Source Code. This community source release contains all the source code and makefiles required for building JDK 5.0…
amean
.
return __getattr__(self,name):chcelo byť
def __getattr__(self,name):však? Množstvo chýb ma začína znepokojovať. Ostatné jazyky máš bez chyby? :-P
Výhoda dynamického provádění kódu je zřejmá. Je možné za běhu sestavovat kód přesně na míru požadavkům, což kompilované jazyky (snad s výjimkou assembleru) neumožňují. Respektive umožňují, ale za cenu přibalení překladače a linkeru k programu a také za cenu starostí s IPC nebo sdílenou pamětí. Nevýhoda je ta, že se dynamické jazyky nedají kompilovat. Respektive dají... až na všechny výskyty podobných konstrukcí, které musí překládat interpret. Ale protože je u těchto jazyků fáze kompilace a spuštění stejná, není to tak markantní.Tady aspoň u mne při prvním čtení došlo k matení rozdělení statické/dynamické typování a kompilovaný/interpretovaný (včetně bytekódu) jazyk. Každopádně moderní Lisp má odlišnou fázi kompilace a spuštění, obyčejně má v programu přibalený kompilátor (ať už do čehokoliv), a takovéto konstrukce kompiluje vždy (sbcl) nebo za určitých okolností (dynamicky tvořený kód je obalen do makra a efektivně jej známe v době kompilace nebo je voláno compile). Takže tady je lisp najednou spíše na druhé straně než ve zbytku článku. Mimochodem, používání přímého eval doporučuje z dobrých důvodů většina novějších praktických knih o Lispu asi stejně jako Wirth goto :)
if
pre každú vetvu do progn
a na koniec každej vetvy dať goto koniec
? Je to samozrejme krapátko ťažšie ako ten if
, ale načo implementovať zložité veci ako cond
priamo v jazyku; to je úplne nelispovské. Rovnako nezmyselné by bolo when
ako špeciálna forma a pomocou neho vytvárať if
.
načo implementovať zložité veci ako cond priamo v jazykuOdpověď možná zní, že v době, kdy McCarthy cond navrhoval, nečekal, že by se někdo pokusil tu teoretickou úvahu z axiomatizace algoritmů implementovat, a pak se to chvíli vezlo.
if
ako špeciálnou formou a cond
ako makrom. A tie vetvy cond
sa dajú samozrejme zreťaziť bez goto – (if a1 b1 (if a2 b2 (if...(if an bn)...)))
– a ako sa tak teraz na to pozerám, tak mi to pripadá elegantnejšie, ale nejakú podstatnú výhodu oproti verzii s goto
tam nevidím.
goto
sa nezaobídeme inde. Ako chceš napísať napríklad do
bez goto
, alebo bez nejakej inej jump inštrukcie?
Ale inak uznávam, že sa mi to naozaj páči bez toho goto
viac z čisto estetického pohľadu (define-macro (do times . exprs) `(letrec ((iter (lambda (times) ,@exprs (if (> times 1) (iter (- times 1)))))) (iter ,times))) (do 5 (display "x") (newline))ono to nejde moc videt (ale ptal jste se jak se udela do), takze ve sve podstate je to neco takoveho
(define (cycle times) (define (iter times) (display "blah") (newline) (if (> times 1) (iter (- times 1)))) (iter times)) (cycle 5)diky tomu, ze volani "iter" je v koncovem postaveni, tak jde pouzit tail-call a hodnoty na zasobniku prepsat, tudiz se to chova jako takovy jump bez goto. vic info na http://www.sidhe.org/~dan/blog/archives/000211.html a pokud pisete zasobnikovi vm, tak tam to goto opravdu zavazi.
(defmacro do2 (times &body body) `(labels ((iter (times) ,@body (if (> times 1) (iter (- times 1))))) (iter ,times))) DO2 (do2 5 (print x))Pozn: do je obsazené, proto do2; a možná bych v reálu místo názvu iters použil nějaký gensym, abych nemusel přemýšlet, zda nehrozí klasický problém maker. To u scheme není nutné.
Myslim, ze uvedeny text nie je velmi presny a skor zamlzuje.
Jednak Java nema sablony, ale generiksy. Druhak sablony su v C++ konstruktivnou zalezitostou a teda kompiler vytvara instancie sablon v momente, ked sa pouziju (to napr. znamena, ze ak sa sablona neinstanciuje - teda nepouzije, nerobi sa ziadna semanticka kontrola). Za runtimu sa uz nepozna, ze dana trieda, funkcia... bola vytvorena zo sablony. Takze dynamicnost asi nie je najlepsie pomenovanie, kedze konci s prekladom.
V Jave generiksy funguju len ako silna typova kontrola, ktora navyse v case runtimu neexistuje vobec (prekadac informacie o generickych typoch zahodi). Takze bavit sa o dynamicnosti asi tiez nie je velmi stastne.
s/ksy/xy/
Dále bych tě chtěl upozornit na několik věcných chyb týkajících se pouze příkladů v Pythonu uvedených ve tvém článku:
1. Příklad s longPrint(): voláš metodu x.longPrint(), mělo by být print x.toLongString()
2. Obdobně s dalším voláním x.__repr__(), metoda __repr__ pouze vrátí řetězcovou reprezentaci objektu, pro její vytisknutí musíš použít příkaz print.
3. Použití příkladu foo.__dict__ v části o reflexi je poněkud nešťastné, nejen, že některé atributy nemusí být v dict (zrovna ty dynamické, které vracíte pomocí getter, setter metod), ale i samotné foo.__dict__ nezasvěcenému moc neřekne. Spíše bych reflexi v Pythonu ilustroval pomocí funkcí hasattr(), getattr(), dir(), modulu inspect atd.
4. Příklad amean má špatné odsazení (podobně jako příklad s třídou C). Dále komentář říká, že průměr lze vypočítat ze všeho, co lze procházet iterátory. Zároveň však prvky tohoto iterovatelného objektu musí podporovat operátor + (tj. nesečtete si třeba pole asociativních polí ).
5. Poslední příklad interaktivního sezení určitě není kopírovaný z Pythonu, protože v Pythonu se výzva interpretru značí >>> a nikoli <<<.
Článek je jinak vcelku povedený, jen tak dále. Pouze Python se trochu přiučte a občas si zkuste spustit i interpretr.
To, co se mi osobně na dynamických jazycích líbí nejvíce, je úspornost zápisu. Jádro celého kódu je díky funkcionálnímu reduce kratší než definice cyklu for v C++, přičemž je ten kód ve výsledku daleko obecnější. Oproti C++ verzi s iterátory ten kód není tak zatemněný (ovšem jenom pro lidi, kteří znají lambda funkce). A to je Python poměrně ukecaný jazyk, stejný příklad třeba v Ruby by mohl být ještě o něco kratší.Další příjemná věc na použití abstrakcí jako reduce je to, že umožňují snadnější přechod k škálovatelnějším implementacím algoritmu. Mimochodem, Googlí MapReduce je pokud vím v C++.
longPrint()
a x.toLongString
.
Okrem toho v Pythone sa vyhybame zbytocnym zatvorkam a bodkociarkam v style C++ ako napr. catch (AttributeError e):
alebo print "default";
, lebo to spomaluje program. Namiesto toho staci catch AttributeError:
a print "default"
.
except AttributeError:namísto "catch" (které IMHO v Pythonu není)? A co se týče výpočtu toho aritmetického průměru, ortodoxní Pythonista by to možná napsal takhle:
def amean(lst): try: return sum(lst)/len(lst) except ZeroDivisionError: return 0(Možná tu výjimku ani není třeba odchytávat - však jen ať si probublá).