How does Haskell's lens package handle fields that are also keywords?
Asked Answered
O

2

10

How does lens handle the case where a de-sugared field is a keyword? I seem to remember reading that something special is done, but I can't remember where I read it or what the name of the "lensed" accessor would end up as.

Consider the following:

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}

import           Control.Lens
import           Control.Monad.IO.Class (liftIO)
import           Data.Maybe
import           Data.Aeson
import           Data.Aeson.TH
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy    as L
import qualified Data.ByteString.Lazy.Char8 as LC8
import qualified Data.Text.Lazy.Encoding as TLE


data Typ = Typ {
    _fld1 :: BS.ByteString
  , _type :: Int
} deriving (Show)

$(deriveJSON tail ''Typ)
$(makeLenses ''Typ)


main = do
  print $ typ^.fld1
  print $ typ^.getType
  where
    jsonTyp = "{\"fld1\": \"Test\", \"type\": 1 }"
    typ'    = decode jsonTyp  :: Maybe Typ
    typ     = fromJust typ'
    getType :: Getter Typ Int
    getType = to _type

What would the _type accessor be called and how do I avoid having to implement getType here?

I had to bash this out on school of haskell because I don't have access to a proper dev environment here, but I figure it might be useful to others. I'll add an answer when I can drop into ghci and do a :browse (if that gives an answer), but in the mean time does anyone know?

Conclusion

Thanks guys, I'll use makeLensesWith along with a mapping of keywords to replacements as per Edward's suggestion.

Oralee answered 17/5, 2013 at 13:2 Comment(0)
B
12

It doesn't do anything special. The generated lens is named type and funnily enough, GHC appears to be totally cool with this. You can even use it if you use the fully qualified name:

{-# LANGUAGE TemplateHaskell #-}    
module Foo where

import Control.Lens

data Bar = Bar { _type :: String }
  deriving Show

$(makeLenses ''Bar)
> :l Foo
> :t type      -- Um...
<interactive>:1:1: parse error on input `type'
> :t Foo.type  -- Haha!
Foo.type
  :: (Functor f, Profunctor p) =>
     p String (f String) -> p Bar (f Bar)
> Bar "hello" ^. Foo.type
"hello"
Burris answered 17/5, 2013 at 15:16 Comment(1)
Note that GHC 7.10 is no longer cool with this. You cannot generate lenses with keyword names anymore. See this issue for an example.Sulky
N
6

I usually just pick a field name that is nearby. Use something like _ty instead of _type and then the resulting lens will be callable.

You can also use makeLensesWith to supply a custom name mangling function.

Needham answered 17/5, 2013 at 15:1 Comment(5)
Thanks Edward. The problem in this case is that the field names come from the json of a web service (and I'm autogenerating the data declarations)... I suppose I could take the same approach with the From|ToJSON derivation as well.Oralee
So what does lens end up doing in this case? It didn't throw an error in this case, does the _type just get dropped?Oralee
It'll make the lens with template haskell, since it constructs the syntax tree directly and doesn't get parsed that works fine. You just can't invoke it without qualification.Needham
Note: you can also just write the ty f (Bar a) = Bar <$> f a lens by hand, and export it.Needham
I have no idea why it took this long but I just noticed how similar lens construction and traverse construction is. Let alone that you explicitly state this similarity on the derivation page AND I've read Functor is to Lens as Applicative is to Biiplate AND it's pretty clear that they're related once you start using Traversals. The syntactic parity gets lost when you just use the TH shortcuts, I suppose.Scheer

© 2022 - 2024 — McMap. All rights reserved.