<div dir="ltr">Tikhon's approach is interesting and seems potentially useful. Li-yao mentioned generic-lens, so I came up with an approach similar to Tikhon's but using generic-lens instead of TH, and prepending 'f' to the field name to get the lens name for additional safety from name collision (though still not absolute safety). In the example, it would be even better if I could use '#fXx' instead of '#fxx' (camel-casing the first letter of 'xx'), but type-level letter-case conversion seems quite troublesome to do.<div><br></div><div>Any comments?<br></div><div><div><br></div><div>Library code (Adapted from <a href="https://raw.githubusercontent.com/kcsongor/generic-lens/master/src/Data/Generics/Labels.hs.">https://raw.githubusercontent.com/kcsongor/generic-lens/master/src/Data/Generics/Labels.hs.</a>):</div><div><div style="color:rgb(147,161,161);background-color:rgb(0,33,41);font-family:"Input Mono","Droid Sans Mono","Courier New",monospace,"Droid Sans Fallback";font-size:13px;line-height:18px;white-space:pre"><div><span style="color:rgb(181,137,0)">{-# </span><span style="color:rgb(133,153,0)">LANGUAGE</span><span style="color:rgb(181,137,0)"> DataKinds, FlexibleInstances, MultiParamTypeClasses, ScopedTypeVariables, TypeApplications</span></div><div><span style="color:rgb(181,137,0)"> , TypeFamilies, UndecidableInstances #-}</span></div><div><span style="color:rgb(181,137,0)">{-# </span><span style="color:rgb(133,153,0)">OPTIONS_GHC</span><span style="color:rgb(181,137,0)"> -Wno-orphans #-}</span></div><br><div><span style="color:rgb(133,153,0)">module</span> FieldLabel <span style="color:rgb(133,153,0)">where</span></div><br><div><span style="color:rgb(133,153,0)">import</span> GHC.OverloadedLabels</div><div><span style="color:rgb(133,153,0)">import</span> GHC.TypeLits (<span style="font-weight:bold">AppendSymbol</span>, <span style="font-weight:bold">KnownSymbol</span>)</div><div><span style="color:rgb(133,153,0)">import</span> Data.Generics.Product.Fields (<span style="font-weight:bold">HasField</span>, <span style="color:rgb(38,139,210)">field</span>)</div><br><div><span style="color:rgb(133,153,0)">instance</span> ( <span style="font-weight:bold">KnownSymbol</span> <span style="color:rgb(38,139,210)">lfield</span>, <span style="color:rgb(38,139,210)">lfield</span> ~ <span style="font-weight:bold">AppendSymbol</span> "<span style="color:rgb(38,139,210)">f</span>" <span style="color:rgb(38,139,210)">field</span></div><div> , <span style="font-weight:bold">HasField</span> <span style="color:rgb(38,139,210)">field</span> <span style="color:rgb(38,139,210)">s</span> <span style="color:rgb(38,139,210)">t</span> <span style="color:rgb(38,139,210)">a</span> <span style="color:rgb(38,139,210)">b</span>, <span style="font-weight:bold">Functor</span> <span style="color:rgb(38,139,210)">f</span>, <span style="color:rgb(38,139,210)">sft</span> ~ (<span style="color:rgb(38,139,210)">s</span> <span style="color:rgb(133,153,0)">-></span> <span style="color:rgb(38,139,210)">f</span> <span style="color:rgb(38,139,210)">t</span>) ) <span style="color:rgb(133,153,0)">=></span></div><div> <span style="color:rgb(203,75,22)">IsLabel</span> lfield ((a <span style="color:rgb(133,153,0)">-></span> f b) <span style="color:rgb(133,153,0)">-></span> sft) <span style="color:rgb(133,153,0)">where</span> fromLabel <span style="color:rgb(133,153,0)">=</span> field <span style="color:rgb(133,153,0)">@</span>field</div><br></div></div><div><br></div><div>User code:</div><div><div style="color:rgb(147,161,161);background-color:rgb(0,33,41);font-family:"Input Mono","Droid Sans Mono","Courier New",monospace,"Droid Sans Fallback";font-size:13px;line-height:18px;white-space:pre"><div><span style="color:rgb(181,137,0)">{-# </span><span style="color:rgb(133,153,0)">LANGUAGE</span><span style="color:rgb(181,137,0)"> DeriveGeneric, OverloadedLabels #-}</span></div><br><div><span style="color:rgb(133,153,0)">import</span> Control.Lens (<span style="color:rgb(38,139,210)">(^.)</span>)</div><div><span style="color:rgb(133,153,0)">import</span> GHC.Generics</div><div><span style="color:rgb(133,153,0)">import</span> FieldLabel ()</div><br><div><span style="color:rgb(133,153,0)">data</span> <span style="font-weight:bold">Foo</span> <span style="color:rgb(133,153,0)">=</span> <span style="color:rgb(203,75,22)">Foo</span> { xx <span style="color:rgb(133,153,0)">::</span> <span style="font-weight:bold">Int</span>, yy <span style="color:rgb(133,153,0)">::</span> <span style="font-weight:bold">Char</span> } <span style="color:rgb(133,153,0)">deriving</span> (<span style="color:rgb(108,113,196)">Generic</span>, <span style="color:rgb(108,113,196)">Show</span>)</div><br><div><span style="color:rgb(38,139,210)">foo</span> <span style="color:rgb(133,153,0)">::</span> <span style="font-weight:bold">Foo</span></div><div>foo <span style="color:rgb(133,153,0)">=</span> <span style="color:rgb(203,75,22)">Foo</span> <span style="color:rgb(211,54,130)">1</span> <span style="color:rgb(42,161,152)">'a'</span></div><br><div><span style="color:rgb(38,139,210)">main</span> <span style="color:rgb(133,153,0)">::</span> <span style="font-weight:bold">IO</span> ()</div><div>main <span style="color:rgb(133,153,0)">=</span> print <span style="color:rgb(133,153,0)">$</span> foo <span style="color:rgb(133,153,0)">^.</span> <span style="color:rgb(133,153,0)">#</span>fxx</div><br></div></div></div></div><div class="gmail_extra"><br><div class="gmail_quote">On Sun, Dec 24, 2017 at 3:47 AM, Tikhon Jelvis <span dir="ltr"><<a href="mailto:tikhon@jelv.is" target="_blank">tikhon@jelv.is</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr"><div class="gmail_extra">This is a real pain point with records in Haskell<font color="#3c4043" face="Roboto, arial, sans-serif">.</font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif"><br></font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif">The fundamental problem is that unlike most languages with records or object, field names are treated as normal identifiers in Haskell. Other languages make fields special</font><span style="color:rgb(60,64,67);font-family:Roboto,arial,sans-serif">—you can only use them with the . operator or in other select contexts. The advantage is that you can do things like `a.author == author`; the disadvantage is that fields become a second-class citizen.</span></div><div class="gmail_extra"><span style="color:rgb(60,64,67);font-family:Roboto,arial,sans-serif"><br></span></div><div class="gmail_extra"><span style="color:rgb(60,64,67);font-family:Roboto,arial,sans-serif">At work, we have a solution that's really nice to use built on top of DuplicateRecordFields and OverloadedLabels. Our approach follows the ideas in the OverloadedRecordFields proposal but with a lens flavor</span><span style="color:rgb(60,64,67);font-family:Roboto,arial,sans-serif">—very similar to the overloaded-records[1] package. (We don't use that package directly because I wrote our own version before I knew about it and I like the ergonomics of our internal version a bit more.)</span></div><div class="gmail_extra"><span style="color:rgb(60,64,67);font-family:Roboto,arial,sans-serif"><br></span></div><div class="gmail_extra"><span style="color:rgb(60,64,67);font-family:Roboto,arial,sans-serif">We have a couple of typeclasses for reading and writing fields:</span></div><div class="gmail_extra"><span style="color:rgb(60,64,67);font-family:Roboto,arial,sans-serif"><br></span></div><div class="gmail_extra"><span style="color:rgb(60,64,67);font-family:Roboto,arial,sans-serif">class HasField (field :: Symbol) s a | s -> a where</span></div><div class="gmail_extra"><span style="color:rgb(60,64,67);font-family:Roboto,arial,sans-serif"> getField :: s -> a</span></div><div class="gmail_extra"><span style="color:rgb(60,64,67);font-family:Roboto,arial,sans-serif"><br></span></div><div class="gmail_extra"><span style="color:rgb(60,64,67);font-family:Roboto,arial,sans-serif">class UpdatesField (field :: Symbol) s t b | name t -> b, name s b -> t where</span></div><div class="gmail_extra"><span style="color:rgb(60,64,67);font-family:Roboto,arial,sans-serif"> updateField :: s -> b -> t</span></div><div class="gmail_extra"><span style="color:rgb(60,64,67);font-family:Roboto,arial,sans-serif"><br></span></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif">A record field can be both read and updated:</font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif"><br></font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif">type Field field s t a b = (HasField field s a, UpdatesField field name s t b)</font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif"><br></font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif">field :: forall (name :: Symbol) s t a b. Field name s t a b => Lens s t a b</font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif">field = lens (getField @name) (updateField @name)</font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif"><br></font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif">Then we have some Template Haskell for generating instances of these classes. Here's a contrived example:</font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif"><br></font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif">data Foo a = Foo { bar :: [a] }</font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif"><br></font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif">record ''Foo</font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif"><br></font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif">which generates:</font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif"><br></font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif">instance HasField "bar" (Foo a) a where</font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif"> getField = bar</font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif"><br></font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif">instance UpdatesField "bar" (Foo a) (Foo b) b where</font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif"> updateField foo bar' = foo { bar = bar' }</font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif"><br></font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif">Given these, we can already write code looking up fields as lenses:</font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif"><br></font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif">> Foo [1,2,3] ^. field @"bar"</font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif">[1,2,3]</font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif"><br></font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif">Now fields aren't normal identifiers any more, the names can be shared over different records (with DuplicateRecordFields) and you can write functions polymorphic over any record with a given field.</font></div><div class="gmail_extra"><font color="#3c4043" face="Roboto, arial, sans-serif"><br></font></div><div class="gmail_extra">The names and details here are a bit different, but I believe this is otherwise exactly what overloaded-records gives you. You could also replace the TH to generate instances with generics in the style of the generic-lens library.</div><div class="gmail_extra"><br></div><div class="gmail_extra">However, the field @"bar" is painfully verbose. We solve this using OverloadedLabels and a somewhat shady orphan instance for IsLabel:</div><div class="gmail_extra"><br></div><div class="gmail_extra"><div class="gmail_extra">instance (Functor f, Field name s t a b, a' ~ (a -> f b), b' ~ (s -> f t)) => IsLabel name (a' -> b') where</div><div class="gmail_extra"> fromLabel = field @name</div><div class="gmail_extra"><br></div><div class="gmail_extra">The details are a bit fiddly, but this is what we need to make type inference work correctly. This lets us replace field @"name" with #name:</div><div class="gmail_extra"><br></div><div class="gmail_extra">> Foo [1,2,3] ^. #bar</div><div class="gmail_extra">[1,2,3]</div><div class="gmail_extra">> Foo [1,2,3] & #bar . each %~ show</div><div class="gmail_extra">Foo { bar = ["1","2","3"] }</div><div class="gmail_extra"><br></div><div class="gmail_extra">The downside is that this is an orphan instance for IsLabel for *all functions*. You would not want to use this in a library but it's fine in an executable as long as you don't mind potentially needing to reword things if a similar IsLabel instance is added to base. (A risk I'm willing to take for better syntax :))</div><div class="gmail_extra"><br></div><div class="gmail_extra">Apart from that (somewhat serious) downside, the final result is pretty much perfect: fields are first-class citizens (as lenses) and are not in the same scope as identifiers. We've been using this extensively throughout our whole project and it's been perfect—perhaps surprisingly, we haven't run into any issues with type inference or type error messages (beyond what you normally get with lens).</div><div class="gmail_extra"><br></div><div class="gmail_extra">With this addition, Haskell records went from being a real blemish on the language to being the best I've ever used. The orphan instance is a definite red flag and you should absolutely *not* have that instance in a library, but if you're working on a standalone executable or some extensive internal code, I think it's absolutely worth it.</div><div class="gmail_extra"><br></div><div class="gmail_extra">[1]: <a href="https://hackage.haskell.org/package/overloaded-records" target="_blank">https://hackage.haskell.<wbr>org/package/overloaded-records</a></div><div class="gmail_extra"><br></div><div class="gmail_extra">[2]: <a href="https://hackage.haskell.org/package/generic-lens" target="_blank">https://hackage.haskell.<wbr>org/package/generic-lens</a></div><div class="gmail_extra"><br></div></div><div><div class="h5"><div class="gmail_extra"><br><div class="gmail_quote">On Sat, Dec 23, 2017 at 6:41 AM, Li-yao Xia <span dir="ltr"><<a href="mailto:lysxia@gmail.com" target="_blank">lysxia@gmail.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">I don't think "authorL" hurts readability. It just seems the logical choice if "author" is already taken.<br>
<br>
Have you seen generic-lens? The lens for the "author" field is (field @"author") so there is some added noise compared to "authorL", but it can be used as a TH-free alternative to makeClassy.<br>
<br>
type Field name a = forall s. HasField name s s a a => Lens s s a a<br>
<br>
authorL :: Field "author" Author<br>
authorL = field @"author"<br>
<br>
Cheers,<br>
Li-yao<div><div class="m_-6151618194568914291gmail-m_-693781337687721066gmail-h5"><br>
<br>
On 12/23/2017 08:36 AM, ☂Josh Chia (謝任中) wrote:<br>
</div></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div><div class="m_-6151618194568914291gmail-m_-693781337687721066gmail-h5">
Quite often, I need to use record types like this:<br>
<br>
data Whole1 = Whole1 { part :: Part, ... }<br>
data Whole2 = Whole2 { part :: Part, ... }<br>
<br>
Where Whole1 & Whole2 are types of things that have a Part and some other things. E.g. a Book has an Author, a Title, etc and so does an Article.<br>
<br>
The problem is that I'm not actually allowed to use the same name (author/part) in two different record types. Some people use lens to solve this. You can have a lens called 'author' for dealing with the Author in both Book and Article (e.g. using makeClassy).<br>
<br>
That's fine, but there's yet another problem. Let's say I have a function that takes an Author and a [Library] and returns all the Libraries that have Books or Articles matching the Author. So:<br>
<br>
findAuthorLibraries :: Author -> [Library] -> [Library]<br>
findAuthorLibraries author libraries = ...<br>
<br>
But I already have a lens called 'author' and ghc will complain about shadowing. So, to avoid shadowing, should I use 'theAuthor' instead of 'author' for the function argument? Or, should I name the lens 'authorLens', 'authorL' or 'lAuthor' instead of 'author'? Prefixing with 'the' is quite unreadable because whether or not an argument has that prefix depends on whether there's a lens with a conflicting name so it adds noise to the code. Adding a 'Lens' prefix to the 'author' lens also seems quite an overbearing eyesore because for consistency I would have to use the prefix for all my field-accessing lenses.<br>
<br>
Maybe I should use Lens.Control.TH.makeClassy and then define:<br>
<br>
findAuthorLibraries :: HasAuthor a => a -> [Library] -> [Library]<br>
findAuthorLibraries hasAuthor libraries = ...<br>
<br>
But that may be making my function more complicated and general than I want, affecting readability, simplicity, compilation time and maybe even performance.<br>
<br>
In summary, I find that there are ways around the problem but they really affect readability.<br>
<br>
I could also disable the warning about shadowing but that seems pretty dangerous. It may be OK to disable the warning for the specific cases where a function argument shadows something from the topmost scope, but GHC does not allow such selective disabling of that warning.<br>
<br>
In a code base that deals mainly with concrete business logic, this problem probably crops up more than in a code base that deals mainly with more abstract things.<br>
<br>
What do people do to address this problem? Any recommendations or best practices?<br>
<br>
Josh<br>
<br>
<br></div></div><span class="m_-6151618194568914291gmail-m_-693781337687721066gmail-">
______________________________<wbr>_________________<br>
Haskell-Cafe mailing list<br>
To (un)subscribe, modify options or view archives go to:<br>
<a href="http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe" rel="noreferrer" target="_blank">http://mail.haskell.org/cgi-bi<wbr>n/mailman/listinfo/haskell-caf<wbr>e</a><br>
Only members subscribed via the mailman list are allowed to post.<br>
<br>
</span></blockquote><div class="m_-6151618194568914291gmail-m_-693781337687721066gmail-HOEnZb"><div class="m_-6151618194568914291gmail-m_-693781337687721066gmail-h5">
______________________________<wbr>_________________<br>
Haskell-Cafe mailing list<br>
To (un)subscribe, modify options or view archives go to:<br>
<a href="http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe" rel="noreferrer" target="_blank">http://mail.haskell.org/cgi-bi<wbr>n/mailman/listinfo/haskell-caf<wbr>e</a><br>
Only members subscribed via the mailman list are allowed to post.</div></div></blockquote></div><br></div></div></div></div>
</blockquote></div><br></div>