Понимание этого задания? - PullRequest
0 голосов
/ 13 июня 2018

Чтобы освежить свой 20-летний опыт работы с Haskell, я прохожу через https://en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours/Adding_Variables_and_Assignment и в какой-то момент вводится следующая строка, чтобы применить op ко всем параметрам.Это для реализации, например, (+ 1 2 3 4)

numericBinop op params = mapM unpackNum params >>= return . Number . foldl1 op

Я не понимаю синтаксис, и объяснение в тексте немного расплывчато.

Я понимаю, что делает и как foldl1расставить точки по точкам (unpackNum - вспомогательная функция), но использование монад и оператора >>= оставляет меня в замешательстве.

Как это читать?

Ответы [ 5 ]

0 голосов
/ 13 июня 2018

Во-первых, синтаксис.Пробел - это приложение , семантически:

f x = f $ x  -- "call" f with an argument x

, поэтому ваше выражение на самом деле

numericBinop op params = ((mapM unpackNum) params) >>= return . Number . (foldl1 op)

Далее, операторы построены из не алфавитно-цифровых символов, без пробелов,Здесь есть . и >>=.Запуск :i (.) и :i (>>=) в GHCi показывает их фиксированность спецификации infixl 9 . и infixr 1 >>=.9 выше 1, поэтому . сильнее >>=;таким образом,

                       = ((mapM unpackNum) params) >>= (return . Number . (foldl1 op))

infixl 9 . означает ., связанное справа, таким образом, наконец, это

                       = ((mapM unpackNum) params) >>= (return . (Number . (foldl1 op)))

(.) определяется как (f . g) x = f (g x), таким образом (f . (g . h)) x = f ((g . h) x) = f (g (h x)) = (f . g) (h x) = ((f . g) . h) x;при сокращении eta мы имеем

(f . (g . h)) = ((f . g) . h) 

, таким образом, (.) является ассоциативным, и поэтому заключение в скобки необязательно.Теперь мы будем отбрасывать явные символы с помощью приложения «пробел».Таким образом, у нас есть

numericBinop op params = (mapM unpackNum params)  >>= 
          (\ x -> return (Number (foldl1 op x)))   -- '\' is for '/\'ambda

. Монадические последовательности легче записать с помощью do, а приведенное выше эквивалентно

      = do 
          {  x <- mapM unpackNum params        -- { ; } are optional, IF all 'do'
          ;  return (Number (foldl1 op x)))    --   lines are indented at the same level
          }

Далее, mapM можно определить как

    mapM f [] = return []
    mapM f (x:xs) = do { x  <- f x ;
                         xs <- mapM f xs ;
                         return (x : xs) }

и Законы Монады требуют, чтобы

      do { r <- do { x ;       ===      do { x ;
                     y } ;                   r <- y ;  
           foo r                             foo r 
         }                                 }

(вы можете найти краткий обзор do обозначений, например, в этом недавнем ответе моего);таким образом,

numericBinop op [a, b, ..., z] =
   do {
         a <- unpackNum a ;
         b <- unpackNum b ;
         ...........
         z <- unpackNum z ;
         return (Number (foldl1 op [a, b, ..., z]))
      }

(вы могли заметить, что я использовал x <- x привязок - мы можем использовать одно и то же имя переменной с обеих сторон <-, потому что монадные привязки не рекурсивно - таким образом, вводя теневое копирование.)

Теперь это стало понятнее, надеюсь.

Но я сказал: " first , синтаксис".Итак, теперь смысл этого.По тем же законам Монады,

numericBinop op [a, b, ..., y, z] =
   do {
         xs <- do { a <- unpackNum a ;
                    b <- unpackNum b ;
                    ...........
                    y <- unpackNum y ;
                    return [a, b, ..., y] } ;
         z <- unpackNum z ;
         return (Number (op (foldl1 op xs) z))
      }

, таким образом, нам нужно только понять последовательность двух"вычислений", c и d,

do { a <- c ; b <- d ; return (foo a b) }
=
          c >>= (\ a ->
                   d >>= (\ b ->     
                       return (foo a b) ))

для конкретной участвующей монады, которая определяется реализацией оператора bind (>>=) для данной монады.

