1. Куча / Говнокод #11968

    +128

    1. 1
    f = expr `catches` [Handler (\ (ex :: ArithException) -> handleArith ex), Handler (\ (ex :: IOException)    -> handleIO    ex)]

    Собственно это пример как в хаскеле перехватывать исключения из одного выражения expr.
    Выглядит конечно отстойно. Очень многословно и судя по всему метода проще нет. И понятное дело, в чистых ленивых функциях это не работает. Ловить исключения можно только в грязных IO-функциях, тк сигнатура такова: catches :: IO a -> [Handler a] -> IO a

    Запостил: HaskellGovno, 21 Октября 2012

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

    • Теперь собственно вопросы:
      1)Почему всё так отстойно?
      2)Как в божественном Хаскеле список [] умудряется хранить сигнатуры разных типов функций?
      В данном случае мы видим, что первая лямбда имеет тип приблизительно :: ArithException -> IO () . А вторая лямбда имеет тип приблизительно :: IOException -> IO () .
      Как такое возможно? Допустим, что они привились к некоторому базовму типу, типа :: Object -> IO () (понятно, что это врятли так, но всеже допустим.). Привидение к некоторому мифическому абстрактному Object всё же необходимо, иначе просто разные сигнатуры (лямбды разных типов) не загонишь в один список. В данном случае что-то типа [Handler (Object -> IO ())] получилось. Разные типы можно было бы в кортеж загнать, но здесь список и он должен содержать элементы одного типа. Раз всё привелось к одному типу, то тогда статическая информация о типе ловимого исключения должна теряться. Но она не теряется. Почему? Как? То есть собственно вопросы:
      1.1)Почему скомпилировался список из разнотипных элементов? 1.2)Почему не потерялась статическая информация о типе исключения?
      Ответить
      • > class (Typeable e, Show e) => Exception e where
        Вот видимо через этот Typeable он и выбирает подходящий элемент из списка. Лень сегодня копаться глубже, сорри ;(
        Ответить
        • Ну он добавляет поддержку что-то типа typeid из крестов.
          Но моя догадка, что сигнатура лямбды приводится к общему типу, верна?
          Просто после такого приведения, мне не понятно, как вообще достать тип параметра сигнатуры. Тоесть как вытащить из экземпляра лямбдочки :: Object -> IO () конкретно "кусок" сигнатуры, тот что типа Object, чтобы потом из него получить уже с помощью Typeable реальный тип исключения.
          И ещё не понятно зачем нужен этот самый Handler обернутый вокруг лямбдочки, который только удлиняет запись для перехвата каждого исключения.
          Ответить
          • Все, что написано ниже, является плодом моего воображения, и не подтверждено чтением кода.
            По идее, нельзя сделать произвольные функции инстансом тайпкласса. Поэтому вводится промежуточная сущность - Handler, которая как раз принадлежит нужному тайпклассу, благодаря чему ее можно засунуть в список. И, видимо, у этого handler'а должен быть метод, которым он сможет обработать экцепшн, если он ему понравится (совпадут тайпиды). Как-то так.
            Ответить
          • У класса Exception есть метод fromException :: SomeException -> Maybe e. Если посмотреть на реализацию catches (вернее
            catchesHandler) - она перебирает обработчики в цикле, пытаясь вызвать fromException, и если это удается - вызвать сам обработчик.

            P.S. Не совсем въезжаю откуда берется тип.
            data Handler a = forall e . Exception e => Handler (e -> IO a)
            
            catches :: IO a -> [Handler a] -> IO a
            catches io handlers = io `catch` catchesHandler handlers
            
            catchesHandler :: [Handler a] -> SomeException -> IO a
            catchesHandler handlers e = foldr tryHandler (throw e) handlers
                where tryHandler (Handler handler) res
                          = case fromException e of
                            Just e' -> handler e'
                            Nothing -> res
            Ответить
            • Спасибо большое.
              >forall e . Exception e => Handler (e -> IO a)
              Про forall прочитать ещё не успел. Сейчас буду вчитываться. Но почему такая странная форма записи? Обычно вроде вместо точки используется запятая при этом тайпклассы начинаются с большой буквы и все это берется в скобочки или как-то так, например:
              (Forall e , Exception e) => Handler (e -> IO a)

              >(throw e)
              Кидает исключение или возвращает в качестве результата функции throw?

              Хаскел - это как черная дыра. И интересно и затягивает.

              Вот так каждый раз. как что-то интересное увидел - хочется заглянуть как они это реализовали...
              Ответить
              • >>(throw e)
                >Кидает исключение или возвращает в качестве результата функции throw?
                Ну судя по тому, что оно вызовется когда foldr переберет все варианты - это просто перевброс необработанного исключения.

                forall это не класс, это специальная форма записи. Читается вроде-бы так: "Для каждого типа e, такого, что e является Exception". (Математический квантор всеобщности).
                Ответить
                • >throw :: Exception e => e -> a
                  >Throw an exception. Exceptions may be thrown from purely functional code, but may only be caught within the IO monad.

                  Не очень понимаю. Функция вброса исключения не IO. При этом она возвращет некую а. При этом результат вброса исключения сложили в аккумулятор foldr и ниже функцией tryHandler приняли в res. Я так понимаю тут в ленивом коде исключения имеют особое значение и ни приводят к никакой размотки стека, а это обычное значение, хранящееся на месте результата функции или в константе произвольного типа. Притом, если их не "трогать", то и исключения не произойдет. Точнее исключение будет просто лениво храниться например в некоторой константе или в списке и приложение совсем не упадет, пока его не "потрогаешь".
                  Ответить
                  • > Функция вброса исключения не IO. При этом она возвращет некую а.
                    Она имеет произвольный возвращаемый тип. Т.к. на самом деле возврата из нее нет, то и на тип всем пофиг.

                    > пока его не "потрогаешь"
                    Ну да, (throw e) спокойно лежит невыполненным дожидаясь своего часа. Если во время исполнения foldr не найдется подходящиго хендлера - это исключение заденут, и оно будет вброшено, и возможно поймано на более высоких уровнях.

                    P.S. Я вот не пойму, как определяется тип для fromException. Тип handler'а ведь безвозвратно проёбан.
                    Ответить
                    • >Я вот не пойму, как определяется тип для fromException. Тип handler'а ведь безвозвратно проёбан.
                      >fromException :: SomeException -> Maybe e
                      >fromException ex =
                      Я вот тоже совсем не понимаю. Во первых почему он возвращает Maybe e, а не e. Видимо потому, что тип исключения ex и e если не совпали, то он возвращает Nothing, иначе Just e. При этом тип исключения результата функции fromException, а именно e он берет за счет вывода типа из использования результата этой функции, так как дальше результат передаётся в лямбду, принимающую параметр определенного типа.
                      Например если лямбда handler принимает ArithException, то вывод типа выводит тип результата функции fromException как MayBe ArithException.
                      (Извиняюсь, если говорю очевидные вещи. Я уверен, что вы это знаете лучше меня. Просто пытался рассуждать логически.)
                      Но есть одно но:
                      handler имеет тип Object -> IO () и принимает в качестве параметра некий тип Object (условно говоря). Переход к Object был необходим, чтобы покласть в одну коллекцию все разнотипные лямбды. То есть на самом деле выводиться все тот же тип MayBe Object в качестве результата fromException. То есть тип действительно теряется и я тоже не понимаю как он работает, тк fromException (ex::SomeException) c результатом MayBe Object должен вернуть всегда Nothing , тк тип ex с Object не совпал... :(
                      Object я говорил для простоты. На самом деле он имеет тип (Exception e => e).
                      Ответить
                      • Вот упрощенный пример: http://ideone.com/AAIkhd.

                        Здесь я попытался повторить ту же самую модель - тип SomeTest, в который при помощи toTest можно поместить любой инстанс класса Test. И функцию test, которая прогоняет массив обработчиков над неким SomeTest и возвращает результат подходящего обработчика, или Nothing.

                        P.S. Да, оно работает. Да, я не понимаю почему оно работает. Или в строчке (Handler handler) оно видит точный тип хендлера, или я ничего не понимаю.
                        Ответить
                        • Ладно. У меня уже ночь. Наверное у вас тоже. Завтра буду разбираться. Спасибо за хорошую беседу. Спокойной ночи. :)
                          Ответить
                      • Стоп. У меня почти всё сошлось.
                        Действительно тип теряется и превращается в
                        fromException :: SomeException -> MayBe Object
                        а если говорить правильнее, то
                        fromException :: Exception e => SomeException -> MayBe e
                        Но мы помним, что Exception потомок Typeable
                        Это значит, что он реализует метод fromException как-то так:
                        fromException ex = (typeid ex) == (typeid $ throw e)
                        Понятно, что в хаскеле typeid имеет другое название, но у Typeable подобная функция есть.

                        Если что-то не понятно, то могу пояснить подробнее.
                        Ответить
                        • *Действительно тип теряется, поэтому fromException превращается в
                          fromException :: SomeException -> MayBe Object из-за вывода типа.
                          Ответить
                        • Этот момент понятен. Как работает fromException и toException я понимаю (см. мою реализацию). Но чтобы fromException корректно работал, нам нужно знать тип его возвращаемого значения. Например в моем коде:
                          fromTest (toTest 'a') :: Maybe Int --вернет nothing
                          fromTest (toTest 'a') :: Maybe Char -- вернет Just 'a'
                          fromTest (toTest 'a') -- не скомпилится, т.к. не может понять тип


                          Я вот не пойму другой момент - как хаскель угадывает нужный тип в tryHandler, ведь там обезличенный handler и обезличенный t. Вот если я пойму, как выводится тип в данном случае - я пойму принцип работы этого механизма.
                          Ответить
                          • >Я вот не пойму другой момент - как хаскель угадывает нужный тип в tryHandler

                            А что тут думать? Теперь это более или менее очевидно:
                            tryHandler :: (Exception e) => (Handler (e -> IO ())) -> e -> IO ()

                            >Но чтобы fromException корректно работал, нам нужно знать тип его возвращаемого значения.

                            Я же выше описал. Тип результата fromException выводится выводом типа из использования этого самого результата и получает тип (Exception e) => SomeException -> MayBe e
                            То есть результат fromException имеет тип (Exception e) => Maybe e, что есть аналог интерфейса из прочих языков, завернутого в контейнер MayBe.
                            То есть результат fromException - экземпляр класса на псевдокоде:
                            typedef (Exception e => e) IException;
                            class СMaybe
                            {
                             IException obj;
                            }


                            Exception в свою очередь потомок Typeable и Show, то есть потомок ITypeable и IShow. А это значит, что у IException как у потомка ITypeable есть аналог метода typeid и поэтому можно сравнить выведенный тип результата с типом проверяемого исключения.

                            А выводится тип результата fromException из использования, тк мы передаём этот результат в функцию handler, имеющую тип (Exception e) => e -> IO ().
                            То есть получается такая ситуация, из которой легко вывести тип результата:
                            handler(ИзвлечьИзMaybe(fromException(ex) )); То есть в данном случае handler принимает IException, а оттуда можно вверх по цепочке применений легко вывести, что fromException вернет MayBe IException.

                            Я пытался провести аналогии между интерфейсами IException и классами типами примененными к некоторому абстрактному типу e: (Exception e => e). Я почти уверен, что примерно так оно и реализовано в Хаскелл.



                            А вообще что-то вы в меня закрали сомнения... Пойду смотреть вашу программу...
                            Ответить
                            • Ноо... tryHandler в моем примере принимает просто [Handler [Char]] (а в библиотеке исключений [Handler (IO())], поэтому типом, который принимает завернутая в него функция там не особо пахнет.
                              handlers = [\(x::Int) -> "int: " ++ show x]
                              -- здесь тип handlers станет [Handler [Char]]
                              t = toTest (5::Int)
                              -- а тип t станет SomeTest
                              test handlers t
                              -- поэтому как компилятор сможет вывести, что в tryTest нужен именно тип Int?
                              Ответить
                        • >fromException ex = if (typeid ex) == (typeid $ throw e)
                          Я кстати ошибку грубую выше допустил. На псевдокоде должно быть примерно так:
                          fromException ex = if ((typeid ex) == (typeid $ throw e)) (just ex) else nothing
                          Я смотрю, вы какой-то cast применяете, вместо этого в своём коде, но видимо он примерно так же и реализован где-то внутри. Сейчас буду смотреть подробне.
                          Ответить
                          • cast находится в Data.Typeable. В реалиазации fromException его тоже юзают.
                            Ответить
                  • Я так понимаю, если ты объявил тип, то очень грубо говоря это не просто
                    data Either a b = Left a | Right b
                    а что-то типа:
                    data (forall e) => Either a b = Left a | Right b | ВнутреннийТипОберткаИсключений e
                    Так и с прочими стандартными типами типа Char и прочих.
                    Извиняюсь за возможно не верное обращение с forall
                    Ответить
                    • Да нет, типы объявляются нормально. Просто throw это грязный хак с вызовом рантайма:
                      throw e = raise# (toException e)
                      Насколько помню все что с # это встроенные в рантайм примитивы.
                      Ответить
      • Насчет гетерогенных списков - делаются они как-то так:
        data ShowBox = forall s. Show s => SB s
         heteroList :: [ShowBox]
         heteroList = [SB (), SB 5, SB True]
        Как видим в данный список можно поместить все, что принадлежит тайпклассу Show.

        Вот Вам на почитать перед сном: http://en.wikibooks.org/wiki/Haskell/Existentially_quantified_types.

        P.S. Запихать не проблема, сложнее вынуть. Элементы гетерогенного списка "обезличиваются", к ним можно обращаться только через методы классов, перечисленных в forall. Поэтому если не пристегнуть какой-нибудь Typeable - вынуть их оттуда в исходном виде уже не удастся.
        Ответить
        • Сейчас буду читать, но пока очень напоминает контейнер, в котором потеряли информации о конкретном типе, а оставили только информацию о его перечисленных в forall интерфейсах. Притом к методам из любого интерфейса можно обращаться через один экземпляр объекта.
          То есть на C#-подобном псевдокоде:
          class Container
          {
            public IDisposable ISerializable obj;
          }
          Container c;
          Теперь можно обратиться как к c.obj.Dispose() так и к c.obj.GetObjectData().
          Ответить
    • Просто в математике не бывает исключений, и поэтому человек который задумывал Хаскел никогда не обременял себя рассуждениями о полезности програм и их реальном применении. А когда "внезапно" оказалось, что программы как-то надо вообще запускать - на ходу сочинил какую-то херню, по принципу "лишь бы работало".
      Хаскел - не язык для написания програм, а язык для того, чтобы потешить эго математиков-прерафаэлитов, способных мыслить только заученными цитатами классиков 14-15 веков.
      Ответить
      • >Хаскел - не язык для написания програм
        Ну и зачем я его тогда изучаю? Не должно быть такого. Все языки программирования пишут для написания программ. Хаскелл должен для этого подходить. Не разрушайте мою детскую влажную мечту. Хаскелл божественен. Он есть царь-бог всех языков и на Земле и на Небе. :/
        Ответить
        • Да, исключения в Haskell выглядят пришельцами из другого мира. Ведь по сути, исключение - это монада, выражающая возможность неудачи выполнения операции. А для этого уже есть встроенные в язык идеоматические конструкции. Поэтому вся система обработки исключений выглядит для меня чужеродной и неестественной.
          Ответить
      • > Хаскел - не язык для написания програм, а язык для того, чтобы потешить эго математиков-прерафаэлитов, способных мыслить только заученными цитатами классиков 14-15 веков.

        К слову, теория множеств возникла в 19 веке. А теория категорий - изобретение математиков 20 века. В том, что исключения в Haskell выглядят хреново, чести не много, однако же, Maybe и Either удовлетворяют большую часть потребностей и весьма удобны. Исключения основном нужны при работе с файлами и базами данных, да и тут всё не так уж плохо: обработка исключения в Haskell, с моей точки зрения, выглядит проще и понятнее, чем работа с (более мощным и илитным) сигнальным протоколом CL.
        Ответить
        • Хаскел - это язык в котором круглое носят, а квадратное - катают потому, что уверены, что так нужно, и параллельно выравнивают земную поверхность, чтобы квадрантое лучше проскальзывало...
          Я не знаю... я каждый раз как вижу код какого-нибудь мудака с кучей степеней (по математике), который пишет (1 / Х) * У потому, что он привык так на бумажке писать. И мало того, еще и считает, что человек реализующий язык должен учитывать такие дибильные случаи и специально закладывать возможность трансформации таких выражений в нормальные. Хочется такого рожей об клавиатуру.
          Я могу понять, когда человек не знакомый вообще с концепцией, или просто неопытный так сделает. Но когда человек умышленно делает через жопу, когда он уже наверняка может понять, что его бюрократическое следование бумажке - вредит, и тем не менее с высокой кафедры продолжает вещать такую херню...
          Нет ни одного достаточно существенного повода для того, чтобы не использовать разрушающие присваивания. Более того, это основа основ в хорошем программировании. Нет вообще никаких мыслимых и немыслимых недостатков от побочных еффектов - все что об этом написано и подписано "умами" совеременности - бред сивого мерина, ничем абсолютно не подкрепленный. Больше всего это напоминает рассуждения схоластов о законах физики, категорически отрицавших любой эмпирический опыт (т.е. только основываясь на изучении святого писания).
          В программировании есть совершенно конкретные реалии и конкретные задачи. Человек, который умышленно избегает решений эксплуатирующих возможности и ресурсы - не просто дурак, а злостный идиот, который своим примером еще и других смущает.
          Ответить
          • > (1 / Х) * У
            Си быстро опускает любящих так писать с небес на землю... Мой первый график 1/2*sin(x) на С выглядел как прямая именно по этой причине.
            Ответить
            • > график 1/2*sin (x) на С выглядел как прямая
              если ее вообще было заметно, т.к. лежала на оси
              Ответить
              • Если мне не изменяет память - ось была другого цвета, поэтому таки было заметно.
                Ответить
          • >(т.е. только основываясь на изучении святого писания).
            О Хаскел всемогущий на небесах! да святится имя Твое
            да приидет Царствие Твое
            да будет воля Твоя и на земле, как на небе
            хлеб наш насущный дай нам на сей день
            и не введи нас в искушение (использовать мутабильные переменные), но избавь нас от лукавого (императивных парадигм). Ибо Твое есть Царство и сила и слава во веки. Аминь.



            PS: Все точки с запятой в конце строк убрал.
            Ответить
      • Вы так говорите, как будто математики никогда не были практиками. Возможно, по отношению к последней половине века это утверждение применимо с оговорками, но большая часть современных математических достижений была получена математиками-практиками.

        Возьмём, к примеру, Эйлера. Он был не только блестящим и невероятно плодовитым математиком, породившим множество методов и даже новых разделов (!) математики, он был прекрасным инженером, глубоким практиком. У него огромное количество работ, посвящённых практическому применению дифференциального и вариационного исчисления.

        Уравнения Лапласа для производных комплексных функций были получены в процессе исследований гидродинамики.

        Гауссу принадлежат несколько изобретений, включая электрический телеграф. Он провёл довольно много опытов вместе с Вебером, и помог ему построить математическую модель электромагнитизма.

        Практически все математики вплоть до 20 века были по совместительству физиками и инженерами. Потом уже выделилась каста "пуристов", занимающихся "диким матаном", который чуть позже нашёл себе применение в современных физических теориях. Да и среди них довольно часто встречаются практики.
        Ответить
        • Тесла, телеграф изобрел, Т-Е-С-Л-А!
          http://theoatmeal.com/comics/tesla

          :)
          Я горовю исключительно про математиков-прерафаелитов (прерафаелиты, это относительно современное явление, это такие готы конца 19, начала 20-го века).
          Это люди, которые, когда реализуют функции в программировании, вместо того, чтобы написать if, который им в их формулах тяжело дается, делают примерно следующее:
          (y - 1) * invertGiganticMatrix() + y * invertGiganticMatrix()

          где y может принимать значения только 0 и 1. (Но это хорошо, если именно так запишут, и можно будет легко отловить, а как правило, закопают так, что хрен найдешь). Ну и, естесственно, без ленивых вычислений с таким очень тяжело бороться. Но это люди, которые как правило сами себе создают неразрешимые пробемы и потом мужественно их решают.
          Это не недостаток математики, ни в коем случае. Это в моем представлении, недостаток системы образования, и, как результат, глубоко искаженного представления о действительности.
          Ответить
          • 1833 год: Гаусс изобретает электрический телеграф и (вместе с Вебером) строит его действующую модель.
            Ответить
            • А вы комикс смотрели? Нельзя же все так буквально воспринимать.
              Ответить
              • комикс предполагает небольшой набор картинок и лаконичный смищной текст к ним
                а не овер9000 пейдждаунов унылого комик-санса - не осилил и 10%
                Ответить
                • Да кстати. Там в основном текст. Могли же html+css3 наверстать - то мне "teleg.." искать неудобно.
                  Заебали форсить Теслу - посмотрите серию симпосонов ниже.
                  Ответить
                • ОК, для неосиливших. Тесла изобрел все. Поэтому про него так много написано.
                  Ответить
          • Simpsons.
            Season 10 Episode 2: The Wizard of Evergreen Terrace.
            Ответить

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