Красиво избежать побочных эффектов
От: Roman Odaisky Украина  
Дата: 22.06.12 15:05
Оценка:
Задание: последовательно обработать объекты, каждый из которых принадлежит определенному юзеру, но своих обработать только первые столько-то (потому что сам юзер мог задействовать undo). В C-подобном синтаксисе получается элементарно:
int cOwnSeen = 0;
for(item : items)
{
    if(item.owner != myself || cOwnSeen++ < limit)
    {
        processItem(item);
    }
}

Но известно, что побочные эффекты — зло. Если потребуется заменить условие на чуть более сложное, есть опасность наделать ошибок. Как выглядит эквивалентный, но идеологически правильный код на вашем любимом языке?
До последнего не верил в пирамиду Лебедева.
Re: Красиво избежать побочных эффектов
От: FR  
Дата: 22.06.12 15:16
Оценка: -1
Здравствуйте, Roman Odaisky, Вы писали:


RO>Но известно, что побочные эффекты — зло. Если потребуется заменить условие на чуть более сложное, есть опасность наделать ошибок. Как выглядит эквивалентный, но идеологически правильный код на вашем любимом языке?


Псевдокод:

items |> take(limit) |> filter(item.owner != myself) |> processItem
Re[2]: Красиво избежать побочных эффектов
От: rameel https://github.com/rsdn/CodeJam
Дата: 22.06.12 15:24
Оценка: +1
Здравствуйте, FR, Вы писали:

FR>Псевдокод:


Скорее всего имелось это
items |> filter(item.owner != myself) |> processItem
items |> filter(item.owner == myself) |> take(limit) |> processItem


но здесь два прохода по коллекции, чего, возможно, захочется избежать
... << RSDN@Home 1.2.0 alpha 5 rev. 43>>
Re[3]: Красиво избежать побочных эффектов
От: FR  
Дата: 22.06.12 15:53
Оценка:
Здравствуйте, rameel, Вы писали:


R>но здесь два прохода по коллекции, чего, возможно, захочется избежать


Да невнимательно условия прочитал, такое на потоки плохо ложится.
Можно попробовать сложный фильтр возвращающий кортеж (element type, bool) где
второй элемент это флаг сигнализирующий что это родной элемент и в конце
take_while с условием. но сложновато получается по сравнению с императивным
циклом да.
Re: Красиво избежать побочных эффектов
От: elmal  
Дата: 22.06.12 17:05
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

RO>Но известно, что побочные эффекты — зло. Если потребуется заменить условие на чуть более сложное, есть опасность наделать ошибок. Как выглядит эквивалентный, но идеологически правильный код на вашем любимом языке?

ИМХО наиболее идеологически правильно просто не допускать таких задач и таких условий, в которых есть опасность наделать ошибок. Для данной задачи ИМХО лучше не сделаешь. Можно конечно сделать изврат с созданием однократно используемого предиката с внутренним состоянием, и фильтрацией с использованием этого предиката, но это извратище из извратов.
То есть псевдокод:
Predicate p = new AllUsersButOnlyLimitedMine(limit);
processByFilter(items, p, processFunction);

Может идеологически и более правильно, но тут настолько легко ошибиться, забыв перед вызовом создать новый предикат, отсчитывающий с нуля, что ну их нахрен, такие извраты, лучше по старинке .
Re[3]: Красиво избежать побочных эффектов
От: elmal  
Дата: 22.06.12 17:14
Оценка:
Здравствуйте, rameel, Вы писали:

R>но здесь два прохода по коллекции, чего, возможно, захочется избежать

Зачем избегать? Избегать, это уже начинаются оптимизации. А хочется же наиболее чистого кода с минимальной вероятностью ошибки. Соответственно наиболее идеологически правильно дробить сложное навороченное правило на 2 простых правила, и выполнять их по отдельности. Время то будет один черт линейное. А когда появится проблема, что хотелось бы пробегать по коллекции один раз, уже придется простотой и идеологической правильностью жертвовать. На двух стульях сразу не усидеть!
Re: Красиво избежать побочных эффектов
От: samius Япония http://sams-tricks.blogspot.com
Дата: 22.06.12 17:25
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

RO>Но известно, что побочные эффекты — зло. Если потребуется заменить условие на чуть более сложное, есть опасность наделать ошибок. Как выглядит эквивалентный, но идеологически правильный код на вашем любимом языке?

Побочные эффекты здесь не только в условии. processItem — тоже не без них, судя по всему.
Re[4]: Красиво избежать побочных эффектов
От: Roman Odaisky Украина  
Дата: 22.06.12 17:30
Оценка:
Здравствуйте, elmal, Вы писали:

R>>но здесь два прохода по коллекции, чего, возможно, захочется избежать

