[Scala] Использование yield в больших проектах
От: avpavlov  
Дата: 24.02.09 16:29
Оценка: -1
Казалось бы, какое отношение величины проекта имеет к yield?

Поясню: допустим есть некоторая функция, которая порождает список из 10 случайных чисел

def generate = for (i <- 1 to 10) yield Math.random


допустим я хочу узнать минимум

val sequence = generate
val min = (1000000d /: sequence)((m,v) => if (m > v) v else m)


а теперь максимум

val max= (0d /: sequence)((m,v) => if (m < v) v else m)


а теперь распечатать

println (sequence)
println (min)
println (max)


В итоге получаем что sequence генерировалась 3 раза, потому что реально это итератор, который передёргивается при каждом новом использовании. Понятно, что можно это поведение можно починить

val sequence = generate.toList


или даже так

def generate = (for (i <- 1 to 10) yield Math.random).toList


К сожалению, ни компилятор ни ИДЕ не подсказывают, что я коллекцию сгенерированную "йелдом" использую больше чем один раз. Соответственно, рано или поздно, когда над проектом работает больше чем один человек, в результате какого-либо рефакторинга или просто по недосмотру, эти грабли больно стукнут по лбу. Конечно, мой случай легко выловить на этапе юнит-тестирования. Но если коллекция более менее статичная, то это может долго работать правильно (с пониженной поизводительностью), а грохнется уже на продакшн.

Как тут быть — запретить использование "йелда"? Или договорится помечать методы, генерирующие что-нибудь с помощью "йелда" специальным префиксом/постфиксом, вроде yieldSomething()? Советы вроде "ты обязан думать головой, чтобы не прострелить себе ногу" на большом проекте не проканают
Re: [Scala] Использование yield в больших проектах
От: Tonal- Россия www.promsoft.ru
Дата: 24.02.09 17:25
Оценка:
Здравствуйте, avpavlov, Вы писали:
A>Как тут быть — запретить использование "йелда"? Или договорится помечать методы, генерирующие что-нибудь с помощью "йелда" специальным префиксом/постфиксом, вроде yieldSomething()? Советы вроде "ты обязан думать головой, чтобы не прострелить себе ногу" на большом проекте не проканают
Итератьр — это не список, так же как и список это не массив.
Так что просто работай с этим так же как с любым другим типом данных, имеющим свои ограничения и достоинства.
... << RSDN@Home 1.2.0 alpha 4 rev. 0>>
Re[2]: [Scala] Использование yield в больших проектах
От: avpavlov  
Дата: 24.02.09 19:17
Оценка:
Здравствуйте, Tonal-, Вы писали:

T>Здравствуйте, avpavlov, Вы писали:

A>>Как тут быть — запретить использование "йелда"? Или договорится помечать методы, генерирующие что-нибудь с помощью "йелда" специальным префиксом/постфиксом, вроде yieldSomething()? Советы вроде "ты обязан думать головой, чтобы не прострелить себе ногу" на большом проекте не проканают
T>Итератьр — это не список, так же как и список это не массив.
T>Так что просто работай с этим так же как с любым другим типом данных, имеющим свои ограничения и достоинства.

К сожалению (хотя в большинстве случаев к счастью) одна из фич Скала это вывод типов, т.е. код который я привёл выглядит абсолютно одинаково как для итератора, так и для списка. Т.е. программисту нужно или помнить (а его знание может устареть после чьего-нибудь рефакторинга) или постоянно проверять имплементацию метода.

Опять же в Яве итератор можно использовать только один раз, потом ему надо ресет делать, а Скала сама его передёргивает.

Вообщем, я считаю что неплохо было бы иметь ворнинг от комплилятора на повторное использование "йелдового" итератора, а текущая имплементация ведёт к трудноуловивым ошибкам практически на ровном месте.
Re[3]: [Scala] Использование yield в больших проектах
От: VoidEx  
Дата: 24.02.09 21:24
Оценка: +1
Здравствуйте, avpavlov, Вы писали:

А разница списка и массива в таком случае не смущает?
Re[4]: [Scala] Использование yield в больших проектах
От: kmmbvnr Россия http://kmmbvnr.livejournal.com
Дата: 25.02.09 05:17
Оценка:
Здравствуйте, VoidEx, Вы писали:


VE>А разница списка и массива в таком случае не смущает?


Есть небольшая разница, о которой говорит автор топика:
>>К сожалению, ни компилятор ни ИДЕ не подсказывают

>>на большом проекте не проканают


Что-то мне кажется, что все опять сводится к вопросу static vs dynamic typed languages
-- Главное про деструктор копирования не забыть --
Re[3]: [Scala] Использование yield в больших проектах
От: Tonal- Россия www.promsoft.ru
Дата: 25.02.09 05:39
Оценка: -1
Здравствуйте, avpavlov, Вы писали:
A>Опять же в Яве итератор можно использовать только один раз, потом ему надо ресет делать, а Скала сама его передёргивает.
Никто ничего не передёргивает. Твоя функция возвращает каждый раз новый итератор.
Т.е. это аналогично тому, как например функция открытия файла, которая возвращает новый объект открытого файла.
... << RSDN@Home 1.2.0 alpha 4 rev. 0>>
Re[5]: [Scala] Использование yield в больших проектах
От: VoidEx  
Дата: 25.02.09 05:48
Оценка:
Здравствуйте, kmmbvnr, Вы писали:

