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>


А чем этот вариант "идеологически более правильный", чем первоначальный код? Двойной проход + то же самое локальное состояние.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.