Монады - это EDSL для композиции обобщенных функций.Последовательность вычислений включает в себя не только явные выражения, появляющиеся в последовательности do, но также неявные эффекты, свойственные конкретной рассматриваемой монаде, выполняемые принципиально и согласованно за кулисами.В этом и заключается весь смысл наличия монад в первую очередь (ну, по крайней мере, одного из основных моментов).

Здесь, по-видимому, рассматриваемая монада озабочена возможностью провала и ранних спасенийв случае, если сбой действительно произойдет.

Итак, с помощью кода do мы пишем сущность того, что мы намереваемся случиться, и возможность прерывистого отказа автоматически рассматривается для нас за кулисами.

Другими словами, если одно из unpackNum вычислений не удастся , то все объединенное вычисление завершится неудачей, не пытаясь выполнить любое из следующих unpackNumсуб-вычислений.Но если все они преуспеют, то комбинированные вычисления также будут успешными.

0 голосов
/ 13 июня 2018

Вы можете «засунуть это» в более удобный для новичков синтаксис с помощью нотации do.Ваша функция numericBinop op params = mapM unpackNum params >>= return . Number . foldl1 op станет:

numericBinop op params = do
    x <- mapM unpackNum params -- "<-" translates to ">>=", the bind operator
    return . Number $ foldl1 op x

Что ж, теперь самой загадочной является функция mapM, то есть sequence . fmap, и она просто принимает функцию fmaps это над контейнером, и переворачивает тип , в этом случае (я полагаю) от [Number Integer] до ThrowsError [Integer], сохраняя при этом любые ошибки (побочные эффекты), которые могут возникнуть во время переворачивания или другихслова, если «переворот» вызвал какую-либо ошибку, он будет представлен в результате.

Не самый простой пример, и вам, вероятно, было бы лучше увидеть, чем mapM (fmap (+1)) [Just 2, Just 3] отличается от mapM (fmap (+1)) [Just 2, Nothing].Для получения более подробной информации обратитесь к Traversable классу типов.

После этого вы связываете [Integer] из монады ThrowsError и передаете его функции, которая выполняет выполнениевычисления в списке, в результате чего получается один Integer, который, в свою очередь, необходимо повторно встроить в монаду ThrowsError с функцией return после ее преобразования в Number.

Если у вас все еще есть проблемы с пониманием монад, я предлагаю вам взглянуть на все еще актуальную главу LYAH , которая мягко познакомит вас с монадами

0 голосов
/ 13 июня 2018

Ваш вопрос касается синтаксиса, поэтому я просто расскажу о том, как разобрать это выражение.Синтаксис Haskell довольно прост.Неофициально:

  • идентификаторы, разделенные пробелами, являются функциональным приложением (первый идентификатор применяется к остальным)
  • , за исключением идентификаторов, которые используют не алфавитно-цифровые символы чата (например, >>= или * 1007).*) являются инфиксными (т. е. их первый аргумент находится слева от идентификатора)
  • первый тип приложения функции выше (не инфиксный) связывается более тесно, чем могут ассоциироваться вторые операторы
  • либо влево, либо вправо, и имеют другой приоритет (определенный с помощью объявления infix...)

Так что, зная это, только если я увижу:

mapM unpackNum params >>= return . Number . foldl1 op

Для началаЯ знаю, что это должен быть синтаксический анализ, подобный

(mapM unpackNum params) >>= return . Number . (foldl1 op)

Чтобы пойти дальше, нам нужно проверить правильность / приоритет двух операторов, которые мы видим в этом выражении:

Prelude> :info (.)
(.) :: (b -> c) -> (a -> b) -> a -> c   -- Defined in ‘GHC.Base’
infixr 9 .
Prelude> :info (>>=)
class Applicative m => Monad (m :: * -> *) where
  (>>=) :: m a -> (a -> m b) -> m b
  ...
    -- Defined in ‘GHC.Base’
infixl 1 >>=

(.)имеет более высокий приоритет (9 против 1 для >>=), поэтому его аргументы будут связываться более тесно (т.е. мы сначала ставим их в скобки).Но как мы узнаем, что из этого является правильным?