VE>>А разница списка и массива в таком случае не смущает?


K>Есть небольшая разница, о которой говорит автор топика:


Тут, как я понял, проблема, что случаи генерации каждый раз нового списка и случай одного списка выглядят одинаково. Т.е. важно именно различие одних данных и разных. В таком случае добро пожаловать в чистые языки, где это отличается на уровне типов.
Если же важна и разница массив/список, то добро пожаловать в языки без вывода типов, где придётся явно указать
Re[4]: [Scala] Использование yield в больших проектах
От: avpavlov  
Дата: 25.02.09 07:45
Оценка:
VE>А разница списка и массива в таком случае не смущает?

Проблемы этой разницы мне пока ещё не бросились в глаза

Если у тебя есть иллюстрация к этой проблеме — то милости просим
Re[4]: [Scala] Использование yield в больших проектах
От: avpavlov  
Дата: 25.02.09 07:53
Оценка:
T>Никто ничего не передёргивает. Твоя функция возвращает каждый раз новый итератор.
T>Т.е. это аналогично тому, как например функция открытия файла, которая возвращает новый объект открытого файла.

-1

В моём примере я присвоил результат вызова переменной, и простейший лог показывает, что функция вызывается ровно один раз, а итератор в начало передёргивает именно Скала где-то у себя внутри

Код

def generate = {println ("generate");for (i <- 1 to 3) yield Math.random}

val sequence = generate
val min = (1000000d /: sequence)((m,v) => if (m > v) v else m)
println (sequence)
println (min)


Вывод

generate
RangeM(0.9562395475577055, 0.1784016357101248, 0.45049842454280675)
0.6017675779423036

Re[2]: [Scala] Использование yield в больших проектах
От: avpavlov  
Дата: 25.02.09 08:11
Оценка:
T>Итератьр — это не список, так же как и список это не массив.
T>Так что просто работай с этим так же как с любым другим типом данных, имеющим свои ограничения и достоинства.

Это да, но если посмотреть на пример кода, то там нигде не упоминается итератор/список/массив, и компилятор Скала в свою очередь не подсказывает на возможное неправильное использование, и ИДЕ (ну с ними всё понятно, они ещё в зачаточном состоянии) тоже не подсказывают.

Получается, что программист должен постоянно перепроверять чего возвращает та или иная ф-ция, и, к сожалению, такая проверка действительна только в момент написания кода. Если метод generate поменяют потом, то всё спокойно перекомпилируется, но работать может неправильно.
Re[5]: [Scala] Использование yield в больших проектах
От: Tonal- Россия www.promsoft.ru
Дата: 25.02.09 09:42
Оценка:
Кажется разобрался.
В твоём коде for (i <- 1 to 10) yield Math.random это единая синтаксическая конструкция аналогичная генератору в Python.
Т.е. твой generate возвращает что-то типа замыкания над этой конструкцией.
Когда ты его пытаешся использовать — происходит приведение к итератору.
На Python код бы выгдядел примерно так:
def generate():
  print "generate"
  return lambda: (random.random for _ in range(1, 3))

sequence = generate()
print list(sequence())
print min(sequence())


Хотя, может я и ошибаюсь — и в интерфейсе итератора смешано 2 разных типа итераций — внешняя и внутреняя.
Внешняя это hasNext и next, а внутренняя это fold-ы и остальные map-ы
При этом интересно как они сочетаются.

Беглое рассматривание документации не ситуацию не проясняет.
Нужно лезть в исходники или спрашивать гуров.

П.С. Я давно и плотно работаю с итераторами на Python. там всё просто и прозрачно.
... << RSDN@Home 1.2.0 alpha 4 rev. 0>>
Re[6]: [Scala] Использование yield в больших проектах
От: avpavlov  
Дата: 25.02.09 11:41
Оценка:
T>Нужно лезть в исходники или спрашивать гуров.

Само собой в исходниках можно найти как это происходит , только меня больше беспокоит зачем это происходит, почему компилятор не предупреждает и как с этим бороться
Re[3]: [Scala] Использование yield в больших проектах
От: Аноним  
Дата: 27.02.09 09:29
Оценка:
Здравствуйте, avpavlov, Вы писали:

T>>Итератьр — это не список, так же как и список это не массив.

T>>Так что просто работай с этим так же как с любым другим типом данных, имеющим свои ограничения и достоинства.

A>Это да, но если посмотреть на пример кода, то там нигде не упоминается итератор/список/массив, и компилятор Скала в свою очередь не подсказывает на возможное неправильное использование, и ИДЕ (ну с ними всё понятно, они ещё в зачаточном состоянии) тоже не подсказывают.


A>Получается, что программист должен постоянно перепроверять чего возвращает та или иная ф-ция, и, к сожалению, такая проверка действительна только в момент написания кода. Если метод generate поменяют потом, то всё спокойно перекомпилируется, но работать может неправильно.


