Здравствуйте, 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
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 xswhere res = comb x
filter ((==0) << (%2) `comb` 10 <| 0) [1..100]
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.
Здравствуйте, Roman Odaisky, Вы писали:
RO>Задание: последовательно обработать объекты, каждый из которых принадлежит определенному юзеру, но своих обработать только первые столько-то (потому что сам юзер мог задействовать undo). В C-подобном синтаксисе получается элементарно: 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);
Здравствуйте, 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)
Здравствуйте, Roman Odaisky, Вы писали:
RO>Но известно, что побочные эффекты — зло. Если потребуется заменить условие на чуть более сложное, есть опасность наделать ошибок. Как выглядит эквивалентный, но идеологически правильный код на вашем любимом языке?
Убрать изменяемую переменную то несложно, вот пример на D
Здравствуйте, 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
Здравствуйте, FR, Вы писали:
FR>Здравствуйте, Буравчик, Вы писали:
FR>Хаскел лучший императивный язык?
Задача императивная, потому и код такой.
Как минимум устранен хак, который может привести к ошибке (это я про использование порядка вычисления элементов в операции ИЛИ).
Здравствуйте, Буравчик, Вы писали:
Б>Задача императивная, потому и код такой. Б>Как минимум устранен хак, который может привести к ошибке (это я про использование порядка вычисления элементов в операции ИЛИ).
А почему вы считаете, что задача императивная? Если, к примеру, processItems представить просто как проекцию.
Неужели такую простую задачу декларативно уже нормально не решить?
Здравствуйте, Воронков Василий, Вы писали:
ВВ>А почему вы считаете, что задача императивная? Если, к примеру, 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. Позволяет пробегать по массиву с аккумулятором.
Вопрос в том, какой вариант будет более понятен и что легче отлаживать.
Здравствуйте, Roman Odaisky, Вы писали:
RO>Задание: последовательно обработать объекты, каждый из которых принадлежит определенному юзеру, но своих обработать только первые столько-то (потому что сам юзер мог задействовать undo). В C-подобном синтаксисе получается элементарно:
А почему бы не добавить что-то типа getObjects() к классу юзера ? Тогда от юзера получаем его объекты и их обрабатываем.
Здравствуйте, Буравчик, Вы писали:
Б>Вопрос в том, какой вариант будет более понятен и что легче отлаживать.
Смотря для кого понятен. Императивщику, возможно, будет понятнее всего изначальный вариант, просто переписанный в явно рекурсивную функцию-всемогутер. Типа такого:
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 как локальное имя, на которое существует единственная ссылка — это по сути же никакая не грязь и бояться тут нечего.
Здравствуйте, _Obelisk_, Вы писали:
_O_>А почему бы не добавить что-то типа getObjects() к классу юзера ? Тогда от юзера получаем его объекты и их обрабатываем.
Порядок нарушится, надо чередовать объекты разных юзеров, в той последовательности, в которой они хранятся.
Здравствуйте, Воронков Василий, Вы писали:
ВВ>При таком подходе и первоначальное продемонстрированное решение уже достаточно неплохое. И на самом деле проблем-то с чистотой там нет — если рассматривать cOwnSeen как локальное имя, на которое существует единственная ссылка — это по сути же никакая не грязь и бояться тут нечего.
Оно плохое не из-за наличия дополнительного элемента изменяемого состояния, а из-за ошибкоопасности в условии. Вдруг понадобится добавить дополнительное требование item.isGood(), куда его сунуть, чтобы не нарушить основанный на ленивости подсчет cOwnSeen? (Оно-то ясно, куда, но ошибиться слишком легко.)
Здравствуйте, Roman Odaisky, Вы писали:
RO>Оно плохое не из-за наличия дополнительного элемента изменяемого состояния, а из-за ошибкоопасности в условии. Вдруг понадобится добавить дополнительное требование item.isGood(), куда его сунуть, чтобы не нарушить основанный на ленивости подсчет cOwnSeen? (Оно-то ясно, куда, но ошибиться слишком легко.)
Ну вы можете просто явно расписать if — ну примерно, как у меня в предыдущем примере.
Здравствуйте, AndrewVK, Вы писали:
AVK> .Select(i => new {Item = i, Cnt = i.Owner == myself ? ownCnt++ : 0})
А чем это лучше по сравнению с изначальным i.Owner != myself || ownCnt++ < limit? Ты тоже пользуешься ленивостью, возникает та же опасность при возможном изменении условия в будущем.