Что предпочтительнее среди liftM, lilftA и т.д. - программирование
Подтвердить что ты не робот

Что предпочтительнее среди liftM, lilftA и т.д.

В последнее время я писал код FFI, который возвращает структуру данных в монаде IO. Например:

peek p = Vec3 <$> (#peek aiVector3D, x) p
              <*> (#peek aiVector3D, y) p
              <*> (#peek aiVector3D, z) p

Теперь я могу представить четыре замечательных способа написать этот код, все тесно связанные:

peek p = Vec3 <$> io1 <*> io2 <*> io3
peek p = liftA3 Vec3 io1 io2 io3
peek p = return Vec3 `ap` io1 `ap` io2 `ap` io3
peek p = liftM3 Vec3 io1 io2 io3

Обратите внимание, что я спрашиваю о монадическом коде, который не требует ничего, кроме Applicative. Каков предпочтительный способ написать этот код? Должен ли я использовать Applicative, чтобы подчеркнуть, что делает код, или я должен использовать Monad, потому что он может (?) Иметь оптимизации над Applicative?

Вопрос немного усложняется тем фактом, что есть только [liftA..liftA3] и [liftM..liftM5], но у меня есть несколько записей с более чем тремя или пятью членами, поэтому, если я решаю пойти с lift{A,M}, я теряю некоторую согласованность, потому что Мне пришлось бы использовать другой метод для больших записей.

4b9b3361

Ответ 1

Первое, что нужно помнить, это немного сложнее, чем это должно быть - любой экземпляр Monad должен иметь связанный экземпляр Applicative, так что функции liftM и liftA совпадают. Таким образом, здесь два руководства:

  • Если вы пишете общую функцию для любого Monad, используйте liftM & co. чтобы избежать несовместимости с другими функциями, которые имеют только ограничение Monad.

  • Если вы работаете с конкретным экземпляром Monad, который, как вы знаете, имеет сопровождающий экземпляр Applicative, используйте операторы Applicative последовательно для любого определения или подвыражения, где вам не нужны операции Monad, но не смешивайте их бесцельно.

Должен ли я использовать Applicative, чтобы подчеркнуть, что делает код, или я должен использовать Monad, потому что он может (?) иметь оптимизации по сравнению с Applicative?

В общем, если есть разница, это будет наоборот. Applicative поддерживает только статическую "структуру" вычисления, тогда как Monad разрешает встроенный поток управления. Рассмотрим списки, например - с помощью Applicative, все, что вы можете сделать, это сгенерировать все возможные комбинации и преобразовать каждый из них - количество элементов результата определяется полностью количеством элементов на каждом входе. С помощью Monad вы можете создавать разные количества элементов на каждом шаге на основе элементов ввода, позволяя вам произвольно фильтровать или расширять.

Более ярким примером является экземпляр Applicative и Monad на основе бесконечных потоков zipping - Applicative может просто закрепить их вместе очевидным образом, тогда как Monad должен пересчитать множество вещей, которые он затем отбрасывает.

Итак, последняя проблема - liftA2 f x y vs. f <$> x <*> y или эквиваленты Monad. Мой совет здесь был бы следующими рекомендациями:

  • Если вы все равно пишете любой аргумент, используйте форму infix, потому что ее легче читать для больших выражений.
  • Если вы просто снимаете существующую функцию, используйте foo = liftA2 bar, а не foo x y = bar <$> x <*> y - она ​​короче и более четко выражает то, что вы делаете.

И, наконец, по вопросу о непротиворечивости нет причин, по которым вы не могли бы просто определить свой собственный liftA4 и т.д., если они вам понадобятся.