Souhlasim s kritikou. Navic ta dvojita reference (push @$ret, ...) je zbytecna - staci push @ret... a pak return [ @ret ];
To je argumentace stylem : umim Perl jen trochu - jednoduchy problem v nem vyresim neprehledne - takze Perl je spatny. Ja osobne bych to resil takhle - myslim ze to je jeste lepsi nez ten ukazkovy Python.
sub merge { my @a=@{$_[0]}; my @b=@{$_[1]}; my @ret=(); my $minlen = (@a<@b ? @a : @b); push @ret, map { shift @a, shift @b } (0 .. $minlen-1); push @ret, @a; push @ret, @b; return [ @ret ]; }
def merge(a,b): for aa in a: for bb in b: if bb>=aa: break yield bb yield aa for aa in a: if aa>bb: break yield aa yield bb for bb in b: yield bb a=iter([3,5,7,9,11,13,15]) b=iter([1,2,4,6,12,14,16,17]) c=merge(a,b) print list(c)Nejen ze funguje na jakekoli iteratory, tedy i treba radky ve 2 souborech, ale bude patrne i rychlejsi.
Tyhle čachry s iterátorama mi teda nepřijdou moc srozumitelný. Nepřijde mi kupříkladu příliš intuitivní, jakým způsobem se tady střídá řízení mezi těmi výběry. Musel jsem nad tím chvíli přemýšlet, abych to přečetl. Inu, Python. :-)
A co takhle pěkně čistě deklarativně, pánové? ;-)
(define (merge . lists) (match lists ((xs ()) xs) ((() xs) xs) (((x . xs) (y . ys)) (if (< x y) (cons x (merge xs (cons y ys))) (cons y (merge (cons x xs) ys))))))
1. Že jsou komentářové systémy špatně napsané, není chyba Pythonu. Není však nic jednoduššího, než na začátek každého řádku napsat třeba tečku a pak ji odstranit v editoru pomocí označení sloupcového bloku.
Na Rootu to lze udělat pomocí html tagu:def hello(): print "hello"Takže v tomto případě je chybou autora, že nepoužil náhled a situaci nějak nevyřešil.
2. V Pythonu jsou všechny modifikované proměnné lokální, pokud nepoužijeme klíčové slovo global. Ochránit kód před překlepy není vždy možné a to bez ohledu na použitý programovací jazyk. Rozumný pythonista ale používá nástroje pylint nebo pychecker, které potenciálních problémů najdou opravdu hodně.
3. One-linery psát lze, ikdyž je to samozřejmě samozřejmě obtížnější než v některých jiných jazycích. Zde je moje one-line (z ohledů ke čtenářům rozdělená do více řádků, ale funguje i po spojení do jednoho řádku) verze programu beer bottles:
print (lambda beers : (globals().setdefault('beers', beers) and beers(99)))(lambda count:(((count == 0) and ("No more bottles of beer on the wall, no more bottles of beer.\nGo to the store and buy some more, 99 bottles of beer on the wall.")) or ((count == 1) and ("1 bottle of beer on the wall, 1 bottle of beer.\nTake one down and pass it around, no more bottles of beer on the wall.\n\n" + beers(count - 1))) or (("%d bottles of beer on the wall, %d bottles of beer.\nTake one down and pass it around, %d bottle of beer on the wall.\n\n" % (count, count, count - 1)) + beers(count - 1)) ))
nutnost explicitních dereferencí je něco, co by se v moderním jazyku už dávno vyskytovat nemělo.
S tim nesouhlasim. Pokud to syntakticky neodlisite, musite zaridit, aby se kazda reference chovala jako samotny objekt. To znamena, ze pri kopii reference budete kopirovat i objekt. V opacnem pripade programator nebude vedet, co se stane, kdyz napise a<-b
. Respektive to bude muset nejak "odtusit" z kontextu, coz rozhodne neprispiva k vytvoreni korektniho kodu.
Číst Perl je díky těm operátorovým zvratkám vo držku
Reference v Perlu jsou jako Perl samotny. Kdo tomu nerozumi, mysli si ze to je o drzku a dela si z toho vtipy. Kdo tomu rozumi, tak uz nechce jinak :-)
Navic jako v kazdem jazyce, i v Perlu lze psat uhledny kod i bordel. Na druhou stranu, sebelepe formatovany kod nepomuze, pokud nekdo nezna syntaxi jazyka.
a to by se v perlu navíc správně mělo místo "@$a" psát "scalar(@{$a})" atp..
Nemelo, @$a
je naprosto spravny zapis. Navic scalar(@{$a})
dela neco jineho.
Nečitelnost není vlastností jazyka ale programátora. Co třeba takhle?
sub merge {
my $r_a=shift;
my $r_b=shift;
my @ret=();
my ($ia,$ib)=(0,0);
my ($ctia,$ctib)=(1,1); # 0..necti 1..cti 2..konec
my ($maxa,$maxb); # maximalni indexy
my $prveka;
my $prvekb;
$maxa=$#{@{$r_a}};
$maxb=$#{@{$r_b}};
while (1) {
$ctia=2 if ($ia>$maxa);
$ctib=2 if ($ib>$maxb);
last if ($ctia==2 and $ctib==2);
if ($ctia==1) {
$prveka=$r_a->[$ia++];
$ctia=0;
}
if ($ctib==1) {
$prvekb=$r_b->[$ib++];
$ctib=0;
}
if ($prveka<$prvekb) {
push(@ret,$prveka);
$ctia=1;
}
elsif ($prveka==$prvekb) {
push(@ret,$prveka);
push(@ret,$prvekb);
$ctia=$ctib=1;
}
else {
push(@ret,$prvekb);
$ctib=1;
}
}
return (\@ret);
Síla? Vážně? A kromě toho, že se to vám nováčkům pěkně čte to má jaké unikátní výhody? Zatím jsem si totiž všiml jediné věci: když se kód rozformátuje, tak jste v hajzlu a opravit to znamená projít a zkontrolovat celou funkcionalitu, protože pouhé odsazení má syntaktický význam - náležitost do sub-bloků kódu. Kromě toho to taky znamená, že si s tím moc nepohrajete, co? Jak to není pod sebou, je to blbě... Zajímavá myšlenka, opravdu!
Co mě ale nejvíc fascinuje - když nějaký beginner (soudím podle tvé implementace) porovnává dva jazyky a přitom zná jen jeden z nich. Fakt přínos.
Teď ti ukážu čtyři možnosti implementace: tvoji, a la Perl, lepší a správnou. První tři jsou ekvivalentní, čtvrtá funguje jak bych si představoval (obecněji). Je to subjektivní hodnocení, takže necháme ostatní, aby to posoudili. Samozřejmě existuje nekonečně mnoho způsobů jak v Perlu správně napsat to, oč jsi se pokusil ty, a to je důvod, proč mám Perl rád.
Mimochodem, nikdy nemám problém s "čitelností", kde se tahle kravina vzala? A reference? Kde bysme byli s C bez pointerů (i.e. referencí) nebo hůř, kdyby C (de)referenci provádělo automaticky?
Přiložený zdroják je samostatný, stačí paste a spustit. Je to i s benchmarkem všech metod.
#!/usr/bin/env perl # vi:ft=perl use Benchmark qw( :all ); my @TA1 = qw/ 1 2 3 /; # Test case A my @TA2 = qw/ 1 5 3 4 5 6/; my @TB1 = qw/ 2 2 3 4 5 6/; # Test case B my @TB2 = qw/ 1 3 3 4 7 /; my (@M1, @M2); # Vysledky #########################[ Reseni 1 ]######################## # # Tvoje sragora, mimochodem v prvnim "push" jsi mel chybu, # cpal jsi do vysledku prvky pole A misto pole B. A chybela # ti zavorka pred elsif. # # Uz jen ty reference a dereference vsude, pane boze!! # sub merge { my ($a, $b) = @_; my $ret = []; my $ia = 0; my $ib = 0; while (1) { if ($ia == @$a) { last if $ib == @$b; push @$ret, $$b[$ib++]; } elsif ($ib == @$b or $$a[$ia] < $$b[$ib]) { push @$ret, $$a[$ia++]; } else { push @$ret, $$b[$ib++]; } } return $ret; } @M1 = @{merge(\@TA1, \@TA2)}; @M2 = @{merge(\@TB1, \@TB2)}; print "Reseni 1a: @M1\tReseni 1b: @M2\n"; #########################[ Reseni 2 ]######################## # # Takhle bych to napsal od boku. # Lepsi? Co myslis? # sub merge2( \@\@ ) { my @a = @{$_[0]}; my @b = @{$_[1]}; my @c = map { my @c; push @c, shift @b while $b[0]<=$_ && @b; @c, $_ } @a; return @c, @b; } @M1 = merge2 @TA1, @TA2; @M2 = merge2 @TB1, @TB2; print "Reseni 2a: @M1\tReseni 2b: @M2\n"; #########################[ Reseni 3 ]######################## # # To same, jen bez kopirovani poli a s primou indexaci. # sub merge3( \@\@ ) { my ($a, $b) = @_; my (@c, $i); map { push @c, $b->[$i++] while $b->[$i] <= $_; push @c, $_ } @{$a}; return @c, @{$b}[$i..$#{$b}]; } @M1 = merge3 @TA1, @TA2; @M2 = merge3 @TB1, @TB2; print "Reseni 3a: @M1\tReseni 3b: @M2\n"; #########################[ Reseni 4 ]######################## # # Tvuj priklad ale testovaci pole spojuje blbe, viz vystup, # protoze predpoklada, ze jsou obe setridena. Co z toho leze # kdyz nejsou jsi videl. Ale beru, zes to tak treba chtel. # # Tohle reseni je ze vsech nejlepsi, protoze pole zmerguje # i v pripade, ze nejsou predem setridena a hlavne je trikat # rychlejsi nez ostatni (a nepotrebuje extra funkci:) # @M1 = sort @TA1, @TA2; @M2 = sort @TB1, @TB2; print "Reseni 4a: @M1\tReseni 4b: @M2\n\n"; ##########################[ Timing ]######################### my $c = 100000; print "Cas reseni 1:\n\t"; timethis($c, sub { @M1 = @{merge(\@TA1, \@TA2)}; @M2 = @{merge(\@TB1, \@TB2)} }); print "Cas reseni 2:\n\t"; timethis($c, sub { @M1 = merge2 @TA1, @TA2; @M2 = merge2 @TB1, @TB2 }); print "Cas reseni 3:\n\t"; timethis($c, sub { @M1 = merge3 @TA1, @TA2; @M2 = merge3 @TB1, @TB2 }); print "Cas reseni 4:\n\t"; timethis($c, sub { @M1 = sort @TA1, @TA2; @M2 = sort @TB1, @TB2 });Výstup:
Reseni 1a: 1 1 2 3 5 3 4 5 6 Reseni 1b: 1 2 2 3 3 3 4 4 5 6 7 Reseni 2a: 1 1 2 3 5 3 4 5 6 Reseni 2b: 1 2 2 3 3 3 4 4 5 6 7 Reseni 3a: 1 1 2 3 5 3 4 5 6 Reseni 3b: 1 2 2 3 3 3 4 4 5 6 7 Reseni 4a: 1 1 2 3 3 4 5 5 6 Reseni 4b: 1 2 2 3 3 3 4 4 5 6 7 Cas reseni 1: timethis 100000: 3 wallclock secs ( 2.93 usr + 0.00 sys = 2.93 CPU) @ 34129.69/s (n=100000) Cas reseni 2: timethis 100000: 3 wallclock secs ( 3.05 usr + 0.00 sys = 3.05 CPU) @ 32786.07/s (n=100000) Cas reseni 3: timethis 100000: 3 wallclock secs ( 2.75 usr + 0.00 sys = 2.75 CPU) @ 36363.64/s (n=100000) Cas reseni 4: timethis 100000: 1 wallclock secs ( 0.97 usr + 0.00 sys = 0.97 CPU) @ 103092.78/s (n=100000)
Napsal jsem jednu netrivialni aplikaci v Pythonu. Reknu Vam, co se mi na Pythonu oproti Perlu nelibilo:
1. Zadne deklarace. Nekdo to povazuje za vyhodu, me to desne chybelo. Hledat chyby v kodu, kde se clovek prepise v nazvu promenne nebo kde zapomene vytvorit promennou pred telem vnoreneho bloku je fakt opruz.
2. Prakticky nepouzitelne defaultni hodnoty parametru funkci. Myslim, ze jednoduchy priklad je dostacujici vysvetleni:
def t (s = []):
s.append (1)
return s
print t()
print t()
3. Nedostacujici uzavery/lambda funkce. Python ma sice konstrukt lambda:, ale v jeho tele muze byt pouze vyraz.
4. Chybejici reference. Chybejici reference znemoznuje napsat neco tak zakladniho, jako je funkce swap.
Tohle jsou vsechno zasadni veci. Pak existuje spousta dalsich mensich problemu jako napr. osklive __identifikatory__, nutnost psat vsude self., chybejici autovivifikace atd.Pokud jde o podtržítka, kdo sleduje vývoj Pythonu, ten ví, že magické metody přibývají snad s každou novou verzí. Určitě by bylo pro vývojáře nemilé, pokud by měli nadefinovanou nějakou metodu, jejíž název se v další verzi zalíbí vývojářům Pythonu a následkem té kolize bude objekt v některých situacích vykazovat podivné chování nebo házet výjimky kvůli odlišnému počtu parametrů. Ano, Python jiný prostředek nenabízí. Jiné jazyky však nenabízejí ani tento.
def minmax(a, b): return min(a, b), max(a, b) a, b = minmax(a, b)Pokud budu mít složitější program, stejně budu pravděpodobně pracovat se složenými typy a ne s primitivními. Co se týče příkladu na tu implicitní hodnotu, tam není důvod implicitní hodnotu vynechat a případný prázdný seznam posílat explicitně. Jasně, Perl dovoluje každý problém řešit ne dvěma nebo pěti způsoby jako Python, ale dvaceti až padesáti. Toto Python neumožňuje schválně, což však někteří z nás spíše vítají. Pokud má někdo námitku, že nějaký problém v Pythonu špatně řešitelný, pobavme se o tom. Pokud ale námitka zní, že v Perlu nebo některém jiném jazyce to dělá takhle a Python to neumožňuje (ne, skutečně nepotřebuju sahat na parametry funkce pomocí shift ani $_), není to pro mě argument.
print t([]) print t([]) print t(t([])) print t(['a','b']) print t(['x'])Toto ako zaobchadza Python s listami ma tiez netesi, lebo pri priradeni list2=list1 sa vytvori len nova referencia na ten isty list. Ak chce clovek vytvorit novy list ako kopiu stareho treba urobit list2=list1[:]
def halo(meno="Vaclav"): return "Halo %s" % meno print halo() print halo ('MikRom')3. Lambda funkcie neposkytuju taku funkcionalitu ako pomenovane funkcie - ano s tym suhlasim, ale Guido sam nema zrejme lambdy rad a chcel ich v Pythone 3000 povodne aj zrusit. Zrejme si mysli, ze sa moc nepouzivaju. 4. Ze referencie nechybaju, toho dokazom je tvoj priklad s funkciou t(). Na zoznamy sa vzdy pouzivaju referencie. Swap tiez funguje
l1=['a','b'] l2=['x','y'] print "l1=%s, l2=%s" % (l1, l2) l1, l2 = l2, l1 print "l1=%s, l2=%s" % (l1, l2)Ina vec je, ze netreba explicitne dereferencovat ako v Perle. Ale to pracu so zlozitejsimi strukturami podla mna skor zjednodusuje.
if __name__ == "__main__": ...ale je to podobne ako vediet v Perle, co to je $_,@_,...
def generuj_soucet(a,b): yield 'print '+str(a)+'+'+str(b) def generuj_funkci_soucet(x,y): yield 'def soucet('+str(x)+','+str(y)+')' for s in generuj_soucet(a,b): yield ' '+s for s in generuj_soucet(1,2): print s for s in generuj_funkci_soucet('cislo1','cislo2'): print sPak muzete generator generuj_soucet pouzit na obou urovnich, a budete mit zarovnani spravne, jak ukazuje funkce generuj_funkci_soucet.
Tomu moc nerozumim. Neni proste v dynamickem jazyce lepsi, nez ten kod vygenerovat, ho rovnou provest?Jako přes eval? A co když ten jazyk používá inkrementální in-memory kompilátor, jako třeba všelijaké mutace Lispu? to ho budete pokaždé znovu kompilovat? ;-)
A generovat kod, ktery bude dale nekdo upravovat rucne, mi nepripada zrovna dobry napad (z hlediska spravy).Proč? To nepoužíváte kompilátory? Nikdo snad neříká, že by se ručně upravoval vygenerovaný kód, takoví šílenci snad už vyhynuli. Pokud generátor vytváří správný výstup, není třeba vrtat se v jeho výstupu - přece když potřebuju upravit program, taky se nevrtám ve strojáku hexeditorem, ale upravím zdrojový kód a zrekompiluju ho. Nevim co nazyvate "cistym zpusobem generovani kodu" - kod (ve smyslu zdrojak) jsou prece retezce. Ehm, řetězce? Už jste slyšel o syntaktickych stromech? A o jejich transformátorech? Nebo třeba o partial application? Pokud vím, partial application používá třeba Psyco.
Tomu moc nerozumim. Neni proste v dynamickem jazyce lepsi, nez ten kod vygenerovat, ho rovnou provest?Jako přes eval? A co když ten jazyk používá inkrementální in-memory kompilátor, jako třeba všelijaké mutace Lispu? to ho budete pokaždé znovu kompilovat? ;-)
A generovat kod, ktery bude dale nekdo upravovat rucne, mi nepripada zrovna dobry napad (z hlediska spravy).
Nikdo snad neříká, že by se ručně upravoval vygenerovaný kód, takoví šílenci snad už vyhynuli. Pokud generátor vytváří správný výstup, není třeba se vůbec vrtat v jeho výstupu - přece když potřebuju upravit program, taky se nevrtám ve strojáku hexeditorem, ale upravím zdrojový kód a zrekompiluju ho.
Nevim co nazyvate "cistym zpusobem generovani kodu" - kod (ve smyslu zdrojak) jsou prece retezce.Ehm, řetězce? Už jste slyšel o syntaktickych stromech? A o jejich transformátorech? Nebo třeba o partial application? Pokud vím, partial application používá třeba Psyco. Techniky jako partial application dále stírají rozdíly mezi kompilátory a interprety a přitom můžou často vyžadovat generování kódu za chodu, s tím, že za to něco přinesou - v případě Psyca třeba ten výkon. :-)
Probém je, kam to generování napasujete. Pokud jazyk AST nějakým způsobem neexponuje (což většina jazyků nedělá), holt se člověk musí uchýlit ke generování textu. Tím sice nenapíšete něco jako Psyco, ale něco jako třeba ESQL už ano, a i k takovým věcem se generování kódu často používá. Nebo třeba AOP, pokud to jazyk neumí lepšími prostředky, jako je třeba metaobjektový protokol. Ale určitě má smysl tyhle věci dělat.
class Generator(object): def __init__(self): self.codeRows = [] def indent(self): self.add('$I') def detent(self): self.add('$D') def add(self, row): self.codeRows.append(row) def process(self): indentLevel = 0 indentedRows = [] for row in self.codeRows: if row == '$I': indentLevel += 1 elif row == '$D': indentLevel -= 1 else: indentedRows.append(indentLevel * 4 * ' ' + row) exec '\n'.join(indentedRows) g = Generator() g.add('print "Zaciname"') g.add('for a in xrange(3):') g.indent() g.add('for b in xrange(4):') g.indent() g.add('print a * b') g.detent() g.detent() g.add('print "Vse v poradku"') g.process()Řešení č. 4: Nasazení preprocesoru případně i v kombinaci s řešením č. 3.
def process_code(code): indentLevel = 0 indentedRows = [] for row in (r.strip() for r in code.split('\n')): if (not row): continue if row == 'BEGIN': indentLevel += 1 elif row == 'END': indentLevel -= 1 else: indentedRows.append(indentLevel * 4 * ' ' + row) return '\n'.join(indentedRows) exec process_code(''' print "Zaciname" for a in xrange(3): BEGIN for b in xrange(4): BEGIN print a * b END END print "Vse v poradku" ''')
def process_code(code): indentLevel = 0 indentedRows = [] for row in (r.strip() for r in code.split('\n')): if (not row): continue if row == 'BEGIN': indentLevel += 1 elif row == 'END': indentLevel -= 1 else: indentedRows.append(indentLevel * ' ' + row) return '\n'.join(indentedRows) exec process_code(''' print "Kaslu na level" for a in xrange(3): BEGIN for b in xrange(4): BEGIN print a * b END END print "Vse v poradku" ''')Tak co, Petře, furt problémy?