Using field selectors in a type constructor

Tom Pledger Tom.Pledger at peace.com
Wed Oct 15 16:03:54 EDT 2003


Graham Klyne writes:
 :
 | What I'd really like to do is assign it to field vbMap, and reference that
 | from the definition of vbEnum, but I can't figure out if there's a way
 | to do so.  Writing this:
 | [[
 | joinVarBindings vb1 vb2
 |      | vbNull vb1 = vb2
 |      | vbNull vb2 = vb1
 |      | otherwise  = VarBinding
 |          { vbMap  = head . filter isJust . flist [ vbMap vb1, vbMap vb2 ]
 |          , vbEnum = map (\v -> (v,fromJust (vbMap v))) $
 | --                             error here  ^^^^^^^
 |                     boundVars vb1 `union` boundVars vb2
 |          , vbNull = False
 |          }
 | ]]
 | Results in a fairly obvious type error: I'd need to have a way to
 | say that vbMap is applied to the value under construction.

That's right.  Field names don't come into scope in a record
expression.  In this sense, they're unlike the top level names in a
let or where.

 | Experience with Java would suggest maybe something like this:
 | [[
 |          , vbEnum = map (\v -> (v,fromJust (vbMap this v))) $
 | ]]
 | but of course Haskell isn't Java.
 | 
 | Does Haskell provide a way to do this, other than the use of a 'where'
 | clause to factor out the common subexpression, as in my working code?

Field names do continue to act as selector functions, so the trick is
to come up with something to take the place of the Java-style 'this',
i.e. to refer back to the same record.

If you're willing to introduce *one* more variable in a let or where,
try:

    joinVarBindings vb1 vb2
        | vbNull vb1 = vb2
        | vbNull vb2 = vb1
        | otherwise  = this
        where
            this = VarBinding
             { vbMap  = head . filter isJust . flist [ vbMap vb1, vbMap vb2 ]
             , vbEnum = map (\v -> (v,fromJust (vbMap this v))) $
                        boundVars vb1 `union` boundVars vb2
             , vbNull = False
             }

Otherwise your only way of referring to the (or an equivalent) record
is joinVarBindings itself:

    joinVarBindings vb1 vb2
        | vbNull vb1 = vb2
        | vbNull vb2 = vb1
        | otherwise  = VarBinding
             { vbMap  = head . filter isJust . flist [ vbMap vb1, vbMap vb2 ]
             , vbEnum = map (\v -> (v,fromJust (vbMap (joinVarBindings vb1 vb2)
                                                      v))) $
                        boundVars vb1 `union` boundVars vb2
             , vbNull = False
             }

I dimly remember reading that Hugs optimises the latter to the former,
recognising when a subexpression exactly matches the head of the
alternative (in this case 'joinVarBindings vb1 vb2').

- Tom



More information about the Haskell-Cafe mailing list