E>Зачем избегать? Избегать, это уже начинаются оптимизации. А хочется же наиболее чистого кода с минимальной вероятностью ошибки. Соответственно наиболее идеологически правильно дробить сложное навороченное правило на 2 простых правила, и выполнять их по отдельности. Время то будет один черт линейное. А когда появится проблема, что хотелось бы пробегать по коллекции один раз, уже придется простотой и идеологической правильностью жертвовать. На двух стульях сразу не усидеть!

Задание было — обойти всё последовательно. Там графические объекты один на другой накладываются, надо рисовать в правильном порядке.
До последнего не верил в пирамиду Лебедева.
Re: Красиво избежать побочных эффектов
От: Kogrom  
Дата: 22.06.12 17:35
Оценка:
Если я верно понял задание, то:

for i, item in enumerate(items):
    if(item.owner != myself or i < limit):
        processItem(item)


Аналогичный код можно написать и на C++. Возможно, на Питоне можно как-то ещё ужать, но задание выполнено: внешнего счётчика нет.
Re[2]: Красиво избежать побочных эффектов
От: Roman Odaisky Украина  
Дата: 22.06.12 17:37
Оценка:
Здравствуйте, samius, Вы писали:

RO>>Но известно, что побочные эффекты — зло. Если потребуется заменить условие на чуть более сложное, есть опасность наделать ошибок. Как выглядит эквивалентный, но идеологически правильный код на вашем любимом языке?

S>Побочные эффекты здесь не только в условии. processItem — тоже не без них, судя по всему.

processItem — это нечто вроде canvas.drawShape. Но никто не мешает переписать чисто функционально наподобие foldl getCanvasCombinedWithShape originalCanvas itemsSubset. Как только сформировать этот itemsSubset?
До последнего не верил в пирамиду Лебедева.
Re[2]: Красиво избежать побочных эффектов
От: Roman Odaisky Украина  
Дата: 22.06.12 17:38
Оценка:
Здравствуйте, Kogrom, Вы писали:

K>Если я верно понял задание, то:


К сожалению, нет: считает все элементы, а не только свои. Если limit = 2, а items = [чужое, чужое, свое, свое], не сработает.

K>for i, item in enumerate(items):

K> if(item.owner != myself or i < limit):
K> processItem(item)
До последнего не верил в пирамиду Лебедева.
Re[3]: Красиво избежать побочных эффектов
От: Kogrom  
Дата: 22.06.12 17:50
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:
RO>К сожалению, нет: считает все элементы, а не только свои. Если limit = 2, а items = [чужое, чужое, свое, свое], не сработает.


class Item:
    def __init__(self, owner):
        self.owner = owner

def processItem(item):
    print item.owner

items = [Item(a) for a in [1, 1, 0, 0]]
limit = 2
myself = 0

for i, item in enumerate(items):
    if(item.owner != myself or i < limit):
        processItem(item)


Вывод:
1
1

Что требовалось?

Тем, кому нравится запутывать читателя можно вместо нормального цикла написать:
[processItem(item) for i, item in enumerate(items) if (item.owner != myself or i < limit)]
Re[3]: Красиво избежать побочных эффектов
От: samius Япония http://sams-tricks.blogspot.com
Дата: 22.06.12 17:53
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

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


S>>Побочные эффекты здесь не только в условии. processItem — тоже не без них, судя по всему.


RO>processItem — это нечто вроде canvas.drawShape. Но никто не мешает переписать чисто функционально наподобие foldl getCanvasCombinedWithShape originalCanvas itemsSubset. Как только сформировать этот itemsSubset?


я считаю что правильно формировать с помощью свертки, где состоянием пойдет счетчик таких что (owner != myself), и последующей фильтрации.

Если последовательность/язык не ленивы, то накапливать нужные элементы прямо в передаваемом состоянии свертки.
Re[4]: Красиво избежать побочных эффектов
От: Roman Odaisky Украина  
Дата: 22.06.12 18:06
Оценка:
Здравствуйте, Kogrom, Вы писали:

K>Вывод:

K>1
K>1

K>Что требовалось?

1
1
0
0


limit должен распространяться только на свои элементы.
До последнего не верил в пирамиду Лебедева.
Re[5]: Красиво избежать побочных эффектов
От: Kogrom  
Дата: 22.06.12 18:14
Оценка: -1
Здравствуйте, Roman Odaisky, Вы писали:

RO>limit должен распространяться только на свои элементы.


В общем, я тупо взял Ваш алгоритм и засунул счётчик внутрь него. Ничего в работе алгоритма не изменилось. Если у меня работает неверно, то значит в Вашем исходном алгоритме ошибка

Кстати, вывод и прорисовка кругов — это побочный эффект.
Re[6]: Красиво избежать побочных эффектов
От: Roman Odaisky Украина  
Дата: 22.06.12 19:02
Оценка:
Здравствуйте, Kogrom, Вы писали:

