juan_gandhi: (Default)
[personal profile] juan_gandhi
1, 2, 3, 4, 5, 6, 7, 8, 9, 10


В этой части мы ещё тоже не дойдём до апликативного стиля - но обсудим альтернативы и затронем монады.

Чтобы справиться с нетотальностью функций, с которыми мы имеет дело, есть несколько альтернатив.

Рассмотрим очевидные:
- null
- exceptions
- null object
- Option

1. null


В принципе, джава унаследовала null из си, и жила не тужила, пока не появились ещё дженерики, и стало как-то неудобно. До монад далеко, но уже неудобно.

То есть, вернуть null - да никаких проблем, кто только не возвращает null; и принять null в качестве параметра тоже милое дело, дескать, нетути этого вашего параметра, ну не шмогла я.

И люди вполне спокойно пишут функции, которые проверяют параметр на null и если что, возвращают null; чем не Option. Да тем, что всё это нужно явно прописывать.

  String countryName(User user) {
    if (user == null) return null;
    String phone = user.getPhone();
    if (phone == null) return null;
    String countryCode = PSTN.extractCountryCode(phone);
    if (countryCode == null) return null;
    Country country = Countries.findByCode(countryCode);
    if (country == null) return null;
    return country.getName();


Этот же код можно написать более прилично (10x [livejournal.com profile] sab123):
  String countryName(String userId) {
    User user; Phone phone; String cc; Country country;
    return userid != null
       && (user = db.findUser(userid)) != null
       && (phone = user.getPhone) != null
       && (cc = phone.getCountryCode) != null
       && (country = Countries.findByCode(cc)) != null ? country.getName() : null
  }


Ниоткуда не известно, что функция, получающая null, волшебным образом должна вернуть null, или что метод, вызванный на объекте null, вернёт null, а не, к примеру, 0, если хотим длину. Поэтому и пишут методы isEmpty(String s) { return s == null ? 0 : s.isEmpty(); }.

А забыли один раз - и наш null куда-то протёк внутрь, в какую-нибудь библиотечную функцию, та дальше передала - и потом разбирайся, в чём вообще дело. В реальности-то в джаве принято возвращать null на выходе, но не проверять на входе.

Но в принципе, если нам попался null вместо осмысленного значения, и нам надо с него что-то взять, то мы получим NPE; исключение - более-менее монада, в отличие от null.

2. exceptions


Если б мы были циниками, а не серьёзным джава-программистами, озабоченными производительностью компьютера, наносекундами и килобайтами, мы б могли писать так:

  String countryName(User user) {
    try {
      return Countries.findByCode(PSTN.extractCountryCode(user.getPhone()));
    } catch (NPE npe) {
      return null;
    }
  }


Согласитесь, выглядит не очень прилично - но гораздо более осмысленно. К тому же, вполне монадично - если NPE на нас не бросится, то вернём "данное", ну а если бросится, так только одно. Может быть, так и надо на джаве писать.
Тут только такая проблема, что мы не знаем, откуда прибежал этот NPE - может, вовсе и не из-за того, что у нас данных нету; для прототипа такой код годится, а в реальной программе вряд ли. Но для прототипа годится.

Хоть и выглядит дико, но у нас тут проступают черты вполне логичного приёмчика - вычисления делаются только в случае, если данные имеются, а исключительный случай отделяется от нормального там, где мы "вылазим из монады".

3. Null Object Pattern



Истинный джавщик не может без паттерна. Null Object горячо рекомендуют наши кумиры, Нил Гафтер и Джош Блок. Имеет смысл - если ничего не нашли в базе, возвращаем не страшный null, а его представителя в данном типе;
NullUser
, у которого есть
NullPhone
у которого есть пустой номер (""), по которому PSTN возвращает пустой код страны (или код пустой страны... хм, я знаю одну пустую страну, называется Атолл Диего Гарсиа - код страны есть, но нет ни одного опубликованного телефона; все на кораблях. Но мы введём пустую страну, NullCountry, и тогда Всё Будет Правильно.

Смысл этой операции такой, что теперь в коде мы можем предполагать тотально определённые функции и не отвлекаться на исключения.
Функция
  String countryName(User user) {
      return Countries.findByCode(PSTN.extractCountryCode(user.getPhone()));
  }

вернёт пустую строку.

В сущности что мы сделали - это каждый тип пополнили своим отдельным None/NAN/Not_a_Country/Not_a_Phone/Not_a_Pipe. Практически монада Option, но имплементированная для каждого типа отдельно, вручную; для различения ещё нужно добавить метод boolean isValid(), который будет возвращать true для нормальных значений и false для нашего None.

4. Option


Ну или можно сделать умственное усилие и добавить интерфейс Option<T>, у которого будет две имплементации, None и Some(T value).

Она монада потому, что имеются две стандартные операции:

единица монады, строящая по значению T x значение new Some<T>(x) - т.е. просто конструктор, и

монадное умножение, часто называемое операцией flatten - если есть Option<Option<T>>, то из него получается Option<T>.

Монады бывают разной степени сложности, Option из них самая простая. Её и плющить особенно легко:
None превращается в None,
Some(None) превращается в None,
Some(Some(x)) превращается в Some(x).

Сравните с плющеньем списка: List<List<T>> → List<T> - в данном случае мы пробегаем по списку списков и возвращаем список всего, что нам попалось по дороге:

  <T> List<T> flatten(List<List<T>> lol) {
    List<T> result = new // something
    for (List<T> list : lol) {
      for (T element : list) {
        result.add(element);
      }
    }
    return result;
  }


На Скале ту же операцию можно выразить проще:
  def flatten[T] (lol: List[List[T]]): List[T] = {
    val result = new scala.collection.mutable.// какой-нибудь список подходящего типа
    for (list    <- lol
         element <- list) result.add(element)
    result
  }


Ну или ещё проще: def flatten[T] (lol: List[List[T]]) = lol.flatten

С помощью Option с частичными функциями обращаться проще - вместо списка, вместо null, вместо исключения мы просто возвращаем всегда Option; а чтобы применить функцию к "содержимому Option", мы можем использовать цикл:

  for (User user : db.findUser(userid)) {
  for (Phone phone : user.getPhone()) {
  for (String countryCode: phone.getCountryCode()) {
  for (Country country: Countries.findByCode(countryCode)) {
    System.out.println("User " + user + " is from " + country);
  }}}}


Чтобы можно было писать цикл, нужно вот что: Option<T> implements Iterable<T> - ну это несложно. None всегда возвращает пустой Iterable, а Some возвращает одноэлементный Iterable.

На скале такая конструкция записывается в виде одного цикла:
  for (user    <- db.findUser(userid)
       phone   <- user.getPhone
       cc      <- phone.getCountryCode
       country <- Countries.findByCode(cc)
  ) println("User " + user + " is from " + country)


За сценой такой хитрый цикл разворачивается в последовательность сплющиваний.

Нет, на самом деле надо добавить ещё одну операцию, преобразование, fmap

Если у нас есть функция <A,B> B f(A a) {...}, то монада... ну, скажем, та же Option, определяет функцию
<A, B> Option<B> Option_f(Option<A> aOpt) { 
  for (a : aOpt) { return new Some<B>(f(a)); }
  return None;
}


На скале это выглядит практически так же:
def Option_f[A,B](aOpt: Option[A]) = {
  for (a <- aOpt) return Some(f(a))
  None
}


Или просто def Option_f[A,B](aOpt: Option[A]) = aOpt map f

Думаю, Вы можете написать аналогичную операцию для монады списка (List).

Монады помогают примирить реальный мир с его, э, исключениями и идеальный мир функций, который мы, теоретически, любим в компьютерах.

К сожалению, и с монадами нас подстерегают неприятности, о которых я расскажу в следующей части. Но не думайте, что без монад всё здорово и можно обойтись if-ами. Напротив; с if-ами вы просто не замечаете проблем в вашем коде - они парят как коршуны где-то над головами, а мы как мыши суетимся, ищем, где бы тут соптимизировать да все случаи учесть. "Книги про паттерны" не помогают.

Date: 2012-02-05 06:03 am (UTC)
From: [identity profile] ygam.livejournal.com
Null References: The Billion Dollar Mistake (http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare)

Date: 2012-02-05 06:24 am (UTC)
nine_k: A stream of colors expanding from brain (Default)
From: [personal profile] nine_k
Красота; ждём продолжения :)

Date: 2012-02-05 07:01 am (UTC)
From: [identity profile] lordakryl.livejournal.com
Спасибо, очень познавательно

Date: 2012-02-05 07:52 am (UTC)
From: [identity profile] quitequietquit.livejournal.com
А зачем примирять два мира, если они вполне могут существовать параллельно?
Я не силен в джаве, но разве нельзя сделать как-то так:
String countryName(User user) {
    Phone user_phone;
    ExactCode country_code;

    try {
      user_phone = user.getPhone();
    } catch (NPE e) {
        throw new UserHasNoPhoneException();
    }

    try {
        country_code = PSTN.exactCountryCode(user_phone);
    } catch (NPE e) {
        throw new UserHasSomeWeirdPhoneNumberException(user.ToString());
    }
 
    try {
        return Countries.findByCode(country_code);    
    } catch (NPE e) {
        throw new CountriesException("Country not found, though phone code is valid.");
    }
}


Ну, надо написать еще пачку исключений, да. Зато, когда все сломается, будет понятно где, почему, и что с этим делать. Даже необработанное исключение, если выскочит в готовой программе, принесет пользу, потому что пользователь будет звонить/писать в поддержку не "у меня все сломалось", а "страну не находит".

Date: 2012-02-05 08:17 am (UTC)
From: [identity profile] lomeo.livejournal.com
Кстати, верно. А то в посте сразу и с нетотальностью борются и с обработкой исключительных ситуаций. Надо как-то разделить.

Date: 2012-02-05 08:52 am (UTC)
From: [identity profile] mikkim08.livejournal.com
Это Вы уже делаете дополнительные предположения о постановке задачи.

А представьте, что наша задача сейчас такова: вывести на экран информацию о пользователе, которая содержит как обязательные, так и необязательные поля. Например, имя пользователя -- обязательное полe, a телефонный номер и код страны в нем -- необязательные. Если необязательное поле не заполнено, это не считается ошибкой, и мы просто игнорируем его при выводе.

Тогда Option[T] как раз подходит для таких необязательных полей.

А случай, когда не заполнено обязательное поле, т.е. случилась ошибка, можно рассмотреть отдельно в следующем посте.
Edited Date: 2012-02-05 08:53 am (UTC)

Date: 2012-02-05 09:32 am (UTC)
From: [identity profile] quitequietquit.livejournal.com
Исключение не обязательно означает ошибку. И нет ничего плохого, в том, чтобы осознанно игнорировать известные исключения на выводе. Но в методе, который может "сломаться" в трех местах, лучше все три места обрабатывать отдельно. Пользователь мог ввести номер полностью, но ошибиться в цифре кода, например. Это же не то же самое, что вообще ничего не вводил.

На самом деле, я наверное придираюсь к примеру. По сути-то все правильно. Option тут действительно позволяет избавиться от исключений.

Date: 2012-02-05 10:53 am (UTC)
From: [identity profile] mikkim08.livejournal.com
Я думаю, Вы правы в том, что в этом примере мы одинаково обрабатываем ситуации, которые на самом деле разные:

1) пользователя нет в базе данных
2) у него нет телефона
3) у телефона нет номера страны
4) страна по номеру не найдена.

