21

In haskell without lenses I can do things like :

data Item = Item { quantity :: Double, price ::Double }

cost :: Item -> Double
cost = (*) <$> quantity <*> price

If I use lenses instead how can I do the equivalent ? The best I can do is

cost = to $ (*) <$> (^. quantity) <*> (^. price)

Is there a better way ? (of course I want a getter or equivalent)

mb14
  • 22,276
  • 7
  • 60
  • 102
  • 4
    Shouldn't it be `cost = (*) <$> (^. quantity) <*> (^. price)`? – Aadit M Shah Jun 23 '15 at 13:00
  • 4
    You could always create two new operators: `f <$>. l = f <$> (^. l)` and `f <*>. l = f <*> (^. l)`. Then your expression becomes `cost = (*) <$>. quantity <*>. price`. – Aadit M Shah Jun 23 '15 at 13:13
  • 1
    It's not any shorter, but using `Control.Lens` you can do `cost = returnA &&& returnA >>> view (alongside quantity price) >>> uncurry (*)`. The `returnA &&& returnA` is equivalent to `\a -> (a, a)` but more generalized, `alongside quantity price :: Lens' (Item, Item) (Double, Double)`, then `uncurry (*)` is pretty self explanatory. This solution does not scale beyond 2 lenses, though. For that @AaditMShah's suggestion is better, but I would prefer `^$^` and `^*^` since the `^` symbol usually indicates a getter in `lens`, and if you use `view` instead of `^.` it'll have a more general type. – bheklilr Jun 23 '15 at 14:51
  • @AaditMShah yes of course. I updated it. – mb14 Jun 23 '15 at 15:17
  • @AaditMShah I also want a lens like which why I start with a `to`. – mb14 Jun 23 '15 at 15:19
  • 1
    You can't make `cost` a lens because a lens must have both a getter and a setter. However, `cost` only has a getter. To set the cost you need to update both `quantity` and/or `price`. Hence, there's no "correct" way to create a setter for `cost`. Therefore, `cost` cannot be a lens. It can only be a function of the type `Item -> Double` (i.e. a getter). – Aadit M Shah Jun 23 '15 at 15:33
  • 1
    That why i put `lens like`, not `lens` ;-). – mb14 Jun 23 '15 at 15:40
  • @bekhlir I think your solution just give a function `Item -> Double`. I'm looking for a getter. – mb14 Jun 23 '15 at 15:46
  • A variation on @bheklilr 's suggestion: `to (join (,)) . alongside quantity price . to (uncurry (*))` – duplode Jun 23 '15 at 20:38
  • 10
    Why are you posting answers as comments? – Bakuriu Jun 25 '15 at 08:45
  • @Bakuriu to not risk getting down voted I presume ;-) – mb14 Jul 30 '15 at 12:09
  • 3
    @Bakuriu My guess would be that no one believes their answer is better than mb14's original idea... – duplode Aug 01 '15 at 00:53
  • 2
    I just stumbled upon [this answer by @danidiaz](http://stackoverflow.com/a/26723075/2751851) which suggests [`Control.Lens.Reified`](http://hackage.haskell.org/package/lens-4.13/docs/Control-Lens-Reified.html): the wrappers for read-only optics there have the instances that you'd expect for a function, so you can do `runGetter ((*) <$> Getter quantity <*> Getter price)`. – duplode Sep 17 '15 at 03:47
  • @duplode seems promising, even though it's much shorter than the `to ... view` version. What's annyoing is this `to .. view` back and forth conversion is crying for an `iso` but I haven't figured out how to use it. – mb14 Sep 17 '15 at 16:55

1 Answers1

2

It just occurred to me that you don't need any special syntax to combine lenses. If you are creating lenses using Template Haskell (which you should be doing) then you already have primitive getters for each field, preceded by an underscore.

Hence, you could use the primitive getters to create your phantom cost getter as follows:

{-# LANGUAGE TemplateHaskell #-}

import Control.Lens

data Item = Item { _quantity :: Double
                 , _price    :: Double
                 }

$(makeLenses ''Item)

cost :: Getter Item Double
cost = to $ (*) <$> _quantity <*> _price

item :: Item
item = Item { _quantity = 2, _price = 5.0 }

main :: IO ()
main = print $ view cost item

However, if you don't have access to the primitive getters then you could define cost as:

cost :: Getter Item Double
cost = to $ (*) <$> view quantity <*> view price

Your fingers don't need to move too far away from the home row in order to type out view.

Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • I'm trying to combine lenses, because they might not correspond to record accessor, therefore using the primitive getters is out of question. However, I agree `view lens` is better `(^. lens)`. – mb14 Sep 13 '15 at 20:25