RO>>limit должен распространяться только на свои элементы.

K>В общем, я тупо взял Ваш алгоритм и засунул счётчик внутрь него. Ничего в работе алгоритма не изменилось.

Ленивость оператора «||» приводит к увеличению счетчика только при обработке своих (owner == myself) элементов, в отличие от enumerate, который возвращает увеличенное значение для всех объектов.

K>Кстати, вывод и прорисовка кругов — это побочный эффект.


Если рассматривать обсуждаемый кусок как функцию (canvas, items) → canvas, то можно считать, что побочных эффектов нет, см. сообщение выше
Автор: Roman Odaisky
Дата: 22.06.12
. Или представь себе yield item вместо processItem(item), пусть самой отрисовкой занимается еще кто-нибудь.
До последнего не верил в пирамиду Лебедева.
Re[7]: Красиво избежать побочных эффектов
От: Kogrom  
Дата: 22.06.12 20:06
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

RO>Ленивость оператора «||» приводит к увеличению счетчика только при обработке своих (owner == myself) элементов, в отличие от enumerate, который возвращает увеличенное значение для всех объектов.


Да, я ошибался. На C++ можно использовать такое жульничество:


for(int i = 0, j = 0; i < items.size(); ++i)
{
    if((items[i].owner != myself) || (j++ < limit))
    {
        processItem(items[i]);
    }
}


Как сделать на Питоне я ещё подумаю.
Re[8]: Красиво избежать побочных эффектов
От: Kogrom  
Дата: 22.06.12 20:23
Оценка:
Здравствуйте, Kogrom, Вы писали:

K>Как сделать на Питоне я ещё подумаю.


Вроде бы оно:

import itertools

for f, item in zip([[itertools.count()]]*len(items), items):
    if(item.owner != myself or f[0].next() < limit):
        processItem(item)


Но красоты тут нет, конечно.
Re: Красиво избежать побочных эффектов
От: DarkGray Россия http://blog.metatech.ru/post/ogni-razrabotki.aspx
Дата: 22.06.12 21:21
Оценка:
items.Merge(.Where(.owner != myself),  .Where(.owner == myself).Take(limit))
.Foreach(item => processItem(item))

функция Merge переписывает переданные в нее выражения для последовательной обработки коллекции в один проход
например,
— (.Where(condition-A), .Where(condition-B)) заменяется на .Where(condition-A || condition-B)
— .Where(condition-A).Where(condition-B) заменяется на .Where(condition-A && condition-B)
— .Take(limit) при необходимости заменяется на Where(wave:i=0, {i < limit; i = i + 1})
и т.д.

пометка wave перед переменной означает, что состояние данной переменной разделяется между итерациями ("движется" вместе с курсором текущего элемента)


Foreach умеет комбинироваться вместе с Merge ("пожирая" его)

в итоге, всё это транслируется в
items.Foreach(wave:i = 0)
 item => 
  if (item.owner != myself || item.owner == myself && i < limit)
    processItem(item);
  if (item.owner != myself)
    i = i + 1;


зы
язык — один из внутренних экспериментов
Re: Красиво избежать побочных эффектов
От: Воронков Василий Россия  
Дата: 22.06.12 21:30
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

Сделать-то можно, но проблема в "красиво".

Вариант, который первым пришел в голову (пишу на своем языке, динамика, но скорее всего можно и типизировать):

let comb p max n e | p e = Some (comb p max n)
                   | n < max = Some (comb p max (n+1))
                   | else = None

let filter _ [] = []
    filter comb (x::xs) | res is Some comb = x :: filter comb xs
                        | else = []
                        where res = comb x

filter ((==0) << (%2) `comb` 10 <| 0) [1..100]


Первоначальное условие для наглядности немного поменял на — берем из списка четные числа или n нечетных.
Проверить код можно здесь: http://elalang.net/elac.aspx

Принцип простой — строим такой предикат, который возвращает результат (в виде варианта) и самого себя — состояние при этом протаскивается через стек. Однако использовать стандартный фильтр с таким предикатом нельзя — тот ждет, что предикат будет возвращать булевое. Поэтому приходится писать еще и свой фильтр, который декомпозирует предикат. Проще говоря на каждой итерации приходится создавать новый предикат.

Этого можно избежать, если написать другую функцию фильтр, в которую захардкодить все проверки и также тащить состояние через стек. Фактически ваш код просто переписывается в рекурсивную функцию — это достаточно просто. Но от ФП тут фактически уже вообще ничего не остается. В общем, не знаю, надо еще подумать
Re[4]: Красиво избежать побочных эффектов
От: samius Япония http://sams-tricks.blogspot.com
Дата: 22.06.12 21:36
Оценка:
Здравствуйте, samius, Вы писали:

