Подтвердить что ты не робот

Что такое практическое использование моноидов?

Я читаю "Learn You a Haskell", и я уже рассмотрел приложение, и теперь я на моноидах. У меня нет проблем с пониманием обоих, хотя я нашел применение полезным на практике, а моноид - не совсем так. Поэтому я думаю, что я ничего не понимаю о Haskell.

Во-первых, говоря о Applicative, он создает нечто вроде единообразного синтаксиса для выполнения различных действий над "контейнерами". Таким образом, мы можем использовать обычные функции для выполнения действий над Maybe, списками, IO (должны ли я сказать монады? Я еще не знаю монад), функции:

λ> :m + Control.Applicative
λ> (+) <$> (Just 10) <*> (Just 13)
Just 23
λ> (+) <$> [1..5] <*> [1..5]
[2,3,4,5,6,3,4,5,6,7,4,5,6,7,8,5,6,7,8,9,6,7,8,9,10]
λ> (++) <$> getLine <*> getLine
one line
 and another one
"one line and another one"
λ> (+) <$> (* 7) <*> (+ 7) $ 10
87

Таким образом, аппликация является абстракцией. Я думаю, что мы можем жить без него, но это помогает четко выражать некоторые идеи и прекрасно.

Теперь взглянем на Monoid. Это также абстракция и довольно простая. Но это помогает нам? Для каждого примера из книги кажется очевидным, что есть более четкий способ сделать что-то:

λ> :m + Data.Monoid
λ> mempty :: [a]
[]
λ> [1..3] `mappend` [4..6]
[1,2,3,4,5,6]
λ> [1..3] ++ [4..6]
[1,2,3,4,5,6]
λ> mconcat [[1,2],[3,6],[9]]
[1,2,3,6,9]
λ> concat [[1,2],[3,6],[9]]
[1,2,3,6,9]
λ> getProduct $ Product 3 `mappend` Product 9
27
λ> 3 * 9
27
λ> getProduct $ Product 3 `mappend` Product 4 `mappend` Product 2
24
λ> product [3,4,2]
24
λ> getSum . mconcat . map Sum $ [1,2,3]
6
λ> sum [1..3]
6
λ> getAny . mconcat . map Any $ [False, False, False, True]
True
λ> or [False, False, False, True]
True
λ> getAll . mconcat . map All $ [True, True, True]
True
λ> and [True, True, True]
True

Итак, мы заметили некоторые шаблоны и создали новый класс типа... Хорошо, мне нравится математика. Но с практической точки зрения, что точка Monoid? Как это помогает нам лучше выражать идеи?

4b9b3361

Ответ 1

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

Это о масштабируемости, архитектуре и дизайне API. Идея заключается в том, что существует "обычная архитектура", которая гласит:

Объедините несколько компонентов вместе типа A, чтобы сгенерировать "сеть" или "топология" типа B

Проблема с таким дизайном заключается в том, что по мере того, как ваша программа масштабируется, так и ваш ад, когда вы реорганизуете.

Итак, вы хотите изменить модуль A, чтобы улучшить свой дизайн или домен, чтобы вы это делали. О, но теперь модули B и C, зависящие от A, сломались. Вы исправляете B, отлично. Теперь вы исправите C. Теперь B снова сломался, так как B также использовал некоторые функции C. И я могу продолжать это навсегда, и если вы когда-либо использовали ООП - так и вы.

Затем, что Габриэль называет архитектурой "Хаскелл":

Объедините несколько компонентов вместе типа A для генерации нового компонент того же типа A, неотличимый по характеру от его заместителей

Это решает проблему, также элегантно. В принципе: не сложите свои модули и не расширьте их, чтобы сделать специализированные.
Вместо этого объедините.

Итак, теперь поощряется то, что вместо того, чтобы говорить такие вещи, как "У меня есть несколько X, поэтому давайте создадим тип, чтобы представить их объединение", вы говорите: "У меня есть несколько X, поэтому позвольте объединить их в X". Или на простом английском языке: "Пусть создавайте составные типы в самом первом месте". (чувствуете ли вы, что моноиды еще скрыты?).

Представьте, что вы хотите создать форму для своей веб-страницы или приложения, и у вас есть модуль "Персональная информационная форма", который вы создали, потому что вам нужна личная информация. Позже вы обнаружили, что вам также нужна "Change Picture Form", чтобы так быстро написать это. И теперь вы говорите, что я хочу их объединить, поэтому создайте модуль "Личная информация и форма изображения". И в реальной жизни масштабируемые приложения это может и выходит из-под контроля. Вероятно, не с формами, а с демонстрацией, вам нужно составить и составить, чтобы в итоге вы получили "Личную информацию и изменили изображение и изменили пароль и изменили статус, а также" Управление друзьями "и" Управление списком желаний "и" Изменить параметры просмотра ", и, пожалуйста, не расширяйте меня и пожалуйста, пожалуйста, прекратите! и остановитесь!!!!" модуль. Это не очень, и вам придется управлять этой сложностью в API. О, и если вы хотите что-то изменить - у него, вероятно, есть зависимости. Итак.. да.. Добро пожаловать в ад.

