Is it reasonable to mix Control.Lens operations with the ones provided by the "standard library"?

Consider the following fragment using qualified Data.Map as Map:

getUniqueIdent :: String -> State (Map.Map String Int) (Maybe String)
getUniqueIdent typeName = do indices <- StM.get
                             let identifiers = Map.lookup typeName typeNameMap
                             let uniqueIndex = Map.lookup typeName indices
                             StM.modify $ over (at typeName) (fmap succ)
                             let uniqueIdent = (^?) <$> identifiers <*> 
                                                        (element <$> uniqueIndex)
                             return $ join uniqueIdent

For example, I do use both the Map.lookup and Lens.at and also the (^?) as a safe alternative for (!!).

Should I just stick to lenses "everywhere" (at least in spatially local code)?

  • \$\begingroup\$ Please state the purpose of your code in the title, rather than your concern — see How to Ask. As the question stands, it's unclear what the purpose of the code is, and therefore your question is rather hypothetical. \$\endgroup\$ May 5 '15 at 8:10
  • \$\begingroup\$ @200_success Thanks! I wonder about the purpose of the code though - would it really help to state it in the title if the question can be applied in broader sense; that is, mixing two different "approaches / libraries" based purely on convenience of the code author? \$\endgroup\$ May 5 '15 at 9:16
  • \$\begingroup\$ We prefer titles describing the code; otherwise half the site would be "Too many if statements?" and the other half would be "Please help speed up my code." You could try a hybrid title like "Indentation control function that inconsistently uses Lens operations". \$\endgroup\$ May 5 '15 at 16:17

I wouldn't say there's anything like a proscription against doing so, but as a non-user of lens I can't say it helps me understand what's going on. I'm also not sure that your data structure is complex enough to warrant using lens in this case, functions out of base tend to produce a pretty concise solution on their own when properly deployed.

I haven't tested this, but I think it's equivalent.

import Control.Monad.State
import Data.Map
import Safe (atMay) -- "safe".Safe.atMay :: [a] -> Int -> Maybe a

getUniqueIdent :: String -> State (Map String Int) (Maybe String)
getUniqueIdent typeName =
  do indices <- get
     modify $ adjust succ typeName
     return $ do identifiers <- Data.Map.lookup typeName typeNameMap
                 uniqueIndex <- Data.Map.lookup typeName indices
                 identifiers `atMay` uniqueIndex
  • \$\begingroup\$ I like the Safe library thing, certainly more explicit than hacking away with Lenses. The code is almost equivalent, but lookup returns Maybe a, so atMay would need to lifted - I think. \$\endgroup\$ May 5 '15 at 9:20
  • \$\begingroup\$ The inner do-block is using the Monad Maybe instance though, so identifiers :: [String], uniqueIndex :: Int, and atMay is effectively a monadic action as it returns Maybe a. The result gets wrapped in a return in the Monad State instance do-block, which is what gives you the right return type overall. I think it works as is, but my internal compiler (The Basically Brainy Haskell Compilation System, version 0.0.0-prealpha9627) has been known to be faulty. \$\endgroup\$
    – bisserlis
    May 5 '15 at 17:08
  • \$\begingroup\$ ah, you actually bind the lookup call with <-; it should strip the Maybe, yes, my bad \$\endgroup\$ May 5 '15 at 17:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.