[Haskell-cafe] use of modules to save typing

Michael Mossey mpm at alumni.caltech.edu
Thu Jul 8 04:08:21 EDT 2010


I'm fairly beginnerish to Haskell, and come from OO. I have a complaint 
about Haskell, but I think I found a good solution. Any suggestions welcome.

I have RSI and like to minimize typing. The use of classes as name 
spaces helps to do that. Also I can use some Emacs abbreviation magic 
easily with OO and not so easily with Haskell. I'll explain in a second.

In Haskell, when defining data for complex programs I like to use named 
fields to allow for changing data definitions without having to change 
all code. But named fields are top-level functions (I think). They must 
be chosen not to clash.

My habit has been to prefix them with the name of the constructor. So in 
a program for playing back musical documents that needs to track some 
state, we have:

data PlayState = PlayState
                  { playState_cursor :: Int
                  , playState_verts :: [Loc]
                  , playState_len :: Int
                  , playState_doc :: MusDoc
                  }

Note all these "playState_" prefixes. Lots of typing, which is not good.

In OO, you could type

    x.cursor()

In Haskell you have to type

    playState_cursor x

which also, I feel, is harder to read.

Now suppose I want to use PlayState with a State monad.

-- Increment the cursor.
incrCursor :: State PlayState ()
incrCursor =
   cur <- gets playState_cursor
   len <- gets playState_len
   let newCur = min (cur+1) (len-1)
   p <- get
   put $ p {playState_cursor = newCur}

Okay, I'm sorry, that is just a lot of typing for what it is doing. Not 
good for people with RSI, and not all that readable.

I could define a function to make modifying the state a little easier.

playState_update_cursor :: Int -> PlayState -> PlayState
playState_update_cursor i p = p {playState_cur=i}

Then incrCursor would look like:

incrCursor :: State PlayState ()
incrCursor =
   cur <- gets playState_cursor
   len <- gets playState_len
   let newCur = min (cur+1) (len-1)
   modify (playState_update_cursor newCur)

Notice how often the characters "playState_" get typed. This would be a 
great situation for Emacs abbreviations. When you define an abbreviation 
in Emacs, such as defining "xps" to expand to "PlayState", emacs will 
watch for the characters xps. It will then replace "xps" with 
"PlayState" when you type a non-alphanumeric character following "xps". 
So if I type "xps." the moment I hit "." it changes to "PlayState."

But I would have a hard time using this feature with "playState_" 
because it is always followed by an alphanumeric character.

So my idea, now, is to put the definition of PlayState in its own module 
and import it qualified as PlayState.

---------------- module PlayState --------------

data PlayState = PlayState
    { cursor :: Int
    , verts :: [Loc]
    , len :: [Int]
    , doc :: MusDoc
    }

update_cursor i p = p {cursor = i}

-----------------------------------------------

I got rid of the "playState_" prefixes because I am not worried about 
using generic field names like "doc". They won't clash if I always 
import this qualified. And that reduces the necessary typing in the 
definition.

Now my monad looks like

testMonad = do
   cursor <- gets PlayState.cursor
   len    <- gets PlayState.len
   let newCur = min (cur+1) (len-1)
   modify $ PlayState.update_cursor newCur

Now I can define an abbreviation for PlayState. This is a big help. 
Also, I find this more readable. To me

    PlayState.cursor

is more readable than
    playState_cursor

For one thing, syntax highlighting helps in the former case. For 
another, the latter case requires that you recognize a naming 
convention, but the former case says clearly: "cursor is within the 
namespace PlayState, so this combination must be describing a cursor 
related to PlayState."







More information about the Haskell-Cafe mailing list