Теперь посмотрим на другой вариант, но сначала посмотрим на это, потому что он поможет нам:

Эти абстракции масштабируют безгранично, потому что они всегда сохраняют совместимость, поэтому нам не нужно наносить дополнительные абстракции наверху. Это одна из причин, почему вы должны изучать Haskell: вы узнаете, как для создания плоских архитектур.

Звучит неплохо, поэтому вместо того, чтобы создавать модуль "Личная информация" / "Изменить форму изображения", остановитесь и подумайте, можем ли мы сделать что-нибудь здесь составным. Ну, мы можем просто сделать "Форму", верно? также будет более абстрактным.
Тогда имеет смысл построить один для всего, что вы хотите, объединить их и получить одну форму, как и любой другой.

Итак, вы больше не получаете грязное сложное дерево из-за ключа, который вы берете в двух формах и получаете одну форму. Итак Form -> Form -> Form. И, как вы уже видите, эта подпись является экземпляром mappend.

Альтернатива, и обычная архитектура, вероятно, будет выглядеть как a -> b -> c, а затем c -> d -> e, а затем...

Теперь, с формами это не так сложно; задача состоит в том, чтобы работать с этим в реальных приложениях. И для этого просто спросите себя как можно больше (потому что он окупается, как вы можете видеть): Как я могу сделать эту концепцию композиционной? и поскольку моноиды - это такой простой способ добиться этого (мы хотим простого) спросить себя первым: как это понятие является моноидом?

Sidenote: К счастью, Haskell будет очень мешать вам расширять типы, поскольку это функциональный язык (без наследования). Но все же можно сделать тип для чего-то, другого типа для чего-то, а в третьем типе - оба типа в качестве полей. Если это для композиции - посмотрите, можете ли вы ее избежать.

Ответ 2

Хорошо, мне нравится математика. Но с практической точки зрения, какой смысл Моноида? Как это помогает нам лучше выражать идеи?

Это API. Простой. Для типов, которые поддерживают:

  • с нулевым элементом
  • с операцией добавления

Многие типы поддерживают эти операции. Таким образом, наличие имени для операций и API помогает нам более четко фиксировать этот факт.

API-интерфейсы

хороши, потому что они позволяют нам повторно использовать код и использовать концепции повторного использования. Значение лучше, более удобный код.

Ответ 3

Дело в том, что когда вы отмечаете Int как Product, вы выражаете намерение умножить целые числа. И пометив их как Sum, которые будут добавлены вместе.

Тогда вы можете использовать тот же mconcat для обоих. Это используется, например, в Foldable, где один foldMap выражает идею складывания над содержащей структурой структуры, комбинируя элементы в определенном моноидном виде.

Ответ 4

Очень простой пример foldMap. Просто подключив различные моноиды к этой единственной функции, вы можете вычислить:

  • first и last элемент,
  • sum или product элементов (из этого также их среднее и т.д.),
  • проверьте, если all элементы или any имеет заданное свойство,
  • вычислить максимальный или минимальный элемент,
  • отобразить элементы в коллекцию (например, списки sets, строки, Текст, ByteString или ByteString Builder) и объединить их вместе - все они моноиды.

Кроме того, моноиды являются составными: если a и b являются моноидами, то есть (a, b). Таким образом, вы можете легко вычислить несколько разных моноидальных значений за один проход (например, сумму и произведение при вычислении среднего количества элементов и т.д.).

И хотя вы можете делать все это без моноидов, используя foldr или foldl, это гораздо более громоздко, а также часто менее эффективно: например, если у вас есть сбалансированное двоичное дерево, и вы хотите найти его минимум и максимальный элемент, вы не можете эффективно работать с foldr (или оба с foldl), каждый из них всегда будет O (n) для одного из случаев, тогда как при использовании foldMap с соответствующими моноидами это будет O (log n) в обоих случаях.

И это была всего лишь одна функция foldMap! Есть много других интересных приложений. Чтобы дать один, exponentiation путем возведения в квадрат является эффективным способом вычисления полномочий. Но это фактически не связано с вычислительными полномочиями. Вы можете реализовать его для любого моноида, и если его <> - O (1), у вас есть эффективный способ вычисления n-раз x <> ... <> x. И вдруг вы можете сделать эффективную экспоненциальную матрицу и вычислить n-й номер Фибоначчи с помощью только O (log n) -множеств. См. times1p в полугруппе.

См. также Моноиды и деревья пальцев.