Кому нужна лишняя связность там, где её может не быть?
Haskell сам вместо map f. map g делает map (f. g)
Можно вообще задавать свои правила оптимизации, язык-то чистый и есть гарантии отсутствия побочных эффектов :)
Итераторы нужны не для усложнения, а для разделения алгоритмов.
Куда как проще и понятнее написать:
filterData = filter somePredicate
translateData = map someOp
И затем
filterData. translateData $ data
и всё это выльется в один цикл.
Нежели
for (… )
{
if (somePredicate(*b))
result.push_back(someOp(*b))
}
Так как в первом случае код разнесён в разные места (а что если эти обработки пишут разные люди?), а во втором — всё намешано в кучу.
Мы, конечно, во втором случае тоже можем разнести в разные места, но тогда в энергичном языке у нас получится 2 цикла, а не один. А если обработок будет больше, чем 2? То и циклов будет сотня. Вот и придумывают всякие yield'ы, и отнюдь не ради забавы.
Было бы интересно ещё про Fusion (как несколько map/filter/… выливаются в один цикл). Это, так сказать, обратный случай, где в обычных языках мы платим, а в ленивом — нет, за счёт высокой абстракции.
Я лично пишу новую монаду тогда, когда она может значительно сократить код, как, например, тут
В статье, правда, не видно сравнения того, что было (а были вызовы с передачей лямбды, внутри которой были такие же вызовы с передачей новых лямбд, выглядело это как большая развесистая лестница) с тем, что стало (фактически простой линейный код).
По поводу второго варианта. У вас получается тип IO (IO ())
Т.е. test2 ничего не печатает, он возвращает монадное действие, которое уже печатает. Чтобы реально напечатать надо сделать так: do
action <- test2
action
Не совсем понял, что значит «на стандартных функциях»? Klesli же стандартные.
Если интересует, как они внутри устроены, то код здесь. Внутри там есть bind, конечно.
Стрелки не для того, чтоб не использовать bind, про них имеет смысл почитать отдельно :)
Можно ещё упомянуть, что a -- [IO] -> b
это стрелки Клейсли, и используя их, можно писать вполне себе: test :: String -> IO ()
test = runKleisli (Klesli purStrLn . arr (map toUpper) . Kleisli readFile)
Напрягает, правда, необходимость оборачивать функцию.
Таким образом, конечно, можно.
Кстати, если data-accessor сделать инстансом Control.Category, то можно будет писать (tail.head.second ^= 12) [('x', 125), ('a', 11)]
:)
Haskell сам вместо map f. map g делает map (f. g)
Можно вообще задавать свои правила оптимизации, язык-то чистый и есть гарантии отсутствия побочных эффектов :)
Тогда как в ленивом будет всего один проход.
Куда как проще и понятнее написать:
filterData = filter somePredicate
translateData = map someOp
И затем
filterData. translateData $ data
и всё это выльется в один цикл.
Нежели
for (… )
{
if (somePredicate(*b))
result.push_back(someOp(*b))
}
Так как в первом случае код разнесён в разные места (а что если эти обработки пишут разные люди?), а во втором — всё намешано в кучу.
Мы, конечно, во втором случае тоже можем разнести в разные места, но тогда в энергичном языке у нас получится 2 цикла, а не один. А если обработок будет больше, чем 2? То и циклов будет сотня. Вот и придумывают всякие yield'ы, и отнюдь не ради забавы.
head $ sort lst
Выдаст минимальный элемент.
Сравните, во что это выльется ленивому и энергичному языку?
Ссылочная прозрачность же, а не мемоизация?
Было бы интересно ещё про Fusion (как несколько map/filter/… выливаются в один цикл). Это, так сказать, обратный случай, где в обычных языках мы платим, а в ленивом — нет, за счёт высокой абстракции.
В статье, правда, не видно сравнения того, что было (а были вызовы с передачей лямбды, внутри которой были такие же вызовы с передачей новых лямбд, выглядело это как большая развесистая лестница) с тем, что стало (фактически простой линейный код).
IO (IO ())Т.е.
test2ничего не печатает, он возвращает монадное действие, которое уже печатает. Чтобы реально напечатать надо сделать так:action <- test2
action
Или проще:
Если интересует, как они внутри устроены, то код здесь. Внутри там есть bind, конечно.
Стрелки не для того, чтоб не использовать bind, про них имеет смысл почитать отдельно :)
a -- [IO] -> bэто стрелки Клейсли, и используя их, можно писать вполне себе:
test :: String -> IO ()test = runKleisli (Klesli purStrLn . arr (map toUpper) . Kleisli readFile)
Напрягает, правда, необходимость оборачивать функцию.
Кстати, если data-accessor сделать инстансом
Control.Category, то можно будет писать(tail.head.second ^= 12) [('x', 125), ('a', 11)]:)
fmap : (a -> b) -> f a -> f bобязан работать на любых типах и морфизмах.
В
Control.Categoryесть классCategory, так что должно было быть так:fmap : g a b -> h (f a) (f b)