(mapM unpackNum params) >>= ((return . Number) . (foldl1 op))
(mapM unpackNum params) >>= (return . (Number . (foldl1 op)))

...?Поскольку (.) было объявлено infixr, оно ассоциируется справа, что означает, что второй синтаксический анализ выше верен.

Как указывает Уилл Несс в комментариях, (.) является ассоциативным (например, сложение), поэтому обаэто семантически эквивалентно.

Имея небольшой опыт работы с библиотекой (или Prelude в данном случае), вы научитесь правильно анализировать выражения с операторами, не слишком задумываясь.

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

0 голосов
/ 13 июня 2018

По сути,

mapM unpackNum params >>= return . Number . foldl1 op

состоит из двух частей.

mapM unpackNum params означает: взять список параметров params.На каждый предмет примените unpackNum: это даст Integer, завернутый в монаду ThrowsError.Таким образом, это не совсем простой Integer, так как он может ошибиться.В любом случае, использование unpackNum на каждом элементе либо приводит к успешному получению всех Integers, либо выдает некоторую ошибку.В первом случае мы создаем новый список типа [Integer], во втором мы (неудивительно) выкидываем ошибку.Итак, результирующий тип для этой части - ThrowsError [Integer].

Вторая часть - ... >>= return . Number . foldl1 op.Здесь >>= означает: если в первой части возникла какая-то ошибка, то и это сообщение выдает и целое выражение.Если детали удалось создать [Integer], перейдите к foldl1 op, оберните результат как Number и, наконец, используйте return, чтобы ввести это значение как успешное вычисление.

В общем, есть монадическиевычисления, но вы не должны думать о них слишком много.Монадические вещи здесь распространяют только ошибки или сохраняют простые значения, если вычисление прошло успешно.Имея небольшой опыт, можно сконцентрироваться только на успешных значениях и позволить mapM,>>=,return обрабатывать случаи ошибок.

Кстати, обратите внимание, что хотя в книге используется код, подобный action >>= return . f, это возможноплохой стиль.С тем же эффектом можно использовать fmap f action или f <$> action, что является более прямым способом выражения того же вычисления.Например,

Number . foldl1 op <$> mapM unpackNum params

, что очень близко к немонадному коду, игнорирующему случаи ошибок

-- this would work if there was no monad around, and errors did not have to be handled
Number . foldl1 op $ map unpackNum params
0 голосов
/ 13 июня 2018

>>= строит вычисление, что может потерпеть неудачу на любом конце : его левый аргумент может быть пустой монадой, в этом случае это даже не происходит, в противном случае его результат также может быть пустым.Он имеет тип

>>= :: m a -> (a -> m b) -> m b

См. Его аргументы: значения, погруженные в монаду, и функция, которая принимает чистое значение и возвращает погруженный результат.Этот оператор является монадической версией того, что известно, например, как flatMap в Scala;в Haskell его конкретная реализация для списков известна как concatMap.Если у вас есть список l, то l >>= f работает следующим образом: для каждого элемента l, f применяется к этому элементу и возвращает список;и все эти результирующие списки объединяются для получения результата.

Рассмотрим код на Java:

try {
    function1();
    function2();
}
catch(Exception e) {
}

Что происходит, когда вызывается function2?Видите, после вызова function1 программа, вероятно, находится в допустимом состоянии, поэтому function2() - это оператор, который преобразует это текущее состояние в другое.Но вызов function1() может привести к возникновению исключительной ситуации, поэтому элемент управления будет немедленно переведен в catch -блок - это можно рассматривать как нулевое состояние, поэтому к function2 применять нечего.Другими словами, у нас есть следующие возможные пути управления:

[S0]  --- function1() -->  [S1]  --- function2() -->  [S2]
[S0]  --- function1() -->  []    --> catch

(Для простоты исключения, выданные из function2, на этой диаграмме не рассматриваются.)

Итак, либо [S1] является (непустым) допустимым состоянием машины, и function2 преобразует его далее в (непустое) действительное [S2], или оно пустое, и, таким образом, function2() не работает и никогда не запускается.Это можно обобщить в псевдокоде как

S2 <- [S0] >>= function1 >>= function2
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...