Събудих се със сравнително добра идея как да помогна на толкова много объркани хора в избора им на кариера. Позанимавали са се малко с програмиране, учили са малко в университета, поработили са няколко месеца. Дали това е правилни житейски път? Как да разберем?
Ето класически проблем, който илюстрира много. Искаме да изпратим писмо до всички активни потребители, че системата няма да работи през уикенда. Едно „класическо“ решение на Ruby. Умишлено написано в не-ruby стил.
active = []
for user in User.find(:all)
active << user if user.active?
end
addresses = []
for user in active
addresses << user.email
end
send_notice(addresses)
end
Сега, погледнете следната модификация.
recepients = User.find(:all).select { |u| u.active? }.map(&:email)
send_notice(recepients)
end
За това мисля си като „функционална парадигма“. Ако виждате това за първи път, има няколко начина да реагирате. Първият е „А, това е хипер яко! Зарязвам тея статични езици и почвам да пиша на Ruby“. Това е добра реакция, но често е нереалистична и за това друга, хубава алтернатива е „Уф, това ме кефи, ама го няма в Java-та :(. Нищо, ще го погледна ако намеря време.“. Разбира се, може да кажете „А, не виждам никаква разлика между тия два кода – просто различен начин да напишеш едно и също“. Нормална, ако отскоро се занимавате с програмиране – скоро би трябвало да проумеете че не е „просто“ различен начин да напишеш нещо. Ако обаче сте нещо повече от junior developer, обяснете на шефа си, че не го заслужавате. А може да си кажете „А, това не само е същия код, но и няма никакъв смисъл от него. Много по-добре да и пиша с for и if, вместо да си натоварвам ума с тия истории“. Тогава най-искрено ви моля да си смените професията (например водопроводчик). Дълбоко ме е срам, когато хората поставят и мен и вас в една и съща категория – програмисти.
Ако този пост ви е издразнил, най-вероятно има защо. Помислете много, много хубаво къде е проблема ако не одобрявате горния код, смятате това за глупости и се ядосвате на написаното от мен.
Краят на тоз list comprehension може да се напише малко по-разбираемо за начинаещи като map{|u| u.email}.
Кога ще го водиш тоя курс по Ruby за да го науча най-после тоя синтаксис и да не мигам на парцали като пишеш код от сорта на accumulate(…filter(…map(…))) 🙂
Искренно се надявам да го направя зимния семестър на 2008. Още е под въпрос, нали. Хвърли един поглед и на втората (хронологично) статия в блога ми. Тя върти тая идея, макар и в малко безвкусен стил.
Ако има само добри програмисти няма да е интересно.
Браво бе, Николай. Чета „трябва да толерираме човешката глупост, щото иначе не е интересно“ 🙂
И все пак една еретична мисъл упорито пропълзява в ума ми и ме гложди отвътре: човекът който казва че двете парчета код са различни начини на изказ на едно и също е технически прав. И аха-аха да изкажа някоя друга дума в защита на добрия стар C-стил на програмиране се загледах пак и се учудих „Яя, наистина първия вариант е много по-тромав за четене“ … прекалено тромав всъщност, което ме навежда на още по-еретичната мисъл че с цел да убедиш читателите си в правата вяра си написал първия вариант излишно тромав. Ами ако го направим така: for user in Users: recipients.add(user.email) if user.IsActive забележи колко по-близо е реда в цикъла до смисъла на кода изразен на естествен език: add each user’s email to the recipients list if the user is active (нарочно пиша смисъла на английски за да се види колко минимална е разликата между естествената мисъл и буквалния прочит на кода). Я да видим буквалния прочит на второто парче код: recipients is the list of all users of which we have selected the active ones and on which we have mapped their emails. Като се замисля това май е най-добрия пример който съм виждал за разликата между императивния и декларативния стил на програмиране 🙂 Както се вижда последното звучи различно от естествената човешка мисъл и някак си по-формално описано. И проблема не е в това че не съм свикнал на този стил (напротив), проблема е в това че този стил те кара да ангажираш съзнанието си в една допълнителна част от секундата за да преведеш формалното описанието на естествен език. Според мен първия вариант, макар и малко по-бавен за писане е по-лек за четене, а не знам за вас но аз лично предпочитам да пиша повече но да мога след време да разбирам по-бързо написаното.
Само две грешки направи.
Не се чете „recipients is the list of all users of which we have selected the active ones and on which we have mapped their emails“. Чете се „избери активните и им вземи адресите“.
Втората е в „…аз лично предпочитам да пиша повече но да мога след време да разбирам по-бързо написаното“. Аз също. Просто не разбирам, защо смяташ, че функционалния стил ще ти е по-труден за разбиране след време.
Чувал съм същия аргумент за C и C++ – „защо да ползвам полиморфизъм, само да се чудя след време какво съм писал“. Звучи ли ти убедително? Или може би проблема е в това, което програмиста приема за трудно 🙂
Накратко: не съм съгласен че се чете така на първи прочит и смятам че функционалния (декларативен) стил изисква малко повече време за осмисляне от императивния (което не означава задължително че е по-труден, просто е различен), но тъй като аргументите ми много се раздуха ще ги спестя на блога ти и ще ги запазя за някоя кръчма 🙂 Накратко едната от идеите ми има нещо общо с факта че с функционален език можеш да изразиш нещо доста голямо като идея на доста малко пространство, което не е лошо нещо по принцип но има една граница зад която разчитането вече става по-бавно. Имаше един пост на много известен блог по този въпрос (мисля си за Paul Graham но не съм сигурен), може би ще се сетиш ако си го чел. Естествено разликата при такива случаи като примера за който говорим е достатъчно незначителна ако въобще я има за да си помислиш че съм или малоумен или дребнав, говоря по принцип:)
P.S.: http://weblog.raganwald.com/2007/07/abbreviation-accidental-complexity-and.html е статията която имах предвид (Google is godlike:) Препрочитайки я съм съгласен че императивният вариант е abbreviation, а декларативният – abstraction, и следвайки логиката на статията би трябвало да е по-добрият избор, но според мен в случая в цикъла няма accidental complexity която да трябва да бъде премахната така че абстракцията играе ролята на съкратена форма на запис. И все още смятам че императивната форма е по-близа до естественото мислене на човека, колкото и субективно да е това, и че в случаи в които не премахваш излишна сложност като я заменяш с декларативна няма нужда въобще да я заменяш.
Цикъла казва „направи нов списък, обходи тези и добави имената им в него“.
Ако това не е accidental complexity, не знам какво е 🙂
можеш и на 1 ред да вкараш цялата програма, но не бих казал, че синтаксиса ми е съвсем натурален. Това |u| u.active? плаче за курс във ФМИ и вика „хайде Стефане, организирай нещо“ 😉
btw ако го направиш for email in User.find(:all).select { |u| u.active? }.map(&:email) snd_notice(email) end
колко пъти ще се изпълни User.find(:all).select { |u| u.active? }.map(&:email)?
Функционалния код казва направи нов списък, от потребители намери всички, от тях избери тези за които е вярно че са активни и на тях им вземи мейлите … игра на думи, винаги мога да го докарам да звучи по-сложно от цикъла без да мамя. Затова и казах че разговора тук се обезсмисля и е по-подходящ за една бира. Просто си избрал достатъчно прост пример в който не е ясно дали си струва да минаваш едно ниво абстракция нагоре или да запазиш по-естествения изказ. А за по-сложни примери – не е като да не съм виждал оптимален път в граф на един ред, но честно казано се чудя дали това е за предпочитане пред няколкото декларативни функции, но тук се намесва вътрешното ми нежелание да се разделям толкова много с усещането за поне приблизителната ефективност на реализацията.
Хм… водя доста дълъг спор с мой колега (очевидно непрограмист според категориите посочени тук) относно читаемостта на подобен тип код. Той специално е твърдо убеден, че подобни „усложнения“ макар и приятни за ума са тежки и тромави за колективна работа. Примера му е следния: Ако аз пиша на Паша-кашавица, ти на С, а той на Джава трябва да намерим такъв метод на писане, че без той да учи руби, аз Джава и ти Паша-кашавица да можем да разберем какво сме написали.
Основанието – да, става тромаво, не ползваш някои „екстри“ на езика, но е читаемо за максимално голяма аудитория. А ти ако се замислиш ще видиш, че можеш да направиш и двата кода много по прегледни и читаеми без да нарушиш основната концепция на програмирането на Руби или Губи или който и да е език.
@blue: Не хората трябва да се учат да програмират, програмирането трябва да се направи така, че да го разбират всички? Не хората трябва да се учат нови неща, трябва да адаптират всичко което не знаят, към това, което вече знаят? Щом имаш чук, значи всичко е пирон? Успех в живота на хората с тази философия :/
@Obi_Wan:Просто си избрал достатъчно прост пример в който не е ясно дали си струва да минаваш едно ниво абстракция нагоре или да запазиш по-естествения изказ.
Странно че разсъждаваш така. Аз пък винаги се чудя дали си струва да счупя едно ниво на абстракция, за да се отдалеча от естествения изказ.
@dzver: Търпението му е майката 😉 (веднъж)
Хмм, хем чупиш ниво абстракция, хем се отдалечеваш от естествения изказ? Първата ситуация която ми минава наум е минаването от C на асемблер:) Е, естествено минаването от Python на C води до същото но загубата не е чак толкова голяма и ми хрумна чак след това:) Две сентенции ми хрумват като казваш че ти е странно че мисля така: „Every problem can be solved by adding an additional abstraction layer, except for the problem of having too much abstraction layers“ и „Keep it simple“. Не че конкретния пример е много сложен в който и да е от вариантите, но трябва да има съществена полза от по-голямата абстракция за да съм убеден че винаги си струва, просто в случая не виждам чак такава разлика. В курса по функционално и по Python съм срещал моменти в които безспорно функционалната парадигма беше много по-удобна и разбираема, само че не можеш да ме убедиш че е крайно необходимо да се използва и при такива случаи в които почни не се печели нищо откъм експресивност П.С.: Приятелката ми казва да спирам да пиша коментари на тоя пост за да имаш време да напишеш някоя друга нова статийка 🙂
Irina’s Weblog » Дочуто
Твърдата позиция — Аз, света и сметачите
Представи си за миг, че всички „програмисти“ прочетат този пост. И за още един миг си представи, наистина тези от тях, които ще предпочетат да пишат с for и if за да не си тормозят мозъка, наистина вземат че си сменят професията. И ето какво получаваме в третия миг … останали са само стойностни програмисти, умни , не мързеливи (в отношението си към запълването на сивите си клетки) … и цяла банда водопроводчици .. ама тях друг ги мисли вече. В четвыртия миг, си отваряш любимото развлекателно сайтче .. и Оо ИЗНЕНАДА … то не работи, защото истинския добър програмист, който е стоял зад него е твърде зает да запълва дупките получили се от липсата на настоящите водопроводчици, и се бави да го оправи. Колко жалко. Няма да се забавляваш в този миг. В петия миг, възхваляваш това че все пак имаш работа и не ти е толкова крайно наложително да се забавляваш с друго. … а какво ли те чака там ? Само истински колеги, разбира се. Хора които гордо наричаш програмисти. С тях можеш да прекараш часове в спорове, кое е най-добро за продукта ни. И така петия миг минава в приятен масаж на сивите клетки и написване на най-интересните неща… остават скучни дреболиики … и разбира се … само качествените програмисти. В шестия миг .. ти се иска да намериш кой да ги свърши тези дреболиики … за да може да продължиш да водиш чудните интелектуални спорове с колеките, истински програмисти. Да обаче всеки от тези истински програмисти си мисли същтото … кои да си намери да му свърши досадната работа.Уви, ще трябва сам да я свършиш. … И след много прекарани мигове в правене на досадни неща, които преди това е вършел един настоящ водопроводчик, ти се чудиш .. кога ли пак ще имаш време да си поговориш с онези така интересни и качествени хора, останалите програмисти….
in a nutshel: и най-богатото и развито кралство би рухнало в мига в който всички селяни и занаядчии изчезезнат ….
Аз имам един въпрос(за разведряване на нажежената обстановка): Ако някой програмист играе Pipes, то той какъв е: програмист или водопроводчик, или е някакъв хибрид програмист-водопроводчик?
Той не е нито едно. Той е отегчен. 🙂
Целта ми не беше да „нажежавам“ обстановката. Просто исках да покажа колко нужно зло са тази група хора – програмисти (бъдещи водопроводчици) за да може останалите (истински добрите) да имат време да се развихрят и творят със замах. Да, може да не са дотам кадърни и хората да се обижда от това че всички се наричат с едно име „програмисти“, но пък в повечето случаи са лесно дресируеми и изпълнителни, което ги кара да бъдат толкова полезни понякога.
Второто парче код използва Rails-изъм ot Active Support: .map(&:email), като „магията“ се състои във факта, че Rails дефинира to_proc конвертор за символи, ала:
def to_proc Proc.new { |o, *args| o.send(self, *args) } end
Въпреки coolness фактора, този идиом не прави кода по-четим, а напротив.
Аз съм на мнението на Сава, че map { |u| u.email } е по-добър за начинаещи, но ще добавя, че и за напреднали. Ruby е динамичен език и е достатъчно объркващо, че всичко може да се предефинира, така че на този Rails-изъм извън Rails разработка аз бих вдигнал вежди. „Якия“ код е четимия преди всичко.
Мога само да добавя, че редица ruby блогъри силно обичат Symbol.to_proc и Matz го е добавил в Ruby 1.9. Което идва да покаже, че доста хора не са съгласни с анализа ти за четимостта 🙂
Здравейте, наистина интересен спор. Въпросът ми е според вас има ли разлика в бързодеиствието и използваната памет между двата синтаксиса. Разбира се при голям брой елементи.
Codito ergo sum » Защо харесвам Java?