2015-05-10 2 views
2

Я нахожу все чаще и чаще делать что-то подобное ...haskell fmap. БПМЖ функция, функция над двумя функторов

У меня есть функция f :: IO [a], то я хочу, чтобы применить на ней функцию типа g :: a -> b, чтобы получить IO [b].

Долгий путь:

x <- f 
let y = fmap g x 

который я затем укорачивается до:

x <- f 
let y = g <$> x 

и в настоящее время я предпочитаю делать:

y <- fmap g <$> f 

, но затем с Functor законами, я что я могу сделать даже:

(<$$>) = fmap . fmap 
y <- g <$$> f 

И хотя я часто видел упоминания об этом fmap . fmap Я вижу, что у него нет специальной привязки в базовой библиотеке, хотя для меня это кажется очень частым идиомом. Я буквально мог использовать это повсюду! Так что теперь я задаюсь вопросом: неужели я недостаточно стараюсь писать чистый код, и именно поэтому я ударил этого более, чем более опытных программистов из haskell ... Или есть более элегантный способ добиться этого, что объясняет, почему эта идиома не используется другими много?

ответ

7

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

В этом случае, похоже, нет никакого реального смысла в том, что здесь есть два вхождения fmap.Тот факт, что вы корректируете результат действия ввода-вывода, не логически связан с тем фактом, что настройка происходит при сопоставлении списка. Если вы видите в коде код «fmap . fmap», это просто потому, что fmap - довольно распространенная функция.

Если вы пишете

y <- g <$$> x  -- or any other made-up operator name 

вы не только заставить читателя изучать незнакомый оператора, но как только они узнали, они должны расширить его y <- fmap g <$> x в любом случае, чтобы понять, что происходит, так как вы сгруппировали две несвязанные операции вместе.

Лучшая форма вашего y <- fmap g <$> x (или даже лучше, y <- map g <$> x), так как она вписывается в привычный шаблон var <- function <$> action.

(Кстати, вы упомянули законы Functor, но они ни в коей мере отношения к какому-либо из переписывания в вашем вопросе, который только равнозначен расширяющиеся определения.)

+0

Я согласен с вашим непопулярным мнением. – mb14

+0

Ваш аргумент, что 'IO' и' [] 'не связаны логически, имеет для меня большой смысл. Но считаете ли вы тогда, что, когда два функтора фактически логически связаны, добавление '(<$$>) = fmap.fmap' имеет смысл? Например, в 'toUpper <$$> [" abc "," cde "]' –

4

Ну, у вас есть Состав функторов. Наиболее распространенная реализация этой концепции –, хотя на самом деле не такая общая, как вам может понравиться – monad transformers.

http://hackage.haskell.org/package/transformers-0.4.3.0/docs/Control-Monad-Trans-List.html#v:ListT

f' :: ListT IO a 
f' = ListT f 

В том, что вы можете просто использовать fmap g, чтобы получить значение h' :: ListT IO b, что может с runListT быть оценены, как IO [b].

Независимо от того, стоит ли это обертывание и развертывание, зависит от того, сколько операций вы выполняете в строке, которые могут использовать один и тот же состав. Для некоторых приложений монадные трансформаторы просто потрясающие; в других случаях они просто делают материал более неуклюжим, чем он есть. fmap . fmap на самом деле не так уж плохо, не так ли? Часто я просто остаюсь с этим.

+0

Да, большую часть времени в моем использовании я согласен с fmap. fmap' является более прагматичным/разумным выбором по сравнению с монадными трансформаторами. Я просто чувствую себя очень искушенным сейчас, чтобы определить этот «<$$>» и начать использовать во всех моих проектах (ввод 'fmap. Fmap' или' fmap g <$> 'просто стыдно, если я могу получить оператора), но это раздражает, чтобы добавить один оператора, а не следовать соглашениям сообщества ... Вы бы написали 'fmap. fmap', если ваш код, если вы наткнулись на эту ситуацию? –

+2

Ну, я бы не назвал это '' <$$> '] (http://hayoo.fh-wedel.de/?query=%3C%24%24%3E) во всяком случае. - Да, я бы набрал 'fmap. fmap' в соответствующей ситуации или 'fmap f <$>'. Возможно, это не самая элегантная вещь, но она определенно работает и очень читаема. Если эта идиома накапливается, я бы переключился на состав трансформатора/функтора. – leftaroundabout

4

Функторы (и их применения) составляют, а the Compose newtype определяет функтор и аппликативные экземпляры для композиции из двух функторов. Это означает, что вы можете обернуть свой IO [a] в Compose IO [] a и применить fmap, например.

import Data.Functor.Compose 
getCompose $ fmap g $ Compose f 
+0

Это хорошо, я не знал, что 'Compose' также был в пакете трансформаторов. – leftaroundabout

+1

, но это более подробно, чем 'fmap. fmap', в чем преимущество этого подхода? –

+1

@EmmanuelTouzery - Это абстрагирует отмену 'fmap',' <*> 'и т. Д. Над внутренним функтором/аппликативным (' [] 'в этом случае). Если у вас есть только одна функция для подъема, то это не будет стоить того, но если у вас есть несколько, то это возможно. – Lee

2

я окажусь делать это часто также и находят это действительно запутанным. Я нашел liftM . map более удобным для чтения, чем fmap . fmap.

Смежные вопросы