3

Suppose I have a type like this:

data Stock = Stock {
               _stockSymbol :: String,
               _stockFairValue :: Float,
               _stockBuyAt :: Float,
               _stockCurrentPrice :: Float
             } |
             Etf {
               _etfSymbol :: String,
               _etfFairValue :: Float,
               _etfBuyAt :: Float,
               _etfCurrentPrice :: Float
             } deriving (Eq)

Stock and Etf both have the same fields. Now I want to access the symbol for one of them:

item ^. symbol -- don't care if stock or etf

I can do this with a typeclass, but I'm wondering if the lens package can build this lens for my automatically? I have looked at the makeFields function, but it seems that works if I have the constructors defined separately:

data Stock = Stock { ... }
data Etf   = Etf { ... }

Is there any way to do this while keeping them under the same type?

Edit: This works:

makeLensesFor [("_stockSymbol", "symbol"),
               ("_etfSymbol", "symbol"),
               ("_stockFairValue", "fairValue"),
               ("_etfFairValue", "fairValue"),
               ("_stockBuyAt", "buyAt"),
               ("_etfBuyAt", "buyAt"),
               ("_stockCurrentPrice", "currentPrice"),
               ("_etfCurrentPrice", "currentPrice")
               ] ''Stock

Not sure if there's a built-in way where I don't have to write the fields out.

crockeea
  • 21,651
  • 10
  • 48
  • 101
Vlad the Impala
  • 15,572
  • 16
  • 81
  • 124
  • 3
    Could you instead define your data type as `Stock { isEtf :: Bool, _stockSymbol :: String, ... }`? If all the fields are the same and the difference is the constructor then just make the constructor a field. I'm going to guess that the lens library doesn't support what you're trying to do since multi-constructor records are generally discouraged. You could try using the same field names for both constructors, though, that might work. – bheklilr Mar 27 '15 at 20:58
  • @bheklilr I have a working example, see my edit. I could use a field like you said, but having data constructors seemed nicer. Why does the lens library discourage multi-constructor records? – Vlad the Impala Mar 27 '15 at 21:00
  • 1
    The haskell community in general discourages them, they can be dangerous. For example, what if I called `_etfBuyAt (Stock "" 0 0 0)`? You lose safety by introducing partial functions and that can lead to crashing your application. – bheklilr Mar 27 '15 at 21:03
  • Ah, makes sense. Thanks! Followup question: suppose I split this into two data types, `Stock` and `Etf`, but I can use the same functions on both thanks to lenses. How do I write a function that can take either one? Like `getSymbol :: EitherStockOrEtf -> String` – Vlad the Impala Mar 27 '15 at 21:17
  • @VladTheImpala Why do you want to do that? You're making life difficult for yourself – Benjamin Hodgson Mar 28 '15 at 00:09
  • @bheklilr I think `lens` actually decreases the problems with multi-constructor records, because it makes sure to only derive a `Traversal` when a field is not always defined. – Ørjan Johansen Mar 28 '15 at 08:13

1 Answers1

2

Not to disagree with bheklilr's comment, but you could just do this:

data Stock =
         Stock {
           _symbol :: String,
           _fairValue :: Float,
           _buyAt :: Float,
           _currentPrice :: Float
         } |
         Etf {
           _symbol :: String,
           _fairValue :: Float,
           _buyAt :: Float,
           _currentPrice :: Float
         } deriving (Eq)
$(makeLenses ''Stock)
Reid Barton
  • 14,951
  • 3
  • 39
  • 49
  • Oh I had no idea multiple data constructors could share the same field name. Thanks! – Vlad the Impala Mar 27 '15 at 22:07
  • 1
    This does use the new (as of 7.10) [OverloadedRecords](http://stackoverflow.com/a/22019416/925978) extension, does it not? – crockeea Mar 27 '15 at 22:10
  • 2
    @Eric: Nope, Haskell 98. ("A data declaration may use the same field label in multiple constructors as long as the typing of the field is the same in all cases after type synonym expansion.") You might be mislead by the OP writing "data types" when they meant "data constructors". – Reid Barton Mar 27 '15 at 22:14