- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
fibs = 0:1:zipWith (+) fibs (tail fibs)
fib = (fibs !!)
main = let
a = [fib 250000..]
b = a!!0
c = 1
in b `seq` print c
Нашли или выдавили из себя код, который нельзя назвать нормальным, на который без улыбки не взглянешь? Не торопитесь его удалять или рефакторить, — запостите его на говнокод.ру, посмеёмся вместе!
−95
fibs = 0:1:zipWith (+) fibs (tail fibs)
fib = (fibs !!)
main = let
a = [fib 250000..]
b = a!!0
c = 1
in b `seq` print c
Haskell не может в not used expression elimination. Не используемые константы a и b не убрал из вычисления.
В результате видим пустую трату времени time: 13.15s :
http://ideone.com/41Q8D
И это то ленивом языке, не смотря на то, что эти вычисления не нужны. Можно писать в багтреккер.
P.S.: Когда уже хаскель в подсветку говнокода добавят?
http://ideone.com/nkoXr
>time: 0.01s
C# LINQ Enumerable.Count станет вычислять каждый элемент списка, так что там подсчет кол-ва на долго затянется.
P.S. HaskellGovno сам заставил хаскель не лениться и вычислить b, и сам обосрал его за то, что он его вычисляет...
http://ideone.com/pV04A
Ну а так баг пропадает:
http://ideone.com/h53JK
Правильно. Потому что WHNF - раскрытие до первого попавшегося конструктора. В данном случае этим конструктором будет конструктор списка. Ни голова списка, ни его хвост вычисляться не будут.
Хаскель к ним, как мы видим, не относится. Никакого выкидывания не используемых переменных во время компиляции. Глупо. Вся надежда только на лень. То есть не используемые выражения выкидываются только в рантайме. Глупо. В результате лишние тормоза и пустое разрастание экзешника.
Толсто же...
seq это специальный оператор, семантика которого заставляет хаскель вычислить левый аргумент до первого конструктора. Зачем было писать seq, если этот эффект не требуется?
Люди тоже ошибаются, а Хаскель проектировался, как думающий за программиста и всё проверяющий язык.
Между прочем, хаскель, если наконец прочитаете документацию, имеет право опустить рекомендацию seq, если она будет не оптимальна. Именно поэтому появилось требование pseq. Только pseq гарантированно оптимизатор Хаскеля опустить не имеет права. seq лишь рекомендация, в отличии от требования pseq.
seq :: a -> b -> bSource
Evaluates its first argument to head normal form, and then returns its second argument as the result.
http://hackage.haskell.org/packages/archive/base/latest/doc/html/Prelude.html#v:seq
http://www.haskell.org/onlinereport/haskell2010/haskellch6.html#x13-1260006.2
Пруф про то, что seq рекомендация, в студию.
>The difference is subtle. The semantics of seq and pseq are identical; however, GHC can see that seq is strict in both its arguments and hence could choose to evaluate them in either order, whereas pseq is only strict in its first argument as far as the strictness analyser is concerned. The point is that pseq is useful for controlling evaluation order, which is what you want for adding parallelism to a program. seq, on the other hand, is not useful for controlling evaluation order. The documentation in 6.6 is also incorrect on this point.
seq строго по левому аргументу, и не специфицировано по правому. Это не противоречит документации, в которой про правую часть умалчивается, а говорится только о том, что левая часть вычислится до первого конструктора, перед тем, как seq вернет результат.
pseq строго только в первом аргументе и возвращает правую часть невычисленной.
Но как это связано с тем, что вы написали?
> Между прочем, хаскель, если наконец прочитаете документацию, имеет право опустить рекомендацию seq, если она будет не оптимальна. Именно поэтому появилось требование pseq. Только pseq гарантированно оптимизатор Хаскеля опустить не имеет права. seq лишь рекомендация, в отличии от требования pseq.
Результат snd res дальше не используется. Но если во втором элементе тупла будет undefined или какое-то длинное вычисление, то оно вылетит\выполнится именно при этом вызове, а не когда кто-то через пару часов полезет смотреть res.
А ваш код аналогичен случаю, если уебать молотком по пальцам, и говорить, а чего это он бьется, это явно бага...
let (a,b) = f
В результате вычисляется неиспользуемое ниже выражение справа от =. Вот тебе и ленивый язык... Зато напридумывали костылей ~, исправляющих это:
let ~(a,b) = f
let не вычисляет выражений. И = не вычисляет. До тех пор, пока выражение описанное в let не будет задействовано в каком-то вычислении, хаскель даже не почешется его вычислять.
P.S. let (a,b) = undefined in 2
> Не используемые константы a и b не убрал из вычисления.
bormand уже написал, но ты реально толст. seq - и есть устранение ленивости, просто использовать нужно по необходимости.
Нет. Имеет смысл вычислять левую часть seq только в случае, если она используется в правой части. А тут получается реально левая часть seq никак не используется и вычислять её не имеет смысла, а она все ровно впустую вычисляется, тк чистые функции все равно не имеют постэффектов.
А для устранения ленивости монады.
Выше я привел пример (хоть и синтетический), когда результат левой части seq не содержится в правой, но тем не менее отбрасывать ее не стоит.
>она все ровно впустую вычисляется
Не впустую, несмотря на то, что она не используется в правой части явным образом, она могла бы использоваться там неявно, например входить в правую часть, как компонент некого списка\тупла\ADT. А компилятор не будет трекать все эти зависимости, только ради того, чтобы защититься от дурака, который написал seq, которое ему не требовалось...
Для умолчательно ленивого языка нужно надежное средство форсировать вычисления. Считайте seq не частью языка, а мостиком в императивный мир (наряду с IO).
> A для устранения ленивости монады.
Будьте любезны пример вычисления sum от списка чисел.
main = print $ sum [1..25000000]
Ну сами же сказали "А для устранения ленивости монады".
Вот и реализуйте суммирование (не используя seq) так, чтобы оно не крашилось со stack/heap overflow из-за излишней ленивости...
Не нужны тут никакие монады. Не придумывай. Суммирование реализовал, не крошится:
http://ideone.com/8mcun
sum' = foldl (+) 0
main = print $ sum' [1..25000000]
А ленивость устранять нужно?
> , не крошится:
А теперь отключи оптимизации и удивись.
Прошу, на сцену.
>А ленивость устранять нужно?
Нет.
Кэп намекает, что ghc может оптимизировать этот случай, но не обязан (без -O и не будет). Другие компиляторы скорее всего отвалятся.
> Нет.
То есть по-вашему, это нормальный код? Вопросы отпали.
http://ideone.com/8ZB1r
>Stack space overflow
Нет, это не нормальный код, нормальный код я написал выше:
http://ideone.com/lpR0q
main = print $ sum [1..25000000]
>result: success time: 3.55s memory: 3588 kB returned value: 0
>output:
>312500012500000
Фома вы наш неверующий...
А на идеоне доказать? Ключи в тексте можно прямо указывать в {} А так я выдуманное тоже напечатать могу.
Вот уж точно неверующий Фома..
http://ideone.com/Vtzln
http://ideone.com/iXfTD
{-# OPTIONS_GHC -O0 #-}
import Data.List
sum' = foldl' (+) 0
main = print $ sum' [1..25000000]
Сам учись:
http://hackage.haskell.org/packages/archive/base/latest/doc/html/src/Data-List.html#foldl%27
Там используют seq.
Я не сказал, что seq не нужен. Я сказал, что не нужна бага, вычисляющая впустую выражение, помеченное в левой части seq, но реально ни где результат этого вычисления не используется.
P.S. Я поставил минус вторым. Ждем первого минуснувшего.
http://ideone.com/0daW7
> result: success
> output: 1
http://ideone.com/oy8hf
>result: runtime error
>stderr: prog: Prelude.undefined
Работает...
Нет, проспал на работу и только-только зашел на ГК.
Возьмем, к примеру, вот такой код:
http://ideone.com/p7AnA
Как видим, исключение про undefined выбросилось только тогда, как мы начали пользоваться результатом.
Немного переделаем его:
http://ideone.com/fDoUy
Теперь исключение возникло вовремя. И, как видим, результат forceList (snd tuple) не используется в правой части.
http://ideone.com/tBIzJ
forceList [] = ()
forceList (x:xs) = x `seq` forceList xs
Ну не хвосты, а пустой список, но да (), в данном случае лучше.
Тогда бы можно было форсировать любое выражение, но конечно же глупый хацкель не может в перегрузку функций.
Так можно же ;) Но правда не совсем любое, а то, что принадлежит классу NFData: http://hackage.haskell.org/packages/archive/deepseq/1.3.0.0/doc/html/Control-DeepSeq.html
Инстансы для примитивных типов, списков и туплов (т.е. все что вами перечислено выше) там уже описаны, а вот для пользовательских ADT нужно описать инстанс самому, что не совсем удобно, но терпимо.
Понятно, что делайте не более чем для туплов 3 элементов, а то много писать придется.
Не удалось... prog.hs:1:7:
Could not find module `Control.DeepSeq':
Use -v to see a list of the files searched for.
force там уже готов, но в старой версии, которая у меня стоит его почему-то нет. Поэтому в примере добавлена реализация force через deepseq, такая же как в новой либе.
Вот так вот можно описать инстанс NFData для своих типов:
Инстансы для примитивов, списков и туплов можно посмотреть тут:
http://hackage.haskell.org/packages/archive/deepseq/1.3.0.0/doc/html/src/Control-DeepSeq.html#rnf (для примитивов используется дефолтовое rnf a = a `seq` ()).
>force (x) = force x `seq` ()
Туплов одного элемента не бывает.
Откуда вы такие лезете.
Никогда. Потому что хаскелл - это функциональный небыдло-язык для элиты, код на нём не может быть говнокодом. А если тебе кажется, что это говнокод - значит ты, быдло, просто не смог осилить своим жалким умишком каррированное замыкание игрек-комбинатора.