Ваш for.. yield будет транслироваться во что-то типа:

val range = (1 to 4);
def generate() = range.map(i => Math.random );
...
val sequence = generate();
println (sequence);
println (sequence);

В данном случае два обращения к sequence выдадут разные значения.
Но если первую строку заменить на:
val range = (1 to 4).toList;

то код прекрасно выдает одно значение.

Похоже, все логично. Если range : Seq, то оно вычисляется при каждом обращении, если List — фиксировано.

А чтобы в дальнейшем не было проблем, можно явно указывать тип операции:
def generate() : List[Double] = range.map(i => Math.random );
Re: [Scala] Использование yield в больших проектах
От: Аноним  
Дата: 27.02.09 10:17
Оценка:
Тебе в таком случае lazy evaluation нужен. Haskell бы это соптимизировал.
Re[4]: [Scala] Использование yield в больших проектах
От: avpavlov  
Дата: 27.02.09 10:30
Оценка:
А>Похоже, все логично. Если range : Seq, то оно вычисляется при каждом обращении, если List — фиксировано.

Ты так и не понял, о чём я говорю — я не про "непонятное" поведение (потому что оно мне понятно), а про то, что делать с этим в больших проектах, когда много людей и всё в голове уже удерживать не получается.

A>def generate() : List[Double] = range.map(i => Math.random );


Это не поможет. Потому что после рефакторинга кто-то поменяет на def generate:Seq[Double] = for (i <- 1 to 10) yield Math.random и если пользователи этого метода не писали что-то вроде

val sequence:List[Double] = generate


а писали

val sequence = generate


а именно этот способ пропагандируется как более короткий/прогрессивный/читаемый, то грабли кого-то больно стукнут по лбу (при полном попустительстве Скалы, которая могла бы давать ворнинг).
Re[5]: [Scala] Использование yield в больших проектах
От: VoidEx  
Дата: 27.02.09 11:01
Оценка:
Здравствуйте, avpavlov, Вы писали:

A>Ты так и не понял, о чём я говорю — я не про "непонятное" поведение (потому что оно мне понятно), а про то, что делать с этим в больших проектах, когда много людей и всё в голове уже удерживать не получается.


A>а именно этот способ пропагандируется как более короткий/прогрессивный/читаемый, то грабли кого-то больно стукнут по лбу (при полном попустительстве Скалы, которая могла бы давать ворнинг).


Это всё частный случай более общей проблемы — нельзя задекларировать, например, тип, который бы указывал, что функция вернёт корень аргумента (я не про принципиальную невозможность, а про отсутствие такого механизма). Если кто-то поменяет кишки sqrt на negate, тоже плохо будет, правда в здравом уме так не делают, потому и обычно не проблема.
Но в целом решение, по-моему, документации и код ревью.
Re[6]: [Scala] Использование yield в больших проектах
От: avpavlov  
Дата: 27.02.09 11:20
Оценка:
VE>Если кто-то поменяет кишки sqrt на negate, тоже плохо будет, правда в здравом уме так не делают, потому и обычно не проблема.

Ну это довольно вырожденный случай. Я указывал, что возможны изменения когда всё продолжает работать до опрделённого момента. Sqrt->negate будет выловлено юнит тестом. Мой пример со случайными числами тоже будет выловлен тест кэйсом. А вот например если коллекция из БД таким образом тягается (довольно бессмысленный пример, но ничего умнее придумать пока не могу), то юнит тест (и интеграционный тоже) не поймает — просто будет 2 раза в базу ходить. А на продакшн само собой между 2мя походами база рано или поздно поменяется

VE>Но в целом решение, по-моему, документации и код ревью.


Ну это да, разве что на ревью трудно обратить на такое внимание, обычно другим вещам значение придается.
Re[5]: [Scala] Использование yield в больших проектах
От: Аноним  
Дата: 27.02.09 14:12
Оценка:
Здравствуйте, avpavlov, Вы писали:

А>>Похоже, все логично. Если range : Seq, то оно вычисляется при каждом обращении, если List — фиксировано.


A>Ты так и не понял, о чём я говорю — я не про "непонятное" поведение (потому что оно мне понятно), а про то, что делать с этим в больших проектах, когда много людей и всё в голове уже удерживать не получается.


Да все я понял.
yield — это всего-лишь синтаксический сахар. Если пользоваться твоими рассуждениями, то в больших проектах надо запретить и .map и .foreach, потому что "проблема" идет оттуда.

A>>def generate() : List[Double] = range.map(i => Math.random );


A>Это не поможет. Потому что после рефакторинга кто-то поменяет на def generate:Seq[Double] = for (i <- 1 to 10) yield Math.random и если пользователи этого метода не писали что-то вроде


A>

A>val sequence:List[Double] = generate


A>а писали


A>

A>val sequence = generate


A>а именно этот способ пропагандируется как более короткий/прогрессивный/читаемый, то грабли кого-то больно стукнут по лбу (при полном попустительстве Скалы, которая могла бы давать ворнинг).


Рефакторинг с List[Double] на Seq[Double] — это не рефакторинг, а изменение интерфейса. Естественно, что логика работы поменяется.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.