A getter and setter bundled together in a first-class value is referred to as a lens. There are quite a few packages for doing this; the most popular are data-lens and fclabels. This previous SO question is a good introduction.
Both of those libraries support deriving lenses from record definitions using Template Haskell (with data-lens, it's provided as an additional package for portability). Your example would be expressed as (using data-lens syntax):
setL idxF_s (b ^. idL_s) a
(or equivalently: idxF_s ^= (b ^. idL_s) $ a
)
You can, of course, transform lenses in a generic way by transforming their getter and setter together:
-- I don't know what swap_by_sign is supposed to do.
negateLens :: (Num b) => Lens a b -> Lens a b
negateLens l = lens get set
where
get = negate . getL l
set = setL l . negate
(or equivalently: negateLens l = iso negate negate . l
1)
In general, I would recommend using lenses whenever you have to deal with any kind of non-trivial record handling; not only do they vastly simplify pure transformation of records, but both packages contain convenience functions for accessing and modifying a state monad's state using lenses, which is incredibly useful. (For data-lens, you'll want to use the data-lens-fd package to use these convenience functions in any MonadState
; again, they're in a separate package for portability.)
1 When using either package, you should start your modules with:
import Prelude hiding (id, (.))
import Control.Category
This is because they use generalised forms of the Prelude's id
and (.)
functions — id
can be used as the lens from any value to itself (not all that useful, admittedly), and (.)
is used to compose lenses (e.g. getL (fieldA . fieldB) a
is the same as getL fieldA . getL fieldB $ a
). The shorter negateLens
definition uses this.