Спасибо за статью. Не приходилось сталкиваться с F#, но, думаю, язык интересный.
> Преподавали ли вам функциональное программирование в вузе?
Да, в ГУАП'е (питерский универ аэрокосмического приборостроения) практическую часть ФП преподают на Common Lisp'e.
P.S.> прошел по ссылке — blogs.msdn.com/sos/, и понять не могу — что это за мой сайт? Вроде не было таких у меня. =) Вспомнил Вас, мы еще ВКонтакте один раз встречались на почве «Двойного тёзки» ;)
> То есть мы просто не хотим ловить деление на 0 в данном месте?
Не ловить в смысле в общих except'ax или сделать еще внутренний try-except в основмном try'e? Первый случай, как раз-таки нежелателен — получается мы общими except'ами пытаемся отловить все, соответственно, и в try'e у нас каша — сколько случаев мы в нем пытаемся защитить?
Если будет внутренний try-except во внешнем try — да, так, конечно, можно, тогда не должно быть внешнего else-блока (поскольку он тоже выполнится, т.к. во внешнем try'e все пройдет успешно).
Однако, если вынести успешный код в else и сделать там try-except — мы разграничиваем типы исключений.
В общем, повторю, это лишь удобный синтаксический сахар (ничто не запрещает написать этот успешный код со своими try-except'aми и после основного try'я)
> А если в else будет эксепшн, finally отработает?
> Вы пытаетесь делать слишком далеко идущие выводы :)
Я никогда не пытаюсь ;) (либо делаю, либо нет) я лишь размышляю, и делаю выводы (а далеко идущие или нет — это уже другой вопрос)
Да, все зависит лишь от задач конторы. Если у вас прикладные задачи, для которых уже написано тонна готовых решений — нет смысла писать на «ассемблере» интернет-магазин ;)
Если же контора занимается разработкой новых подходов, способных «осчастливить человечество», то — у них и цели здесь другие. Но они так же — работаю и получают зарплату (и, если оценивать справедливо, — получают больше прикладных программистов, поскольку и знаний применяют больше)
По поводу же велосипедов — тоже от уровня и требований зависит. К примеру, у меня в JS всегда свой фреймворк был написан (который легче и быстрее всех этих Prototype'ов.js, jQuery и т.д.).
А полностью подавлять инициативу и самообразование подчиненных — тоже не стоит (иначе — наймите лучше «кодеров» (программистов без собственной мысли), которые будут просто выполнять приказы, не двигаясь ни вправо, ни влево, а не программистов). Наоборот, компания должна сопутствовать образованию своих сотрудников.
Но, другое дело, когда эти «научные изыскания» совсем не связанны с работой — тогда да, здесь уже другой разговор (и вы имеете в виду именно этот случай; я понимаю).
только наоборот — S and A or B (если сравнивать с A if S else B), но сути это не меняет:
поскольку, действительно, эти два варианта работают по-разному: второй вариант трактует полученный ноль (S and A) как ложь и, соответственно, переходит к вычислению в B. Поэтому, если нужно учитывать 0, второй вариант уже не подходит. Поэтому, да — для общего случая лучше использовать вариант с A if S else B.
Для одиночных же проверок (a and b, c = x || 'defaultValue'), будут работать нормально.
a = {'x': 0}
try:
b = a['x']
#b = a['c']
#print(1 / b)
except KeyError as e:
print('KeyError')
except Exception as e:
print('Common Exception')
else:
print(1 / b)
finally:
print('finally')
В данном случае, программа упадет на else (поскольку будет деление на 0).
Если в try'e использовать только вторую строку (b = a['c']), то поймается KeyError и else не будет обработан.
Если в try'e использовать строки 1 и 3 (b = a['x'] и print(1 / b)), то поймается общий Exception, но! — не для того случая, который мы защищали (b = a['несуществующий_ключ']), а для «непредвиденного» — из «успешного» кода (в данном случае — тривиальный — деление на ноль, где ноль — предварительно полученный код из защищаемого кода). В данном случае, наш Exception поймал исключение, но предназначался этот Exception не для этого исключения. Т.е. мы будем считать, что мы что-то обработали, на самом же деле — код должен упасть в else, сигнализируя, что помимо кода, который мы защитили, есть еще один такой код и его так же надо обрабатывать, но, возможно, совершенно другими классами исключений (это основная суть).
Так что здесь вопрос, скорее, идеологический (ничто не мешает использовать этот «успешный» код и в самом try'e и вообще — за пределами try-блока), просто ввели такой синтаксический сахар в виде else.
P.S> в Ruby этот else-блок тоже присуствует в данном блоке.
> В университете — да.
> А если этим будет заниматься мой подчинённый в рабочее время — настучу по голове.
Да не только в университетах. Просто условно все задачи можно разделить на:
— задачи на инструментарий (написание языков, сред, фреймворков, виджетов и т.д.)
— прикладные задачи «на данные», с использованием уже готовых вещей из первого пункта.
Естественно, для выполнения первых нужны более глубокие знания — как в конкретной технологии, так и в общей, фундаментальной.
И не только в университетах, есть и такие «работы», на которых «подчиненные» пишут для потребителей усиленные абстракции. Далее некоторые потребители сами становятся созидателями (относительно нового уровня абстракции). Вы пользуетесь каким-нибудь фреймворками? Их разве в университетах написали? Нет, так же люди ходят на работу, так же им деньги за это платят.
> чето как то слишком сложно всё… слишком на низком уровне что ли.
Наоборот (если вы про автоматизированные средства построения сканеров и парсеров, типа javacc). Здесь вам не надо самому строить парсеры для LL / LR (1) — грамматик, здесь вы просто описываете формальную грамматику. Да, понадобится знать регулярные выражения, но, все же, это более эффективно и позволит избежать ошибок.
> Ох уж эти велосипедисты
Ну, есть и работы научные и академические, а не только прикладные-потребительские. Кто-то же за вас написал эти автоматизаторы :) Кому-то просто потреблять — мало, им нужно знать, как это работает. И «изобретение велосипеда» — это отличное подтверждение практикой изученной теории.
или jflex + cup (агалоги Си-шных flex'a и bison'a, который часто используются для этих целей) — так же строят сканер и парсер по заданным формальным грамматикам.
Блок else идеологически выделен, чтобы вынести в него код, который должен быть выполнен в случае успеха (в случае, если в try не произойдут исключений). Теоретически и практически, весь код блока else можно и записать в сам try, однако, в try желательно писать только то, что должно быть защищено. Остальной успешный код лучше вынести в else, поскольку в самом этом коде могут быть также исключения и, если они будут в try'e, то будут обрабатываться нашими except'aми, которые предназначены для обработки для конкретных типов исключений.
> else: # ловим все остальные исключения
> То есть смысл в том, чтобы отловить любое исключение, которое вылезет (считаем, что самого базового класса нет)
Ветвь else выполнится только в том случае, если блок try не сгенерировал исключение. Рациональное объяснение: лучше дополнительный код, который должен быть выполнен при успешном try'e помещать в else, это позволит избежать обработку исключений, сгенерированных кодом, который мы не собирались защищать.
Поддерживаю. Если официальный сайт технологии выкладывает официальный Programming Stryle Guide, то это правило хорошего тона и профессионализм — использовать эти рекомендации. При обучении, тоже лучше сразу приучать писать согласно этим правилам.
> Существует так же краткая форма записи (аналог тернарного оператора в Си): X = A if условие else B
Можно еще упомянуть про логические операторы or и and, которые так же можно использовать для данной конструкции:
x = условие and a or b
Отсюда можно выводить и следующие производные:
b = a or 10 # либо «а», либо дефолтное значение
# можно, например, и функцию вызвать по условию:
def c(): print('O.k')
b and c() # функция «с» вызовется только в том случае, если b — истинно
Все дело в особенности операторов or и and:
— or возвращает левый операнд, если он истинен, иначе — правый;
— and — вернет результат самого правого операнда, если все предыдущие были истины.
Когда условия одиночны, то такая запись может быть очень удобной (не нужно городить лишние if-else-блоки), если же условий несколько, то лучше воспользоваться if-else'ами, чтобы не портить читаемость кода.
P.S.> в JavaScript и Ruby аналогичная функциональность этих операндов.
Ну, в принципе, подобных статей уже навалом. Однако, всегда можно рассказать о некоторой теме предмета более развернуто и, если статья массовая, а не академическая, то — как можно доступней, чтобы не отпугивать людей, а наоборот привлекать, показывая, что JS — прост, но очень изящен. К тому же, если чувствуешь потребность написать, — конечно пиши.
Было уже много статей, но попробую рассказать кратко (но очень подробно) и простыми словами в этом комменте:
Замыкание — это функция, которая помнит все переменные той области видимости (и всех родительских областей видимости), в которой эта функция была порождена. Важным моментом здесь является тот факт, что объект, порождающий эту область видимости может уже не существовать на момент, когда замыкание будет использоваться.
Пример:
var a = 10; // глобальная переменная
function b() {
var c = 20; // локальная переменная
alert(a + c); // 30
}
В данном случае, функция «b» порождена в глобальной области видимости, и, соответственно, ей доступны как свои локальные переменные, так и глобальные. Однако, переменная «а» является свободной для функции «b» (т.е. переменная «а» не определена в области видимости функции «b»). Не буду вдаваться в подробности и механизм реализации этого эффекта в JS (variable object и scope chain), но главное здесь понять, что функция имеет доступ, помимо своих переменных, к переменным, которые были объявлены в той же области, что и сама функция, а также к переменным из областей стоящих выше в иерархии.
Пример:
var a = 10;
function b() {
var c = 20;
function d() {
var e = 30;
function f() {
var g = 40;
// доступна своя "g", а также
// "а", "c" и "е" из областей выше
alert(a + c + e + g);
}
f();
}
d();
}
b();
В принципе, это очевидно, и реализовано практически во всех языках.
При подходе, описанном выше, ни переменная «с», ни переменная «e», ни переменная «g» не будут существовать после вызова функции «b». Т.е. (локальные) переменные доступны только в области видимости, в которой они описаны, а также в областях, стоящих ниже в иерархии.
А теперь главная особенность, которая и позволит создать эффект замыкания. Функции в JS — это обычые (переменные) объекты, которые можно вернуть (после описания) из других функций:
function a(i) {
var z = 10;
function b() {
alert(i + z);
}
return b; // возвращаем функцию "b"
}
var c11 = a(1);
var c12 = a(2);
c11(); // 11
c12(); // 12
Т.к. функция «а» возвращает функцию, то переменные с11 и с12 — есть функции. Но! Не просто функции, а функции, помнящие всю цепь областей видимости, начиная от области видимости, в которой они порождены (в данном случае, это внутренняя функция «b»). Т.е. функции c12 и c12 будут помнить область видимости функция «а».
В данном случае, переменные «i» и «z» в отличии от случая, описанного выше (с «с», «е» и «g») живы после того, как функция «а» отработала. Это и есть замыкание (т.е. значения переменных «i» и «z» замкнулись в функции «b», которая затем вернулась из «а» и стала доступна для вызова через переменную c11 (аналогично и с12)).
Поэтому, замыкание — это не только, когда свободные переменные из внешнего scope'a доступны внутреннему, но и тогда, когда они могут быть доступны после того, как функция, порождающая внешний scope отработает.
> Преподавали ли вам функциональное программирование в вузе?
Да, в ГУАП'е (питерский универ аэрокосмического приборостроения) практическую часть ФП преподают на Common Lisp'e.
P.S.> прошел по ссылке — blogs.msdn.com/sos/, и понять не могу — что это за мой сайт? Вроде не было таких у меня. =) Вспомнил Вас, мы еще ВКонтакте один раз встречались на почве «Двойного тёзки» ;)
Правило Генерации: «Избегайте ручного ввода кода, при любом удобном случае пишите программы, которые бы писали программы.» (С) «Философия UNIX».
Для этого и создан этот синтаксический сахар. Без натяжек.
Не ловить в смысле в общих except'ax или сделать еще внутренний try-except в основмном try'e? Первый случай, как раз-таки нежелателен — получается мы общими except'ами пытаемся отловить все, соответственно, и в try'e у нас каша — сколько случаев мы в нем пытаемся защитить?
Если будет внутренний try-except во внешнем try — да, так, конечно, можно, тогда не должно быть внешнего else-блока (поскольку он тоже выполнится, т.к. во внешнем try'e все пройдет успешно).
Однако, если вынести успешный код в else и сделать там try-except — мы разграничиваем типы исключений.
В общем, повторю, это лишь удобный синтаксический сахар (ничто не запрещает написать этот успешный код со своими try-except'aми и после основного try'я)
> А если в else будет эксепшн, finally отработает?
Да.
Я никогда не пытаюсь ;) (либо делаю, либо нет) я лишь размышляю, и делаю выводы (а далеко идущие или нет — это уже другой вопрос)
Да, все зависит лишь от задач конторы. Если у вас прикладные задачи, для которых уже написано тонна готовых решений — нет смысла писать на «ассемблере» интернет-магазин ;)
Если же контора занимается разработкой новых подходов, способных «осчастливить человечество», то — у них и цели здесь другие. Но они так же — работаю и получают зарплату (и, если оценивать справедливо, — получают больше прикладных программистов, поскольку и знаний применяют больше)
По поводу же велосипедов — тоже от уровня и требований зависит. К примеру, у меня в JS всегда свой фреймворк был написан (который легче и быстрее всех этих Prototype'ов.js, jQuery и т.д.).
А полностью подавлять инициативу и самообразование подчиненных — тоже не стоит (иначе — наймите лучше «кодеров» (программистов без собственной мысли), которые будут просто выполнять приказы, не двигаясь ни вправо, ни влево, а не программистов). Наоборот, компания должна сопутствовать образованию своих сотрудников.
Но, другое дело, когда эти «научные изыскания» совсем не связанны с работой — тогда да, здесь уже другой разговор (и вы имеете в виду именно этот случай; я понимаю).
только наоборот — S and A or B (если сравнивать с A if S else B), но сути это не меняет:
поскольку, действительно, эти два варианта работают по-разному: второй вариант трактует полученный ноль (S and A) как ложь и, соответственно, переходит к вычислению в B. Поэтому, если нужно учитывать 0, второй вариант уже не подходит. Поэтому, да — для общего случая лучше использовать вариант с A if S else B.
Для одиночных же проверок (a and b, c = x || 'defaultValue'), будут работать нормально.
a = {'x': 0} try: b = a['x'] #b = a['c'] #print(1 / b) except KeyError as e: print('KeyError') except Exception as e: print('Common Exception') else: print(1 / b) finally: print('finally')В данном случае, программа упадет на else (поскольку будет деление на 0).
Если в try'e использовать только вторую строку (b = a['c']), то поймается KeyError и else не будет обработан.
Если в try'e использовать строки 1 и 3 (b = a['x'] и print(1 / b)), то поймается общий Exception, но! — не для того случая, который мы защищали (b = a['несуществующий_ключ']), а для «непредвиденного» — из «успешного» кода (в данном случае — тривиальный — деление на ноль, где ноль — предварительно полученный код из защищаемого кода). В данном случае, наш Exception поймал исключение, но предназначался этот Exception не для этого исключения. Т.е. мы будем считать, что мы что-то обработали, на самом же деле — код должен упасть в else, сигнализируя, что помимо кода, который мы защитили, есть еще один такой код и его так же надо обрабатывать, но, возможно, совершенно другими классами исключений (это основная суть).
Так что здесь вопрос, скорее, идеологический (ничто не мешает использовать этот «успешный» код и в самом try'e и вообще — за пределами try-блока), просто ввели такой синтаксический сахар в виде else.
P.S> в Ruby этот else-блок тоже присуствует в данном блоке.
> А если этим будет заниматься мой подчинённый в рабочее время — настучу по голове.
Да не только в университетах. Просто условно все задачи можно разделить на:
— задачи на инструментарий (написание языков, сред, фреймворков, виджетов и т.д.)
— прикладные задачи «на данные», с использованием уже готовых вещей из первого пункта.
Естественно, для выполнения первых нужны более глубокие знания — как в конкретной технологии, так и в общей, фундаментальной.
Все это вытекает из усиления абстракций, habrahabr.ru/blogs/webdev/45552/#comment_1152704 — здесь кратко говорил об этом.
И не только в университетах, есть и такие «работы», на которых «подчиненные» пишут для потребителей усиленные абстракции. Далее некоторые потребители сами становятся созидателями (относительно нового уровня абстракции). Вы пользуетесь каким-нибудь фреймворками? Их разве в университетах написали? Нет, так же люди ходят на работу, так же им деньги за это платят.
Наоборот (если вы про автоматизированные средства построения сканеров и парсеров, типа javacc). Здесь вам не надо самому строить парсеры для LL / LR (1) — грамматик, здесь вы просто описываете формальную грамматику. Да, понадобится знать регулярные выражения, но, все же, это более эффективно и позволит избежать ошибок.
> Ох уж эти велосипедисты
Ну, есть и работы научные и академические, а не только прикладные-потребительские. Кто-то же за вас написал эти автоматизаторы :) Кому-то просто потреблять — мало, им нужно знать, как это работает. И «изобретение велосипеда» — это отличное подтверждение практикой изученной теории.
Блок else идеологически выделен, чтобы вынести в него код, который должен быть выполнен в случае успеха (в случае, если в try не произойдут исключений). Теоретически и практически, весь код блока else можно и записать в сам try, однако, в try желательно писать только то, что должно быть защищено. Остальной успешный код лучше вынести в else, поскольку в самом этом коде могут быть также исключения и, если они будут в try'e, то будут обрабатываться нашими except'aми, которые предназначены для обработки для конкретных типов исключений.
> То есть смысл в том, чтобы отловить любое исключение, которое вылезет (считаем, что самого базового класса нет)
Ветвь else выполнится только в том случае, если блок try не сгенерировал исключение. Рациональное объяснение: лучше дополнительный код, который должен быть выполнен при успешном try'e помещать в else, это позволит избежать обработку исключений, сгенерированных кодом, который мы не собирались защищать.
P.S> в Ruby еще «интересней» — elsif =)
Можно еще упомянуть про логические операторы or и and, которые так же можно использовать для данной конструкции:
Отсюда можно выводить и следующие производные:
Все дело в особенности операторов or и and:
— or возвращает левый операнд, если он истинен, иначе — правый;
— and — вернет результат самого правого операнда, если все предыдущие были истины.
Когда условия одиночны, то такая запись может быть очень удобной (не нужно городить лишние if-else-блоки), если же условий несколько, то лучше воспользоваться if-else'ами, чтобы не портить читаемость кода.
P.S.> в JavaScript и Ruby аналогичная функциональность этих операндов.
Ну, в принципе, подобных статей уже навалом. Однако, всегда можно рассказать о некоторой теме предмета более развернуто и, если статья массовая, а не академическая, то — как можно доступней, чтобы не отпугивать людей, а наоборот привлекать, показывая, что JS — прост, но очень изящен. К тому же, если чувствуешь потребность написать, — конечно пиши.
Было уже много статей, но попробую рассказать кратко (но очень подробно) и простыми словами в этом комменте:
Замыкание — это функция, которая помнит все переменные той области видимости (и всех родительских областей видимости), в которой эта функция была порождена. Важным моментом здесь является тот факт, что объект, порождающий эту область видимости может уже не существовать на момент, когда замыкание будет использоваться.
Пример:
var a = 10; // глобальная переменная function b() { var c = 20; // локальная переменная alert(a + c); // 30 }В данном случае, функция «b» порождена в глобальной области видимости, и, соответственно, ей доступны как свои локальные переменные, так и глобальные. Однако, переменная «а» является свободной для функции «b» (т.е. переменная «а» не определена в области видимости функции «b»). Не буду вдаваться в подробности и механизм реализации этого эффекта в JS (variable object и scope chain), но главное здесь понять, что функция имеет доступ, помимо своих переменных, к переменным, которые были объявлены в той же области, что и сама функция, а также к переменным из областей стоящих выше в иерархии.
Пример:
var a = 10; function b() { var c = 20; function d() { var e = 30; function f() { var g = 40; // доступна своя "g", а также // "а", "c" и "е" из областей выше alert(a + c + e + g); } f(); } d(); } b();В принципе, это очевидно, и реализовано практически во всех языках.
При подходе, описанном выше, ни переменная «с», ни переменная «e», ни переменная «g» не будут существовать после вызова функции «b». Т.е. (локальные) переменные доступны только в области видимости, в которой они описаны, а также в областях, стоящих ниже в иерархии.
А теперь главная особенность, которая и позволит создать эффект замыкания. Функции в JS — это обычые (переменные) объекты, которые можно вернуть (после описания) из других функций:
function a(i) { var z = 10; function b() { alert(i + z); } return b; // возвращаем функцию "b" } var c11 = a(1); var c12 = a(2); c11(); // 11 c12(); // 12Т.к. функция «а» возвращает функцию, то переменные с11 и с12 — есть функции. Но! Не просто функции, а функции, помнящие всю цепь областей видимости, начиная от области видимости, в которой они порождены (в данном случае, это внутренняя функция «b»). Т.е. функции c12 и c12 будут помнить область видимости функция «а».
В данном случае, переменные «i» и «z» в отличии от случая, описанного выше (с «с», «е» и «g») живы после того, как функция «а» отработала. Это и есть замыкание (т.е. значения переменных «i» и «z» замкнулись в функции «b», которая затем вернулась из «а» и стала доступна для вызова через переменную c11 (аналогично и с12)).
Поэтому, замыкание — это не только, когда свободные переменные из внешнего scope'a доступны внутреннему, но и тогда, когда они могут быть доступны после того, как функция, порождающая внешний scope отработает.