Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Haskell have pointers/references to record members?

I can create and reference relative pointers to struct members in C++ using the ::*, .*, and ->* syntax like :

char* fstab_t::*field = &fstab_t::fs_vfstype;
my_fstab.*field = ...

In Haskell, I can easily create temporary labels for record getters like :

(idxF_s,idxL_s) = swap_by_sign sgn (idxF,idxL) ;

Afaik, I cannot however then update records using these getters as labels like :

a { idxF_s = idxL_s b }

Is there an easy way to do this without coding for each record setter?

like image 974
Jeff Burdges Avatar asked Dec 09 '22 02:12

Jeff Burdges


1 Answers

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 . l1)

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.

like image 191
ehird Avatar answered Jan 29 '23 00:01

ehird