Похоже, что Option вполне подходит для (2) и (3), а для (1) и (4) -- не вполне.

Date: 2012-02-05 05:39 pm (UTC)
From: [identity profile] ivan-gandhi.livejournal.com
Можно; и спасибо за тему, нужно будет учесть в следующей части.

Date: 2012-02-05 08:08 am (UTC)
From: [identity profile] lomeo.livejournal.com
> <Option<A>, Option<B>> Option<B> Option_f(Option<A> aOpt) {

Э-э-э... Так же:
<A, B> Option<B> Option_f(Option<A> aOpt) {

Date: 2012-02-05 05:41 pm (UTC)
From: [identity profile] ivan-gandhi.livejournal.com
Спасибо! Пофиксил.

Date: 2012-02-05 09:12 am (UTC)
From: [identity profile] mikkim08.livejournal.com
Спасибо. Я бы отдельно "разжевал", зачем нужны flatten и flatMap.

То есть, сначала мы ввели Optiоn[T] с очевидным методом isValid() безо всяких map, flatten, и flatMap. Теперь запишем предыдущий пример (с проверками на null), по-новому, то есть используя isValid. Уже получше ? А теперь добавляем в Optiоn методы map и flatten и переписываем пример снова. И наконец, заменяем map и flatten на flatMap.

(Тут, наверное, интересно будет показать, что map, flatten на flatMap определены и для коллекций, и это сходство неслучайно :) Потому что на самом деле это монадные операции).

Возвращаясь к нашему примеру, видим, что получилось то, что мы хотели. Ну а для компактности переписываем наш пример еще раз с использованием for-comprehension.

***

Кстати говоря, обучить использованию Option, в общем, несложно. Дальше сложнее. Надеяться, что программисты, обучившись Option, дальше сами смогут использовать, например, Either, нельзя. Нужно учить и об'яснять дальше под угрозой лишения квартальной премии. На следующем занятии можно усложнить задачу. Теперь, если обязательное поле отсутствует, нужно вывести сообщение об ошибке (например, "отсутствует имя пользователя").
Edited Date: 2012-02-05 09:58 am (UTC)

Date: 2012-02-05 05:42 pm (UTC)
From: [identity profile] ivan-gandhi.livejournal.com
Да; спасибо. Поработаю над этим.

Date: 2012-02-08 09:11 am (UTC)
From: [identity profile] smalgin.livejournal.com
Мораль: без угрозы лишения квартальной премии вышеупомянутые программисты ни хрена не выучат.

Вряд ли в UC Riverside есть курсы по этому делу...

Date: 2012-02-05 12:18 pm (UTC)
From: [identity profile] vit-r.livejournal.com
String countryName(User user) {
    
    assert(  user.isCorrect() ) ;

    try {
      return Countries.findByCode....
    ...

Date: 2012-02-05 07:51 pm (UTC)
From: [identity profile] gabaidulin.livejournal.com
Вот я в прошлом посте пытался примерно о таком же намекнуть.

Только есть еще аннотации @NotNull/@Nullable и Preconditions, которые сработают и без ключика -ea в production.

Но в целом, пост Влада понятен. И мне кажется он несколько о другом. То есть, очевидно, существуют ситуации, когда параметр не обязателен. В посте предлагается способен подумать над тем, как можно обрабатывать такие ситуации на ином уровне абстракции, более обобщенном и таким образом ввести фактически новую идиому в язык, если говорить принятыми терминами.

Date: 2012-02-05 07:54 pm (UTC)
From: [identity profile] vit-r.livejournal.com
Тут просто разница между гениальными кодописателями и программистами, которые знают, что постоянно делеют ошибки, и хотят уметь их быстро отлавливать.

Date: 2012-02-05 08:05 pm (UTC)
From: [identity profile] gabaidulin.livejournal.com
Я заметил, что если делать интерфейсы ясными(strong types and annotations) и проверять инварианты в ключевых, то ошибок типа NPE практически не бывает(они отлавдиваются еще на этапе unit tests). Причем заметил я это еще до java. Да и Страуструп помнится об этом тоже писал(про инварианты).

Но от проверок не уйти, просто они будут в другом месте.

Date: 2012-02-05 01:13 pm (UTC)
From: [identity profile] sassa-nf.livejournal.com
"без монад всё здорово и можно обойтись if-ами"

if-ами обойтись можно. Трудность в том, чтобы if-ы _везде_ расставить одинаково правильно. Монада позволяет такой if поставить один раз - в определении монады. Вот так и минус один источник ошибок.

Date: 2012-02-05 01:33 pm (UTC)
From: [identity profile] justy-tylor.livejournal.com
Для таких случаев с Maybe (или скальным Option) подходит контекст с ловлей _правильного_ исключения. Когда db.findUser(userid) и user.getPhone при отсутствии бросают DataPathNotFound, а над всем блоком висит какой-нибудь with Searching: ... (context manager в Python), который его ловит и продолжает код после блока. Пользуюсь. Но в Scala/Java это менее удобно, ибо ручной catch.

Вообще, мне монадный for в скале давно нравится. Особенно в плане с getPhone на getPhones переводить в случае чего. :)

Кстати, а где сейчас наиболее красивый скальный код пишут (не Scalaz, а что-то более земное)?

Date: 2012-02-05 05:43 pm (UTC)
From: [identity profile] ivan-gandhi.livejournal.com
Вот не знаю. Акка? В Лифт лучше не заглядывать, там ужас.

Date: 2012-06-25 10:17 am (UTC)
From: [identity profile] krlz.livejournal.com
А что там ужасного? Плохой пример скала кода?

Date: 2012-06-25 04:40 pm (UTC)
From: [identity profile] ivan-gandhi.livejournal.com
Ужасный пример скала кода.

Date: 2012-06-25 07:05 pm (UTC)
From: [identity profile] krlz.livejournal.com
А где можно посмотреть на хороший скала код? Play2? Akka?

Date: 2012-06-25 07:42 pm (UTC)
From: [identity profile] ivan-gandhi.livejournal.com
Ох не знаю даже, я помню, был восхищён sbt - но там тоже раз на раз не приходится.

Date: 2012-02-05 04:34 pm (UTC)

Date: 2012-02-05 08:01 pm (UTC)
From: [identity profile] gabaidulin.livejournal.com
А что если сделать язык, в котором предположим будет только один тип значений ,а именно список некоторого типа(тип может быть и рекурсивным). Ввести соответственно универсальные операции и трансформации списков и так далее.

Здесь есть очевидные плюсы, но есть очевидные и минусы. Например, ведь довольно очевидно(?), что хотя мы можем все привести к списку, но на самом-то деле, по-сути не все является списоком или является списоком лишь отчасти, искуственно, отражаем природу той или иной сущности. То есть придеться отойти от ООП, как наиболее(?) естественной формы выражения сущностей и полностью перестроить свое восприятие.

То есть разница между моделью предметной области и кодом будет огромной. В принципе и ООП с его паттернами, тоже далек, но в нем существует хотя бы четкая доменная модель. А как здесь быть?

Ну и вот я слышал пару раз про DDD, который как раз эту проблему пытается решить, но применить не довелось.

Date: 2012-02-06 07:14 am (UTC)
From: [identity profile] lomeo.livejournal.com
LISP? :)

Date: 2012-02-05 10:27 pm (UTC)
stas: (Default)
From: [personal profile] stas
Почти всё понятно, непонятно, откуда берётся цикл for. Имеется в виду, что все эти функции возрващают нечто Iterable, которое может содержать либо 0, либо 1 элемент? Или что-то другое?

Date: 2012-02-06 12:42 am (UTC)
From: [identity profile] ivan-gandhi.livejournal.com
Да, все эти классы имплементируют Iterable.

Date: 2012-02-06 12:53 am (UTC)
From: [identity profile] sab123.livejournal.com
Тогда почему не использовать &&? А, это небось какие-то джавные ограничения. А на C++ запросто можно определить приведение от жаждуемого типа к boolean и писать типа

if ( (user = db.findUser(userid))
&& (phone = user.getPhone)
&& (cc = phone.getCountryCode)
&& (country = Countries.findByCode(cc)) )
  printf("User %s is from %s", user.c_str(), country.c_str());


Вообще говоря, вся проблема выглядит надуманной. Уж если на то пошло, чем оно лучше, чем NullObject или даже простой null? Вот, пожалуйста:

if (userid != null
&& (user = db.findUser(userid)) != null
&& (phone = user.getPhone) != null
&& (cc = phone.getCountryCode) != null
&& (country = Countries.findByCode(cc)) != null)
  println("User " + user + " is from ", country);

Date: 2012-02-06 03:13 am (UTC)
From: [identity profile] ivan-gandhi.livejournal.com
Good point, добавил.
Edited Date: 2012-02-06 06:20 pm (UTC)

Date: 2012-02-06 07:19 am (UTC)
From: [identity profile] lomeo.livejournal.com
Предполагаю, что позже [livejournal.com profile] ivan_gandhi расскажет, что то же самое можно делать со списками, состояниями, продолжениями, парсингом... И у всего этого будет единый интерфейс. Ну и соответственно над этим интерфейсом общие вещи можно дорисовать.

Date: 2012-02-06 07:23 am (UTC)
From: [identity profile] ivan-gandhi.livejournal.com
Парсинг и продолжения я пропущу, наверное, пока; нужна отдельная тема, что-то вроде малой энциклопедии монад. State monad, однако, имеет смысл пристегнуть, да. Спасибо.

Date: 2012-02-06 06:10 pm (UTC)
stas: (Default)
From: [personal profile] stas
Да, у меня тот же вопрос. В конце концов, если этот for заменяет if, то почему не писать прямо if? T.e., возможно, там дальше какие-то конструкции из этого произрастут, где if недостаточно, но пока смысла в этом усложнении я не совсем понимаю - любой тип ведь сам себе Option, где None является null. В чём тут added value?

Date: 2012-02-06 06:19 pm (UTC)
From: [identity profile] ivan-gandhi.livejournal.com
Good point. Надо это прояснить будет; спасибо.

Date: 2012-02-06 06:55 pm (UTC)
From: [identity profile] sassa-nf.livejournal.com
if-а всегда достаточно. Но он оказывается по другую сторону от архитектора.

Date: 2012-02-06 07:29 pm (UTC)
stas: (Default)
From: [personal profile] stas
Извините, я не понял - что вы имели в виду?

Date: 2012-02-06 09:15 pm (UTC)
From: [identity profile] sassa-nf.livejournal.com
а я не совсем понял, что из двух вы не поняли.

ок, чтоб два раза не вставать:

1. всё переводится в if-ы, т.к. это атомарная единица логики. Так что, это достаточное условие. Что не доказано, так это необходимость.

2. Сравните с ситуацией, когда бы null заменял пустые списки. Пустой список как объект позволяет вызывать на нём методы (=задача архитектора задать кошерную реализацию оных). Пустой список как null требует реализовать эти методы везде (=задача пользователя, плюс проблема с reuse).

Date: 2012-02-06 09:51 pm (UTC)
stas: (Default)
From: [personal profile] stas
1. Это утверждение верно, но в данном контексте нерелевантно - поскольку речь идёт не о редукции к куче ifов "в конце концов" и не к построению машины Голдберга-Тьюринга, а к замену одного if на один for с той же функцией. Возникает вопрос, а зачем?

2. Но в данном случае мы не вызываем никаких методов до проверки. Однако даже в вашем случае, ну, допустим, позволяет. Вот мы имеем: List herd = foo.getHerd(); herd.first().moo(); Допустим, нам не нравится, что метод getHerd() возвращает null в случае, когда нужного стада нет, потому что first() не работает. Вот мы вернули пустой список, и first() заработал. Чем лучше стало? moo() то всё равно вызывать не на чем. Списки - это сервисные структуры, у них есть клиенты, и эти клиенты всё равно вынуждены будут как-то прописывать случай пустого списка. Какая в таком случае разница между null и пустым списком?

Date: 2012-02-06 11:28 pm (UTC)
From: [identity profile] sassa-nf.livejournal.com
1. этот пример с одним объектом None. Код с ним изоморфен коду с null. Но есть код и с объектом Infinity, NaN и ещё чего-нибудь, которые к null не свести. Но разница ещё и в (2)

2. списки - это сервисные структуры. Поэтому никому и в голову не придёт пустые списки реализовать как null.

Date: 2012-02-06 11:48 pm (UTC)
stas: (Default)
From: [personal profile] stas
1. Т.е. в данном случае разницы нет? Или есть, но для её демонстрации нужен другой пример? Какой?

2. Почему не придёт? Если это так очевидно - наверное, это должно быть очень легко обьяснить? Буквально в двух словах? Не в чём разница между [] и null, а почему в случае getHerd() надо возвращать именно [], а не null? Причём настолько надо, что никому и в голосу не придёт делать по-другому?

Date: 2012-02-07 08:48 am (UTC)
From: [identity profile] sassa-nf.livejournal.com
1. для меня разница маленькая. if заменить на for - разницы в строках кода нет.

2. потому что если не пользоваться Option, тип результата A|Nothing. Значит, везде, даже где используется просто A, должен быть паттерн матчинг. Если вместо типа A|Nothing используем список [A], то нужно проверять пустоту списка (тоже типа паттерн матчинг) _не везде_, а там, где используется Nothing. В остальных случаях пустой список себя ведёт по-другому, поэтому можно положиться на его поведение, на реализацию его итератора.

Разница маленькая. Примерно как вместо наследования и перегрузки операций пользоваться instanceof. Да, можно. В C так до сих пор делают.

Date: 2012-02-06 09:24 am (UTC)
From: [identity profile] fukanchik.livejournal.com
http://codemonkeyism.com/for-hack-with-option-monad-in-java/

Кстати, а как идиомой 'for/Iterable' заматчить None?
Edited Date: 2012-02-06 11:09 am (UTC)

Date: 2012-02-06 04:48 pm (UTC)
From: [identity profile] fukanchik.livejournal.com
Ок. Подожу следующих частей.

Date: 2012-02-10 01:41 am (UTC)
From: [identity profile] astoon.livejournal.com
> Но не думайте, что без монад всё здорово и можно обойтись if-ами. Напротив; с if-ами вы просто не замечаете проблем в вашем коде - они парят как коршуны где-то над головами, а мы как мыши суетимся, ищем

Да!

Спасибо.

Ждем продолжения.

Profile

juan_gandhi: (Default)
Juan-Carlos Gandhi

July 2025

S M T W T F S
  12345
6789 1011 12
131415 1617 1819
20212223242526
2728293031  

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jul. 20th, 2025 09:11 pm
Powered by Dreamwidth Studios