31

All I know is that one works and the other doesn't.

Context: I have one data structure F which contains a Data.Map.Map k S to another data structure S. My goal was to build a Lens that given an F and k would describe a field in S.

The difficulty is that the key k may not be present in the map. That's fine the function can wrap its return in Maybe. However I could not propagate a lens through a Maybe using at. After reading a lot of Stack Overflow answers, I came across this one.

It turns out that replacing at with ix solved my type problems if I also replaced (^.) with (^?).

Question: It seems like at and ix do the same thing, at least with regard to Map. Both take a key and give a 'Lens' to the value at that key. However, ix seems to play nice with the function composition operator (.). What is the difference between the two?


Off Topic Rant:

I like infix operators as much as the next guy but the Control.Lens package seems to have gone a little overboard. For a new user having some English names and a key somewhere would lower the learning curve. Due to the huge number of wrapper classes used in the Lens library it is particularly difficult to dig through the type signatures if you don't already know what is going on. My code is starting to look like Perl for heaven sake.

Community
  • 1
  • 1
John F. Miller
  • 26,961
  • 10
  • 71
  • 121
  • You can use `lens` without a single infix operator. Just about every operator is just a convenient alias for a non-operator name -- you don't have to use them (and I often don't). – shachaf Aug 24 '13 at 03:35
  • 5
    what you cannot do is try to read an understand the code others have written in, say, the libraries example folder. The process of copying some code and beating on it in GHCi is significantly harder in Lens then most other libraries. – John F. Miller Aug 24 '13 at 06:19
  • @shachaf that's not true: certain highly useful operators do not exist as words, namely `%=` (not so bad given it's use in `do` blocks) and `?~` (pretty bad, as it's really useful in pure code, requiring switching from `$` to `&` if you don't want to load up on parens). – spopejoy Mar 19 '16 at 01:46

1 Answers1

28

That at and ix are different is already noticable if you look at the available instances for the classes containing these functions:

  • instances of At: Map, IntMap, HashMap
  • instances of Ixed: [a], Map, ByteString, Text, and a lot more

All instances if At are also an instance of Ix, but not all instances of Ix are also an instance of At.

So what's the difference between them? At is for containers that allow inserting keys that are not present in the container. This is obviously possible for a Map, but not e.g. for a list. To still be able to index into a list and change items that are there, Ix doesn't allow creating new items, but just "does nothing" when you try to write to a key that is not there.

>>> Data.Map.fromList [('a', 1)] & at 'b' .~ Just 4 
fromList [('a',1),('b',4)] -- Inserts value 
>>> Data.Map.fromList [('a', 1)] & ix 'b' .~ 4  
fromList [('a',1)]          -- Does nothing because key is not present

(There is also a shortcut for a .~ Just b, a ?~ b)

Technically, this difference comes from the fact that ix is a Traversal whereas at is a Lens. And because at is a Lens that "returns" a Maybe Something, you cannot compose it with a lens that takes just a plain "Something". ix is a Traversal with 0 or 1 values, so you can compose ix just like any other traversal (just like you can write traverse . traverse). (^?) just takes the first value (head) of that traversal.

You can always derive ix from at:

ixAt = at . traverse

The same definition is already in lens, except it's using (<.) for composition to keep the index from at. (at and ix are both indexed lenses / traversals).

Off-topic: Most operators from lens also have infix names, you can find a (incomplete) table at: https://github.com/ekmett/lens/wiki/Operators

John F. Miller
  • 26,961
  • 10
  • 71
  • 121
bennofs
  • 11,873
  • 1
  • 38
  • 62
  • 3
    I think "quite different" is overstating it -- `ix k = at k . traverse` is a law, when both are implemented. `at` provides strictly more functionality than `ix` (but `ix` is implemented for more types). – shachaf Aug 24 '13 at 03:32
  • 1
    Thanks for the great answer; one thing I don't know, being new to Lens and a bit new to Haskell, what do you mean by "`ix` is a Traversal with 0 or 1 values"? I don't see what is meant by this in the type signature: `ix :: Index m -> Traversal' m (IxValue m)`. – bbarker Oct 18 '18 at 20:45
  • 1
    @bbarker a traversal is like a lens but with multiple values. If you update a traversal, all the values selected by it will be updated and you can use `toListOf` instead of `view` to retrieve every value selected by it. Now, `ix` is a special traversal because it never selects multiple values (at most one) but it sometimes selects no values (so it is not a lense since a lens must select exactly one target) – bennofs Oct 19 '18 at 15:11