1. Haskell / Говнокод #11478

    −95

    1. 1
    2. 2
    3. 3
    4. 4
    5. 5
    6. 6
    7. 7
    8. 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

    Haskell не может в not used expression elimination. Не используемые константы a и b не убрал из вычисления.
    В результате видим пустую трату времени time: 13.15s :
    http://ideone.com/41Q8D
    И это то ленивом языке, не смотря на то, что эти вычисления не нужны. Можно писать в багтреккер.

    P.S.: Когда уже хаскель в подсветку говнокода добавят?

    Запостил: HaskellGovno, 26 Июля 2012

    Комментарии (58) RSS

    • Зато вот это уже заслуживает уважения:
      http://ideone.com/nkoXr
      fibs = 0:1:zipWith (+) fibs (tail fibs)
      fib = (fibs !!)
       
      main =  let 
                      a = [fib 250000, fib 25000000, fib 3] 
                      b = length a
              in print b
      >time: 0.01s
      C# LINQ Enumerable.Count станет вычислять каждый элемент списка, так что там подсчет кол-ва на долго затянется.
      Ответить
    • а если через where написать?
      Ответить
      • Будет тоже самое. Тут дело в том, что когда потребуется результат seq, сначала должен быть приведен к WHNF ее левый аргумент, т.е. рантайму придется вычислить fib 250000.

        P.S. HaskellGovno сам заставил хаскель не лениться и вычислить b, и сам обосрал его за то, что он его вычисляет...
        Ответить
      • Не помогает:
        http://ideone.com/pV04A
        Ну а так баг пропадает:
        http://ideone.com/h53JK
        Ответить
        • > Ну а так баг пропадает:
          Правильно. Потому что WHNF - раскрытие до первого попавшегося конструктора. В данном случае этим конструктором будет конструктор списка. Ни голова списка, ни его хвост вычисляться не будут.
          Ответить
          • А смысл это делать в ленивом языке, если выражение нигде дальше используется? Это явно бага. Не анализирует или не получается у него проанализировать данное выражение. Все современные языки уже умеют выкидывать не используемые вычисления выражений.
            Ответить
            • >Все современные языки уже умеют выкидывать не используемые вычисления выражений.
              Хаскель к ним, как мы видим, не относится. Никакого выкидывания не используемых переменных во время компиляции. Глупо. Вся надежда только на лень. То есть не используемые выражения выкидываются только в рантайме. Глупо. В результате лишние тормоза и пустое разрастание экзешника.
              Ответить
              • > Никакого выкидывания не используемых переменных во время компиляции.
                Толсто же...

                seq это специальный оператор, семантика которого заставляет хаскель вычислить левый аргумент до первого конструктора. Зачем было писать seq, если этот эффект не требуется?
                Ответить
                • показать все, что скрытоЛол, зачем было абсолютно впустую вычислять чистую функцию, тратя 13 секунд, если результат её нигде не используется? Это лишь бага хаскела и задайте этот вопрос его создателям.

                  Люди тоже ошибаются, а Хаскель проектировался, как думающий за программиста и всё проверяющий язык.

                  Между прочем, хаскель, если наконец прочитаете документацию, имеет право опустить рекомендацию 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 рекомендация, в студию.
                    Ответить
                    • Ну например по первой же ссылке:
                      http://www.mail-archive.com/[email protected]/msg10973.html


                      >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.
                        Ответить
            • let
                  -- пусть parseSomeData возвращает тупл
                  res = parseSomeData data
              in (snd res) seq res


              Результат snd res дальше не используется. Но если во втором элементе тупла будет undefined или какое-то длинное вычисление, то оно вылетит\выполнится именно при этом вызове, а не когда кто-то через пару часов полезет смотреть res.

              А ваш код аналогичен случаю, если уебать молотком по пальцам, и говорить, а чего это он бьется, это явно бага...
              Ответить
              • Между прочим весьма глупо, что казалось бы чистый let иногда вычисляет выражение, а именно в случае:
                let (a,b) = f
                В результате вычисляется неиспользуемое ниже выражение справа от =. Вот тебе и ленивый язык... Зато напридумывали костылей ~, исправляющих это:
                let ~(a,b) = f
                Ответить
                • Стоит иметь ввиду что ленивый язык -- не одно и тоже, что и экстрасенс, читающий ваши мысли.
                  Ответить
                • Лолчто.
                  let не вычисляет выражений. И = не вычисляет. До тех пор, пока выражение описанное в let не будет задействовано в каком-то вычислении, хаскель даже не почешется его вычислять.

                  P.S. let (a,b) = undefined in 2
                  Ответить
                • Вообще-то сопоставление по образцу на верхнем уровне (точнее pattern binding называется) по умолчанию irrefutable.
                  Ответить
    • показать все, что скрытоТолько тут я делал для наглядности. реально вместо fib 250000 нужно использовать undefined. То есть вместо тормозов будет кидаться рантайм еррор
      Ответить
    • > b `seq` ...
      > Не используемые константы a и b не убрал из вычисления.

      bormand уже написал, но ты реально толст. seq - и есть устранение ленивости, просто использовать нужно по необходимости.
      Ответить
      • >seq - и есть устранение ленивости
        Нет. Имеет смысл вычислять левую часть seq только в случае, если она используется в правой части. А тут получается реально левая часть seq никак не используется и вычислять её не имеет смысла, а она все ровно впустую вычисляется, тк чистые функции все равно не имеют постэффектов.
        А для устранения ленивости монады.
        Ответить
        • >если она используется в правой части
          Выше я привел пример (хоть и синтетический), когда результат левой части seq не содержится в правой, но тем не менее отбрасывать ее не стоит.

          >она все ровно впустую вычисляется
          Не впустую, несмотря на то, что она не используется в правой части явным образом, она могла бы использоваться там неявно, например входить в правую часть, как компонент некого списка\тупла\ADT. А компилятор не будет трекать все эти зависимости, только ради того, чтобы защититься от дурака, который написал seq, которое ему не требовалось...
          Ответить
        • > Нет. Имеет смысл вычислять левую часть seq только в случае, если она используется в правой части.
          Для умолчательно ленивого языка нужно надежное средство форсировать вычисления. Считайте seq не частью языка, а мостиком в императивный мир (наряду с IO).

          > A для устранения ленивости монады.
          Будьте любезны пример вычисления sum от списка чисел.
          Ответить
          • http://ideone.com/jJ7Jo

            main = print $ sum [1..25000000]
            Ответить
            • Смотрю, вы, батенька, знатный затейник. Мне интересна реализация sum "на монадах".
              Ответить
              • Зачем тут монады, если они тут ни к селу ни к городу?
                Ответить
                • > Зачем тут монады, если они тут ни к селу ни к городу?
                  Ну сами же сказали "А для устранения ленивости монады".
                  Вот и реализуйте суммирование (не используя seq) так, чтобы оно не крашилось со stack/heap overflow из-за излишней ленивости...
                  Ответить
                  • >Вот и реализуйте суммирование (не используя seq) так, чтобы оно не крашилось со stack/heap overflow из-за излишней ленивости...

                    Не нужны тут никакие монады. Не придумывай. Суммирование реализовал, не крошится:
                    http://ideone.com/8mcun
                    sum' = foldl (+) 0
                    main = print $ sum' [1..25000000]
                    Ответить
                    • > Не нужны тут никакие монады.
                      А ленивость устранять нужно?

                      > , не крошится:
                      А теперь отключи оптимизации и удивись.
                      Ответить
                      • >А теперь отключи оптимизации и удивись.
                        Прошу, на сцену.

                        >А ленивость устранять нужно?
                        Нет.
                        Ответить
                        • Да спасибо, но вы сами неплохо выступаете, пару раз порадовали.

                          Кэп намекает, что ghc может оптимизировать этот случай, но не обязан (без -O и не будет). Другие компиляторы скорее всего отвалятся.

                          > Нет.
                          То есть по-вашему, это нормальный код? Вопросы отпали.
                          Ответить
                          • показать все, что скрытоНичего он не оптимизирует. Это нормальное поведение. Если ты не читал документацию и применяешь foldr там где он не подходит, то я могу тебе только посочувствовать:
                            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
                            Ответить
                        • > Прошу, на сцену.
                          Фома вы наш неверующий...
                          $ cat 1.hs
                          sum' = foldl (+) 0
                          main = print $ sum' [1..25000000]
                          $ ghc 1.hs
                          [1 of 1] Compiling Main             ( 1.hs, 1.o )
                          Linking 1 ...
                          $ ./1 
                          Stack space overflow: current size 8388608 bytes.
                          Use `+RTS -Ksize -RTS' to increase it.
                          Ответить
                          • >Фома вы наш неверующий...
                            А на идеоне доказать? Ключи в тексте можно прямо указывать в {} А так я выдуманное тоже напечатать могу.
                            Ответить
                            • > А на идеоне доказать?
                              Вот уж точно неверующий Фома..

                              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. Я поставил минус вторым. Ждем первого минуснувшего.
                            Ответить
                            • Окей, покажи мне этот пример, но так чтобы элементом тупла были списки. Думаю ты так просто не отделаешься.
                              Ответить
                            • bormand? Ну так что там? Не справился? То, для чего ты сказал эта бага для полного вычисления списка бесполезна.
                              Ответить
                              • Упс:
                                http://ideone.com/0daW7
                                main = let a = [1, 2, 4, undefined] in a `seq` print 1

                                > result: success
                                > output: 1
                                http://ideone.com/oy8hf
                                import Data.List
                                force = foldl' go ()
                                        where go a b = a `seq` b `seq` ()
                                main = let a = [1, 2, 4, undefined] in force a `seq` print 1

                                >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
                                  Ответить
                                  • > зачем ты возвращаешь хвосты
                                    Ну не хвосты, а пустой список, но да (), в данном случае лучше.
                                    Ответить
                                • На самом деле было бы действительно красиво, если бы можно было писать:
                                  force x = x `seq` ()
                                  force [] = ()
                                  force (a:b) = force a `seq` force b
                                  force () = ()
                                  force (x) = force x `seq` ()
                                  force (x, y) = force x `seq` force y `seq` ()
                                  force (x, y, z) = force x `seq` force y `seq` force z `seq` ()
                                  ...
                                  Тогда бы можно было форсировать любое выражение, но конечно же глупый хацкель не может в перегрузку функций.
                                  Ответить
                                  • перегрузка функций не особо нужна, когда есть истинный полиморфизм. Основное ограничение - число аргументов должно быть строго фиксированным. Через typeclasses можно написать функции, принимающие совершенно разные типы (и, о ужас, возвращающие значения разных типов, как =~ из Text.Regex.Posix).
                                    Ответить
                                  • > Тогда бы можно было форсировать любое выражение
                                    Так можно же ;) Но правда не совсем любое, а то, что принадлежит классу NFData: http://hackage.haskell.org/packages/archive/deepseq/1.3.0.0/doc/html/Control-DeepSeq.html

                                    Инстансы для примитивных типов, списков и туплов (т.е. все что вами перечислено выше) там уже описаны, а вот для пользовательских ADT нужно описать инстанс самому, что не совсем удобно, но терпимо.
                                    Ответить
                                    • Если не сложно, то используя мой пример сможете реализовать на основе NFData force для примитивных типов? Или я чего не понял и force там уже готов?

                                      Понятно, что делайте не более чем для туплов 3 элементов, а то много писать придется.
                                      Ответить
                                      • http://ideone.com/R1P0y
                                        Не удалось... prog.hs:1:7:
                                        Could not find module `Control.DeepSeq':
                                        Use -v to see a list of the files searched for.
                                        Ответить
                                      • > Или я чего не понял и force там уже готов?
                                        force там уже готов, но в старой версии, которая у меня стоит его почему-то нет. Поэтому в примере добавлена реализация force через deepseq, такая же как в новой либе.

                                        Вот так вот можно описать инстанс NFData для своих типов:
                                        import Control.DeepSeq
                                        import Control.Exception
                                         
                                        data MyType = MyType Int [Int] deriving Show
                                         
                                        instance NFData MyType where
                                            rnf (MyType x ys) = x `seq` rnf ys `seq` ()
                                        
                                        force x = x `deepseq` x
                                        
                                        main = do
                                            let x = MyType 100500 [1, 2, 3, undefined]
                                            putStrLn "Deep sequencing..."
                                            evaluate $ force x
                                            putStrLn "Deep sequencing done"
                                            print x


                                        Инстансы для примитивов, списков и туплов можно посмотреть тут:
                                        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` ()
                                      Туплов одного элемента не бывает.
                                      Ответить
    • Охуеть. Сам ведь форсируешь вычисление, с помощью seq, а потом жалуешься чо так долго.
      Откуда вы такие лезете.
      Ответить
    • > P.S.: Когда уже хаскель в подсветку говнокода добавят?

      Никогда. Потому что хаскелл - это функциональный небыдло-язык для элиты, код на нём не может быть говнокодом. А если тебе кажется, что это говнокод - значит ты, быдло, просто не смог осилить своим жалким умишком каррированное замыкание игрек-комбинатора.
      Ответить
      • Маловато будет баззвордов, лучше так: http://ibash.org.ru/quote.php?id=15426
        Ответить
    • Кстати, параллельный код на Хаскеле, форсированный аннотациями выглядит как я.
      Ответить

    Добавить комментарий