"Čistě funkcionální jazyky obecně nedovolují modifikace datových struktur"
Tohle má dvě roviny, teoreticky (sémanticky) modifikace nedovolují, ale konkrétní implementace to běžně dělají, když překladač umí zaručit referenční transparentnost.
jasně, interně se data mezi strukturami sdílí (jinak by třeba vektory a mapy v Clojure byly neúměrně pomalý) atd. ale z pohledu programátora jsou to immutable struktury
tady je vubec skoda, ze mezi pythonnimi lambda vyrazy a "plnotucnymi" funkcemi nejsou lambda funkce. Kdyz to umi i pitomej JS...
Až taková škoda to podle mě není. Pythonní funkce lze psát vnořeně, mají přístup k proměnným nadřazené funkce, těch pár znaků navíc nikoho nezabije. Že něco umí konkrétně pitomej JS, pro mě není zrovna známka potřebnosti. Máš nějaký use case, který by lambda funkce nějak zásadně potřeboval?
no uz treba ty dva if-y v sobe jsou dost necitelny. Hmm ted me napada, jestli ty nove pattern matching struktury jsou vyrazem. pokud jo, tak by to stacilo.
(ja nemam JS rad, ale v tomto to ma IMHO lepsi nez Python. a Lua to ma taky podobne jako JS)
Ty dva ify v sobě jsou nečitelné hlavně proto, že autor nepoužil závorku k vyjasnění struktury ve výrazu - závorka dost podle mě pomůže:
converted = ["negative" if x < 0 else ("positive" if x > 0 else "zero") for x in values]
Dokonce můžeš napsat třeba tohle, tam nevidím žádnou nejasnost:
converted = [ "negative" if x < 0 else "positive" if x > 0 else "zero" for x in values ]
Pro složitější věci lze vždycky napsat funkci a pak ji použít.
Konkrétně je obojí kupa hnoje. První zápis bych asi ještě zkousnul. Ale zapsat to na více řádků mě přijde jako úplný nesmysl. Tam postrádám jediný rozumný důvod proč nepoužít for a vnořené if. Navíc je to ještě nečitelnější než bez jakýchkoliv závorek.
l = []
for x in values
if x < 0:
l.append("negative")
elif x > 0:
l.append(""positive")
else:
l.append("zero")
nebo ješte lépe, jak bych to udělal:
def foo(x):
if x < 0:
return "negative"
elif x > 0:
return ""positive"
else:
return "zero"
converted = [foo(x) for x in values]
V pohodě. Pokud je Tvoje řešení použít raději cyklus, nemám problém. Edit: A s tou funkcí to je samozřejmě ještě lepší, ale tu možnost jsem taky zmiňoval.
27. 7. 2023, 14:35 editováno autorem komentáře
Je to úplně stejný Yoda speech jako byl, akorát to je přepsané tak, aby to bylo čitelnější. Samozřejmě můžeš navrhnout jiné řešení, ale obecně třeba rozhození delší podmínky na více řádků dost pomáhá.
Jsi moc náročný, Python v praxi slouží jen pro glue code do nativních knihoven (to říkají jeho zastánci, když někdo argumentuje jeho pomalostí).
jednu naši službu v Pythonu jsme prostě naškálovali 64x a nakonec to jede (výsledky agreguje jediná instance v Go :-)
něco to stojí, to je ta daň za Python tedy zde konkrétně za CPython (Numba by to řešila)
My nedávno potřebovali komponentu (mikroslužbu) v ObjC. Naštěstí do toho šla staticky přilinkovat síťová vrstva v Go, ale je to slušný Frankenstein :)
Mozna je to dobre. Plnohodnotne anonymni funkce by neskutecne zkomplikovaly odsazovaci pravidla.
Anonymni bloky kodu jdou v nekterych situacich predavat pomoci kontext manageru.
27. 7. 2023, 13:54 editováno autorem komentáře
nazývá se generátorová notace, což je poněkud nepřesně přeložený anglický termín (list/tuple) comprehension
No tak tenhle český výraz vidím prvně a není nepřesný, ale úplně špatný. Když už tak generátorovou notaci bych přiřadil jenom k generator comprehension ale určitě ne k list nebo dict. A co koukám do dokumentace, tak se tam vždy píše o generator expression a list comprehension viz: https://docs.python.org/3.13/howto/functional.html#generator-expressions-and-list-comprehensions
Ještě jedna věc, kterou jsem nedávno objevil, když jsem potřeboval udělat typ k nějaké složitější funkci a s Callable to prostě nešlo (nebo to bylo šílené) . Lze použít typing.Protocol https://docs.python.org/3/library/typing.html#typing.Protocol
class Ask(Protocol):
def __call__(self, question: str, default: str | None = "yes") -> bool: ...
def foo(call: Ask):
call("something")
plně souhlasím, že to není dobře přeložený termín, ale zrovna ve světě Pythonu se to snaží takto prosadit (http://diveintopython3.py.cz/comprehensions.html#listcomprehension, https://coobas.gitlab.io/python-fjfi/posts/iteratory-a-generatory.html#Gener%C3%A1torov%C3%A1-notace). V tomto případě bych asi zůstal u původního list comprehension nebo ještě lépe - prostě používat map a filter :) [ale to není idiomatické, vím, vím]
Absolútne súhlasím s názorom Sailjacka, že ten výraz nie je len nepresný, ale je úplne nesprávny.
To, že sa niekto snaží niečo presadiť, aj keby sa to malo diať dlhodobo, vôbec neznamená, že sa to má nasledovať, zvlášť pokiaľ je to nesprávne.
Prekladanie termínov ako také je pritom podľa mňa potrebné, lebo môže odstrániť jednu úroveň zložitosti v chápaní preberanej látky. Takže ja osobne to podporujem aj veľmi oceňujem a to napriek tomu, že s angličtinou naozaj problém nemám.
Je ale nutné, aby preklady neboli otrocké a aby vychádzali z nejakého kontextu.
Ten kontext existuje a zvyčajne presahuje do iných oborov, pre náš obor väčšinou asi do matematiky.
Podľa výsledkov krátkeho hľadania, na základe rýchleho preletenia textom, keď sa toto konkrétne riešilo v roku 2010, tak v diskusii bol odkaz, že to má v pythone pripomínať zápis definície množín, kde sú uvedené aj vlastnosti jednotlivých členov. Takže preklad by mal vychádzať z toho a nie z prekladu slov alebo z toho, že sa tam niekedy používa nejaký generátor, ktorý s tým pritom ale vôbec nijako nesúvisí, ten predsa môže byť použitý aj v cykle. Teda aspoň si to myslím, v pythone som už dávno nepísal ;-)
Podobne, aj v nejakom dávnejšom článku som si všimol odkaz na preklad point free alebo tacit programming. To prvé má tiež pôvod v matematike a má to tam nejaký význam. To druhé v latinčine, tak ako enormné kvantum výrazov, ktoré v IT, aj v bežnom jazyku dlhodobo používame.
Jednoducho, keď je nejaký termín použítý v nejakom novom obore nemusí to vôbec znamenať, že bol v tom novom obore použitý ako v prvom.
Nejaký kontext jednoducho v tom pôvodnom nepreloženom názve je, niečo sa nejako volá, lebo sa to podobá na niečo, čo sa dlhodobo používa inde, ľudia, ktorí to v origináli nejako pomenovali v tom vidia nejakú spojitosť a asi to v čase pomenovania chápali tak, že daná spojitosť prispieva k pochopeniu látky a umožňuje vidieť ten nový koncept v súvislostiach s pôvodným konceptom.
A ešte na záver, podľa môjho skromného názoru, preklad naozaj nemusí obsahovať rovnaký alebo menší počet slov a ani nemusí mať slová v rovnakom poradí, zvlášť ak majú dva jazyky iné pravidlá. Preklad má byť jednoducho zrozumiteľný a popisovať to, čo pojem znamená.
Bylo by fajn, kdyby autor někdy napsal článek o moderních trendech ve funk. jazycích. Konkrétně mě napadá například “local mutability” (a její čistě funkcionální interpretace a implementace). To jen tak trochu wishful thinking. (Byl by to návrat k ML-like jazykům.)
no mám zatím rozepsaný F#, ale asi to nechám uležet na začátek podzimu. Teď je okurková sezóna asi i tady na rootu :-)
Ale díky za inspiraci, podívám se i na něco dalšího.
Až budu doma. Jinak zajímavé jsou i některé vedlejší produkty FJ, pro Koku například vznikl Mimalloc, který se teď často používá v Rustu.
Tady jsou příklady témat, co jsem měl na mysli:
https://leanprover.github.io/talks/IFL2019.pdf
https://arxiv.org/pdf/1908.05647.pdf
https://dl.acm.org/doi/pdf/10.1145/3547640
Ta lokální imperativita přeskládá kód s mutabilními proměnnými do “do” bloků (pomocí bind) v čistě funkcionálním jazyce. A ten FBIP zaručí čistě funkcionální datové struktury s výkonem mutabilních (když je refcount 1). Pokud jsou všechny funkce totální a všechny typy induktivní, znamená to, že program vždy skončí a zaručeně nemá memory leaky. Což jinak zaručí jen tracing GC, akorát že nedeterministicky. Jsem k dispozici k případným dotazům :)
BTW můžu případně pomoct s benchmarky, ono to není až tak triviální, teď jsem zjistil, že všechna porovnání s Julií a Rustem musíme předělat. To “do unchained” je fakt slušná magie.
Keď som sa prvý krát bližšie zoznámil s list comprehensions v Pythone, tak som si pomyslel, aká super vec!
O pár rokov neskôr som narazil na F# a tam mi spadla sánka. Ten obsahuje bezhraničné možnosti spracovania dátových štruktúr. Python je len slabý odvar možností funkcionálneho programovania a aj to je zakamuflované kdesi v nič nehovoriacich moduloch ako itertools.
(Aj tak asi väčšina programátorov používa Pandas a pod. a nie built-in funkcie.)
Odtedy som presedlal na F# a Groovy keď treba pokročilú dátovú analýzu. Oba jazyky majú priamo zabudované funkcie a navyše query výrazy. (Ginq v Groovy je inšpirovaný LINQom zo C#.)
Groovy príklad
def numbers = [0, 1, 2, -3, -1, 9]
def res = GQ {
from n in numbers
where n > 0
orderby n in desc
select n
}
println res
def res2 = numbers.findAll { it > 0 }.sort { a, b -> b <=> a }
println res2
F# príklad
type User =
{ FirstName: string
LastName: string
Occupation: string
Salary: int }
let users = [
{ FirstName="Robert"; LastName="Novak"; Occupation="teacher"; Salary=1770 }
{ FirstName="John"; LastName="Doe"; Occupation="gardener"; Salary=1230 }
{ FirstName="Lucy"; LastName="Novak"; Occupation="accountant"; Salary=670 }
{ FirstName="Ben"; LastName="Walter"; Occupation="teacher"; Salary=2050 }
{ FirstName="Robin"; LastName="Brown"; Occupation="bartender"; Salary=2300 }
{ FirstName="Amy"; LastName="Doe"; Occupation="technician"; Salary=1250 }
{ FirstName="Joe"; LastName="Draker"; Occupation="musician"; Salary=1190 }
{ FirstName="Janet"; LastName="Doe"; Occupation="actor"; Salary=980 }
{ FirstName="Peter"; LastName="Novak"; Occupation="singer"; Salary=990 }
{ FirstName="Albert"; LastName="Novak"; Occupation="teacher"; Salary=1930 }
]
let users2 =
users
|> List.map (fun e ->
{| FirstName = e.FirstName
LastName = e.LastName |})
users2 |> List.take 3 |> List.iter (printfn "%A")
printfn "---------------------"
query {
for user in users do
select {| FirstName = user.FirstName; LastName = user.LastName |}
take 3
} |> Seq.iter (printfn "%A")
Budem piť Bud light keď môžem Prazdroj?
jj F# je fajn jazyk ze skupiny ML. Sice tam kvůli zasazení do .NET udělali ústupky, ale takový lepší OCaml to je.
Už som si to pozeral a aj pár príkladov skompiloval. Syntax je fakt parádna, páči sa mi aj používanie nelatinských znakov.
Pokiaľ však nebudú k dispozícii externé knižnice, tak v tom veľa nespravíme.
Jo, syntax je super (prostě à la ML). Externí knihovny moc nejsou, ale má to bezešvé FFI do C++ (případně Rustu nebo Go), linkuje si ho staticky.
Pozor na to, že map i filter vrací generátor (tuším, že ke změně došlo mezi py2 a py3). A obsah generátoru se vyhodnocuje líně, až ve chvíli, kdy je to nezbytně potřeba. I proto jsou demonstrační příklady plné list()ů. Takže jednak přes výsledek nepůjde iterovat dvakrát...
>>> g = map(lambda x: x, range(10)) >>> list(g) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> list(g) []
... druhak, pokud map-ujete něco, co má svůj stav, výsledek nebude odpovídat stavu, kdy byl generátor vytvořený, ale stavu, kdy byl rozbalený. Já vím, není to funkcionální, ale mohlo by to překvapit.
>>> class Multiplier: ... def __init__(self, by): ... self.by = by ... def __call__(self, number): ... return self.by * number ... >>> m = Multiplier(1) >>> num_in = list(range(5)) >>> num_out = map(m, num_in) >>> m.by = 7 >>> list(num_out) [0, 7, 14, 21, 28] >>> num_out = map(m, num_in) >>> num_in.append(11) >>> list(num_out) [0, 7, 14, 21, 28, 77]
E: oprava vyšinutí z větné stavby
27. 7. 2023, 16:58 editováno autorem komentáře
máte absolutně pravdu (+ používat funkci se stavem u filter a map je cesta do hodně nepěkných míst :-)
jinak ten příklad jde přepsat na použití uzávěru a nolocal (už to, že se musí přidat nové klíčové slovo, značí, že to není moc čisté)
U světla, tykat prosím, děkuju Vám :)
Jo, jde to napsat líp, a já bych takhle map dobrovolně nepoužil, ale programátoři (a skoro bych řekl zejména v pythonu) jsou různí. Kdo ví, nedokážu si představit, že by "mutabilní map" byl k něčemu dobrý, ale nehodlám kategoricky tvrdit, že taková situace nenastane.
Dost pochybuju, podle toho, co kolem sebe vidím. Od září budu učit Computational applied mathematics (v Pythonu, hehe), tak jsem zvědavý, kdo tam bude psát typy.
Nebavím se o seminárkách, ale o firmách se stovkami tisíc řádků kódu. U nás jsme třeba začali a na Redditu těch lidí, kteří si to nemůžou vynachválit, psala spousta. Pravdou je, že každý může mít tu motivaci jinou - někdo řeší korektnost (mypy/pydantic), někdo chce optimalizaci (mypyc/lpython), někdo jiný zase staví weby na FastAPI. Už i obyčejné dataclassy jsou postavené na typových anotacích.
Zrovna Ty, kterej zminil OCaml Scientific Computing, zna Idris, Julii, Fortran? :)
Kde ten kurz pobezi?
Já si to nevymyslel, takový modul se připravuje roky a musí být přiměřeně konzervativní. Přišel jsem už k hotovému. Julia by byla určitě lepší, ale to při troše štěstí až s dalším updatem někdy za 10 let. (Je to na Open University).
Nicméně pokud se chytí ten LPython a třeba i Mojo, tak by to mohlo zůstat jako schůdná alternativa.
Abych to doplnil, já plánuju všechny příklady a cvičení přepsat do Fortranu a Julie, ale to jen čistě z vlastní iniciativy (a taky pro benchmarky).
Pekny, nasel jsem si osnovu. Tedy popravde receno ta cena vypada docela brutalne, ale asi na UK dobry.
"možnost zapamatování návratových hodnot funkcí v cache (což je pro čisté funkce samozřejmě možné)"
Asi jen pro totální funkce :)
Mně to pro čisté funkce ve smyslu https://en.wikipedia.org/wiki/Pure_function přijde v pořádku. Otázka definice nebo je i s nimi problém?
Nevím jak to je v pythonu, ale čekal bych že buď parciální funkce něco vrátí jako error hodnotu (a to se dá cacheovat), nebo signalizuje výjimku a to beru jako side effect a není čistá. Ještě mne napadá vracení multiple values výsledek - výsledek platí, ale to snad v Pythonu není.
Co mi uniká?
Když u parciální funkce vhodně rozšířím obor hodnot například vracením chyb, je z ní funkce totální, o tom diskuze není. Alternativně se dá totality dosáhnout zúžením definičního oboru, to ale většina typových systémů neumožňuje (třeba v Rustu smůla).
Kdyby chtěl někdo vidět kód (à la ML), tak funkce
def decBy1 : Nat -> Nat
-- Nat = {0, 1, 2, ...}
není totální, protože není definovaná pro nulu.
Totality se dá dosáhnout rozšířením návratové hodnoty na součtový typ:
def decBy1 : Nat -> Option Nat
| .succ n => .some n
| .zero => .none
Nebo zúžením def. oboru funkce:
def decBy1 (n : Nat) (_ : n≠0) : Nat :=
match n with
| .succ n => n
Memoizace pak bude fungovat pro obě funkce, v tom druhém případě se nebudou ukládat neplatné vstupy (a výsledek se nemusí vázat přes Kleisliho nesmysle).
To je narážka na to, že tomu rozumí jen 1% vývojářů (když vynecháme frontend, tak 2%). Ten zbytek svou ignoranci omlouvá tím, že to označuje za zbytečné nesmysly.
A poněkud vážněji, je nesmysl používat zbytečně složitou a abstraktní konstrukci (i když jinde se může náramně hodit, sama o sobě tedy nesmyslem není), když existuje jednodušší a čitelnější řešení, jehož podstatu každý zná ze ZŠ matematiky (i když plné pochopení toho, co se pak odehrává v rámci statické kontroly, překladu a za běhu zas tak jednoduché není, ale pořád o řád lepší než Kleisli).
Pěkný úvod. A v diskuzi taky pěkné příspěvky.
V kapitole 11 mi přijde takové zbytečné a matoucí "Můžeme použít i uživatelskou funkci s jedním parametrem (a jednou výstupní hodnotou – ovšem lze využít i implicitní hodnotu None)."
Výstupní hodnota bude vždy jen jedna, což je ostatně řečeno už v kapitole 7 a tady by se to dalo krásně ukázat.
U vstupních parametrů by mě mátlo, že jde použít i funkce, která používá více parametrů, akorát pojmenovaných.