RO>>processItem — это нечто вроде canvas.drawShape. Но никто не мешает переписать чисто функционально наподобие foldl getCanvasCombinedWithShape originalCanvas itemsSubset. Как только сформировать этот itemsSubset?


S>я считаю что правильно формировать с помощью свертки, где состоянием пойдет счетчик таких что (owner != myself), и последующей фильтрации.


S>Если последовательность/язык не ленивы, то накапливать нужные элементы прямо в передаваемом состоянии свертки.


Пришлось ввести функцию гибрид свертки и проекции, которая каждый элемент проецирует в кортеж где на первом месте элемент, а на втором — соответствующее состояние от свертки.
// F#
type MyType = { owner : obj }

// foldm : ('State -> 'a -> 'State) -> 'State -> seq<'a> -> seq<'a * 'State>
let rec foldm (f: 'State -> 'a -> 'State) s xs =
    if Seq.isEmpty xs
        then Seq.empty
        else let h = Seq.head xs
             seq { yield (h, f s h)
                   yield! foldm f s (Seq.skip 1 xs) }

let limit = 10

let filter self =
    foldm (fun i x -> if (x.owner = self) then 0 else i+1) 0
    >> Seq.takeWhile (fun (_, s) -> s < limit)
    >> Seq.map fst
Re[2]: Красиво избежать побочных эффектов
От: Воронков Василий Россия  
Дата: 22.06.12 21:43
Оценка:
Здравствуйте, Воронков Василий, Вы писали:

Ошибочка в filter, надо так:

let comb p max n e | p e = Some (comb p max n)
                   | n < max = Some (comb p max (n+1))
                   | else = None

let filter _ [] = []
    filter comb (x::xs) | res is Some comb = x :: filter comb xs
                        | else = filter comb xs
                        where res = comb x

filter ((==0) << (%2) `comb` 10 <| 0) [1..100]
Re: Красиво избежать побочных эффектов
От: Воронков Василий Россия  
Дата: 22.06.12 22:15
Оценка: 7 (1)
Здравствуйте, Roman Odaisky, Вы писали:

Нормальный вариант

open core

let comb p max n e | p e = Some (comb p max n)
                   | n < max = Some (comb p max (n+1))
                   | else = None

let apply f c e | c e is Some c' = f e $ c'
                | else = c

let _ = foldl (apply id) ((==0) << (%2) `comb` 5 <| 0) [1..100]


Через обычный foldl. В качестве "действия" задан просто id.
Re: Красиво избежать побочных эффектов
От: Mamut Швеция http://dmitriid.com
Дата: 22.06.12 23:08
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

RO>Задание: последовательно обработать объекты, каждый из которых принадлежит определенному юзеру, но своих обработать только первые столько-то (потому что сам юзер мог задействовать undo). В C-подобном синтаксисе получается элементарно:

RO>
RO>int cOwnSeen = 0;
RO>for(item : items)
RO>{
RO>    if(item.owner != myself || cOwnSeen++ < limit)
RO>    {
RO>        processItem(item);
RO>    }
RO>}
RO>

RO>Но известно, что побочные эффекты — зло. Если потребуется заменить условие на чуть более сложное, есть опасность наделать ошибок. Как выглядит эквивалентный, но идеологически правильный код на вашем любимом языке?

Один вариант — foldl, в котором в аккумулятор загоняем наш счетчик. Минус — придется пройти по всему списку, даже если счетчик перевалил
Process = fun(Element, Acc) ->
             if 
                not Element#item.own andalso Limit >= Acc ->
                   process_element(Element),
                   Acc
                true ->
                   Acc + 1
             end
          end,
lists:foldl(Process, 0, Items).


Второй вариант — передавать счетчик в вызываемую функцию. Предполагаю, что объекты — record'ы, из которых можно вытянуть информацию о «нашести»:

process_items(Items) ->
  Limit = get_limit(), % получаем откуда-то предел
  do_process_items(Items, Limit, 0).

%% Описание function clauses:
%% 1. элементы закончились. Завершаем
%% 2. количество наших элементов больше лимита. Завершаем
%% 3. следующий элемент в списке наш, мы его пропускаем, увеличиваем счетчик на один
%%    вызываем себя же рекурсивно с остатком списка
%% 4. айтем не наш, мы его процессим
      вызываем сами себя с остатком списка

do_process_items([], _, _) ->
  ok;
do_process_items(_, Limit, Acc) when Acc >= Limit ->
  ok;
do_process_items([#item{own=Own} | Rest], Limit, Acc) when Own =:= true ->
  do_process_items(Rest, Limit, Acc + 1);
do_process_items([#item{} = Element | Rest], Limit, Acc) ->
  do_something(Element),
  do_process_items(Rest, Limit, Acc);


dmitriid.comGitHubLinkedIn
Re: Красиво избежать побочных эффектов
От: Буравчик Россия  
Дата: 23.06.12 00:07
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

RO>Но известно, что побочные эффекты — зло. Если потребуется заменить условие на чуть более сложное, есть опасность наделать ошибок. Как выглядит эквивалентный, но идеологически правильный код на вашем любимом языке?


Haskell
algo2 myself limit processItem xs = evalStateT (mapM_ f xs) 0
  where f x = do cOwnSeen <- get
                 when (owner x /= myself || cOwnSeen < limit) (lift $ processItem x)
                 when (owner x == myself) $ modify (+1)
... << RSDN@Home (RF) 1.2.0 alpha 5 rev. 17>>
Best regards, Буравчик
Re[2]: Красиво избежать побочных эффектов
От: FR  
Дата: 23.06.12 03:27
Оценка: +1
Здравствуйте, Буравчик, Вы писали:

Хаскел лучший императивный язык?
Re: Красиво избежать побочных эффектов
От: FR  
Дата: 23.06.12 03:51
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

RO>Но известно, что побочные эффекты — зло. Если потребуется заменить условие на чуть более сложное, есть опасность наделать ошибок. Как выглядит эквивалентный, но идеологически правильный код на вашем любимом языке?


Убрать изменяемую переменную то несложно, вот пример на D

import std.stdio;

void processItem(Item)(const Item item)
{
writeln(item);
}

void process(Items, Item)(const Items items, const Item myself, const int limit, const int cOwnSeen = 0)
{
if(items.length){
    if(items[0] != myself || cOwnSeen < limit){
        processItem(items[0]);
        }
    process(items[1 .. $], myself, limit, cOwnSeen + 1);
    }    
}

void main()
{
process([1, 2, 1, 3, 1, 4, 1, 5, 6, 1], 1, 3);
}


то есть замена цикла рекурсией, но выразительность при этом не лучше цикла конечно.
Re[9]: Красиво избежать побочных эффектов
От: Kogrom  
Дата: 23.06.12 05:21
Оценка:
Здравствуйте, Kogrom, Вы писали:

K>Но красоты тут нет, конечно.


Посмотрел как "функциональщики" решают. Рекурсия безусловно рулит. Но я продолжу версию с циклом:


for counter, item in zip([[limit]]*len(items), items):
    if item.owner != myself or counter[0]:
        processItem(item)
    if item.owner == myself:
        counter[0] -= 1



тут избавился от itertools.
Re[3]: Красиво избежать побочных эффектов
От: Буравчик Россия  
Дата: 23.06.12 06:44
Оценка: +1
Здравствуйте, FR, Вы писали:

FR>Здравствуйте, Буравчик, Вы писали:


FR>Хаскел лучший императивный язык?


Задача императивная, потому и код такой.
Как минимум устранен хак, который может привести к ошибке (это я про использование порядка вычисления элементов в операции ИЛИ).
... << RSDN@Home (RF) 1.2.0 alpha 5 rev. 17>>
Best regards, Буравчик
Re[4]: Красиво избежать побочных эффектов
От: Воронков Василий Россия  
Дата: 23.06.12 08:22
Оценка: +2
Здравствуйте, Буравчик, Вы писали:

Б>Задача императивная, потому и код такой.

Б>Как минимум устранен хак, который может привести к ошибке (это я про использование порядка вычисления элементов в операции ИЛИ).

А почему вы считаете, что задача императивная? Если, к примеру, processItems представить просто как проекцию.
Неужели такую простую задачу декларативно уже нормально не решить?
Re[5]: Красиво избежать побочных эффектов
От: Буравчик Россия  
Дата: 23.06.12 09:28
Оценка:
Здравствуйте, Воронков Василий, Вы писали:

ВВ>А почему вы считаете, что задача императивная? Если, к примеру, processItems представить просто как проекцию.

ВВ>Неужели такую простую задачу декларативно уже нормально не решить?


Можно решить и начальную задачу. Вот, например, более "функциональное" решение.
Здесь каждый элемент заворачивается в Maybe — в Just, если его надо обрабатывать или Nothing, если нет.

algo3 myself limit processItem xs = mapM_ processItem $ catMaybes $ snd $ mapAccumL f 0 xs
  where f cOwnSeen x | owner x /= myself = (cOwnSeen, Just x)
                     | cOwnSeen < limit  = (cOwnSeen+1, Just x)
                     | otherwise         = (cOwnSeen, Nothing)


Функция mapAccumL :: (acc -> x -> (acc, y)) -> acc -> [x] -> (acc, [y]) есть в Data.List. Позволяет пробегать по массиву с аккумулятором.

Вопрос в том, какой вариант будет более понятен и что легче отлаживать.
... << RSDN@Home (RF) 1.2.0 alpha 5 rev. 17>>
Best regards, Буравчик
Re: Красиво избежать побочных эффектов
От: _Obelisk_ Россия http://www.ibm.com
Дата: 23.06.12 12:42
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

RO>Задание: последовательно обработать объекты, каждый из которых принадлежит определенному юзеру, но своих обработать только первые столько-то (потому что сам юзер мог задействовать undo). В C-подобном синтаксисе получается элементарно:


А почему бы не добавить что-то типа getObjects() к классу юзера ? Тогда от юзера получаем его объекты и их обрабатываем.



Душа обязана трудиться! (с) Н.Заболоцкий.
Re[6]: Красиво избежать побочных эффектов
От: Воронков Василий Россия  
Дата: 23.06.12 14:10
Оценка: +2
Здравствуйте, Буравчик, Вы писали:

Б>Вопрос в том, какой вариант будет более понятен и что легче отлаживать.


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

let map _ _ _ _ [] = []
    map p f max n (x::xs) = 
      if p x then
        x :: map p f max n xs
      else if n < max then
        x :: map p f max (n+1) xs
      else
        map p f max n xs


Но это бессмысленно, как, собственно, и любое подобное решение, включая монады. При таком подходе и первоначальное продемонстрированное решение уже достаточно неплохое. И на самом деле проблем-то с чистотой там нет — если рассматривать cOwnSeen как локальное имя, на которое существует единственная ссылка — это по сути же никакая не грязь и бояться тут нечего.
Re[2]: Красиво избежать побочных эффектов
От: Roman Odaisky Украина  
Дата: 23.06.12 14:50
Оценка:
Здравствуйте, _Obelisk_, Вы писали:

_O_>А почему бы не добавить что-то типа getObjects() к классу юзера ? Тогда от юзера получаем его объекты и их обрабатываем.


Порядок нарушится, надо чередовать объекты разных юзеров, в той последовательности, в которой они хранятся.
До последнего не верил в пирамиду Лебедева.
Re[7]: Красиво избежать побочных эффектов
От: Roman Odaisky Украина  
Дата: 23.06.12 15:28
Оценка:
Здравствуйте, Воронков Василий, Вы писали:

ВВ>При таком подходе и первоначальное продемонстрированное решение уже достаточно неплохое. И на самом деле проблем-то с чистотой там нет — если рассматривать cOwnSeen как локальное имя, на которое существует единственная ссылка — это по сути же никакая не грязь и бояться тут нечего.


Оно плохое не из-за наличия дополнительного элемента изменяемого состояния, а из-за ошибкоопасности в условии. Вдруг понадобится добавить дополнительное требование item.isGood(), куда его сунуть, чтобы не нарушить основанный на ленивости подсчет cOwnSeen? (Оно-то ясно, куда, но ошибиться слишком легко.)
До последнего не верил в пирамиду Лебедева.
Re[8]: Красиво избежать побочных эффектов
От: Воронков Василий Россия  
Дата: 23.06.12 17:01
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

RO>Оно плохое не из-за наличия дополнительного элемента изменяемого состояния, а из-за ошибкоопасности в условии. Вдруг понадобится добавить дополнительное требование item.isGood(), куда его сунуть, чтобы не нарушить основанный на ленивости подсчет cOwnSeen? (Оно-то ясно, куда, но ошибиться слишком легко.)


Ну вы можете просто явно расписать if — ну примерно, как у меня в предыдущем примере.
Re: Красиво избежать побочных эффектов
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 24.06.12 20:06
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

Если без извращений, то только с модифицируемым замыканием
var ownCnt = 0;
var toProcess =
  item
    .Select(i => new {Item = i, Cnt = i.Owner == myself ? ownCnt++ : 0})
    .Where(p => p.Cnt < limit)
    .Select(p => p.Item);
... << RSDN@Home 1.2.0 alpha 5 rev. 52 on Windows 7 6.1.7601.65536>>
AVK Blog
Re[2]: Красиво избежать побочных эффектов
От: Roman Odaisky Украина  
Дата: 24.06.12 21:50
Оценка:
Здравствуйте, AndrewVK, Вы писали:

AVK> .Select(i => new {Item = i, Cnt = i.Owner == myself ? ownCnt++ : 0})


А чем это лучше по сравнению с изначальным i.Owner != myself || ownCnt++ < limit? Ты тоже пользуешься ленивостью, возникает та же опасность при возможном изменении условия в будущем.
До последнего не верил в пирамиду Лебедева.
Re[3]: Красиво избежать побочных эффектов
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 25.06.12 14:33
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

RO>Ты тоже пользуешься ленивостью, возникает та же опасность при возможном изменении условия в будущем.


ИМХО ленивость тернарного оператора, очищенного от проверки лимита все таки более очевидна. Но можно и раскрыть:
item =>
{
  var cnt = 0;
  if (item.Owner == myself)
    cnt = ownCnt++;
  return new {item, cnt}
}


Все, никакой ленивости в условиях.
... << RSDN@Home 1.2.0 alpha 5 rev. 52 on Windows 7 6.1.7601.65536>>
AVK Blog
Re[2]: Красиво избежать побочных эффектов
От: Воронков Василий Россия  
Дата: 25.06.12 14:57
Оценка:
Здравствуйте, AndrewVK, Вы писали:

AVK>Если без извращений, то только с модифицируемым замыканием

AVK>
AVK>var ownCnt = 0;
AVK>var toProcess =
AVK>  item
AVK>    .Select(i => new {Item = i, Cnt = i.Owner == myself ? ownCnt++ : 0})
AVK>    .Where(p => p.Cnt < limit)
AVK>    .Select(p => p.Item);
AVK>


А чем этот вариант "идеологически более правильный", чем первоначальный код? Двойной проход + то же самое локальное состояние.
Re[3]: Красиво избежать побочных эффектов
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 25.06.12 15:01
Оценка:
Здравствуйте, Воронков Василий, Вы писали:

ВВ>А чем этот вариант "идеологически более правильный", чем первоначальный код?


Ничем.

ВВ> Двойной проход


Какой такой двойной проход?

ВВ> + то же самое локальное состояние.


А без состояния эта задачка нормально не решается. Его, конечно, можно в стек выпихнуть, но кайфа от этого немного.
... << RSDN@Home 1.2.0 alpha 5 rev. 52 on Windows 7 6.1.7601.65536>>
AVK Blog
Re[4]: Красиво избежать побочных эффектов
От: Воронков Василий Россия  
Дата: 25.06.12 15:11
Оценка:
Здравствуйте, AndrewVK, Вы писали:

ВВ>> Двойной проход

AVK>Какой такой двойной проход?

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

И, кстати, его необходимость как раз и выдает главную проблему этого кода. Автору топика поведение (||) кажется неочевидным. Ты вместо этого заменил все на non-strict последовательность (без мемоизации!), причем с побочным эффектом.

Как-то это... хуже, да простите меня за критику.
Re[5]: Красиво избежать побочных эффектов
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 25.06.12 15:14
Оценка:
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Ну предполагается, что получив отобранную таким образом коллекцию, мы с ней что-то сделаем, т.е. совершим проход по ней, так ведь?


Ну да. Ровно один проход.

ВВ> Первоначальный код делает все в один проход. В твоем варианте я *предполагаю*, что ты просто забыл дописать в конце ToList.


Ну ты крут. Ничего я не забывал, и никаких двойных проходов нет.

ВВ> Вот тебе и двойной проход. А ToList там, мягко говоря, нужен


Зачем? Следующей строкой будет foreach. А если ты собрался результат отдавать наружу, то надо просто вынести каунтер в специальное хранилище, чтобы интерференции не было.
... << RSDN@Home 1.2.0 alpha 5 rev. 52 on Windows 7 6.1.7601.65536>>
AVK Blog
Re[6]: Красиво избежать побочных эффектов
От: Воронков Василий Россия  
Дата: 25.06.12 15:19
Оценка:
Здравствуйте, AndrewVK, Вы писали:

ВВ>>Ну предполагается, что получив отобранную таким образом коллекцию, мы с ней что-то сделаем, т.е. совершим проход по ней, так ведь?

AVK>Ну да. Ровно один проход.
ВВ>> Первоначальный код делает все в один проход. В твоем варианте я *предполагаю*, что ты просто забыл дописать в конце ToList.
AVK>Ну ты крут. Ничего я не забывал, и никаких двойных проходов нет.

Нет, я просто пытаюсь представить, как должен выглядеть твой код *на самом деле*. Ведь не совсем так, как ты написал, правда? Теперь вот оказывается, что надо каунтер выносить в некое "специальное хранилище" (это, кстати, что такое?). При этом вообще-то проблемой первоначального варианта называлась неочевидность.

В общем можно полный код в студию. Со специальными хранилищами и всем прочим. У меня вот все же есть определенное подозрение, что твой вариант все же содержит некоторое количество изврата.
Re[7]: Красиво избежать побочных эффектов
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 25.06.12 15:27
Оценка:
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Нет, я просто пытаюсь представить, как должен выглядеть твой код *на самом деле*.


Проблемы с восприятием текстовой информации?

ВВ> Ведь не совсем так, как ты написал, правда?


Ненправда.

ВВ> Теперь вот оказывается, что надо каунтер выносить в некое "специальное хранилище"


Не надо. Это просто я пытаюсь додумывать фантазии в твоей голове.

ВВ>В общем можно полный код в студию.


Нет никакого полного кода. Идею я продемонстрировал, а флудить тебе придется без меня.
... << RSDN@Home 1.2.0 alpha 5 rev. 52 on Windows 7 6.1.7601.65536>>
AVK Blog
Re[8]: Красиво избежать побочных эффектов
От: Воронков Василий Россия  
Дата: 25.06.12 15:32
Оценка:
Здравствуйте, AndrewVK, Вы писали:

AVK>Нет никакого полного кода. Идею я продемонстрировал, а флудить тебе придется без меня.


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

Я лишь просто поинтересовался у тебя, в чем, собственно, заключалась продемонстрированная идея. А теперь, оказывается, у меня с восприятием проблемы. Ну пусть так
Re[10]: Красиво избежать побочных эффектов
От: Kogrom  
Дата: 25.06.12 17:48
Оценка:
Знаю, что говорю сам с собой, но из любви к искусству ещё вариант с циклом:

def gen(items, myself, limit):
    for item in items:
        if item.owner != myself:
            yield item
        elif limit:
            limit -= 1
            yield item
        else:
            return

for item in gen(items, myself, limit): processItem(item)


Внешнего счётчика нет, функция (processItem), которая теоретически может дать побочный эффект, не находится в дебрях кода. Поэтому её в тестах легко можно заменить на подделку.
Re[11]: Красиво избежать побочных эффектов
От: Roman Odaisky Украина  
Дата: 25.06.12 20:49
Оценка:
Здравствуйте, Kogrom, Вы писали:

K>Знаю, что говорю сам с собой, но из любви к искусству ещё вариант с циклом:


K>
def gen(items, myself, limit):
    for item in items:
        if item.owner != myself:
            yield item
        elif limit:
            limit -= 1
            yield item
        else:
            return

А вот и нет. Проблема осталась: при добавлении условий легко напутать и поставить их не туда, сбив подсчет limit. Ну и ветка else лишняя.

Интересно подобрать декларативный подход, не императивный.
До последнего не верил в пирамиду Лебедева.
Re[12]: Красиво избежать побочных эффектов
От: Kogrom  
Дата: 27.06.12 13:10
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

RO>А вот и нет. Проблема осталась: при добавлении условий легко напутать и поставить их не туда, сбив подсчет limit. Ну и ветка else лишняя.


else не лишний — без него цикл пройдёт по всему списку. Проблема с условиями сомнительна: почему её не будет при других подходах?

RO>Интересно подобрать декларативный подход, не императивный.


С этого надо было начинать.
Re[13]: Красиво избежать побочных эффектов
От: Воронков Василий Россия  
Дата: 27.06.12 13:47
Оценка:
Здравствуйте, Kogrom, Вы писали:

RO>>А вот и нет. Проблема осталась: при добавлении условий легко напутать и поставить их не туда, сбив подсчет limit. Ну и ветка else лишняя.

K>else не лишний — без него цикл пройдёт по всему списку. Проблема с условиями сомнительна: почему её не будет при других подходах?

Так он и должен идти по всему списку. Задача же обработать все элементы, у которых owner != myself (плюс доп. условие). У вас же получается, что как только лимит кончается и попадается первый элемент, у которого owner == myself, то вы выходите из функции. А это неправильно, т.к. в списке могут быть еще элементы, удовлетворяющие первому условию.

RO>>Интересно подобрать декларативный подход, не императивный.

K>С этого надо было начинать.

Я так понимаю, идея ТС в том, чтобы максимально упростить добавление новых условий. Здесь декларативный подход напрашивается сам собой — чтобы была возможность эти условия просто комбинировать. Вариант, который я привел, тоже, конечно, не идеален:

open core

let apply f c e | c e is Some c' = f e $ c'
                | else = c

let comb p max n e | p e = Some (comb p max n)
                   | n < max = Some (comb p max (n+1))
                   | else = None

let process act comb xs = foldl (apply act) comb xs

process processItem (comb even 5 0) [1..100]


Однако в нем по крайней мере можно ввести дополнительный комбинатор и делать так:

let orelse p a e | p e = a e | else = ()

process (processItem |> orelse (<25) |> orelse (>5) )  (comb even 5 0) [1..100]
Re[14]: Красиво избежать побочных эффектов
От: Kogrom  
Дата: 27.06.12 16:59
Оценка:
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Так он и должен идти по всему списку.


Да, действительно. Значит код будет ещё короче. Тогда условия не влияют друг на друга и нет проблемы в добавлении новых (elif limit легко можно поменять на if item.owner == myself and limit).

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

Ладно, извините, что я вторгся в клуб любителей ФП со своими циклами и за мою пониженную внимательность.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.