On Tue, Dec 1, 2009 at 9:01 PM, Robert Greayer <robgreayer@gmail.com> wrote:


On Tue, Dec 1, 2009 at 8:01 PM, Michael P Mossey <mpm@alumni.caltech.edu> wrote:
Perhaps someone could either (1) help me do what I'm trying to do, or (2) show me a better way.

I have a problem that is very state-ful and I keep thinking of it as OO, which is driving me crazy. Haskell is several times harder to use than Python in this instance, probably because I'm doing it wrong.

To give you a larger context, this problem is essentially compiling a description of music (my own) into a kind of music-machine-language (CSound). CSound is relatively untidy.

In this one example, in a OO way of thinking, I have data called AssignedNumbers that assigns integers to unique strings and keeps track of the used integers and next available integer (the choice of available integer could follow a number of conventions so I wanted to hide that in an ADT.) So it has an associated function:

getNumber :: String -> AssignedNumbers -> (Int,AssignedNumbers)

What getNumber does is:

 - check if the string already has a number assigned to it. If so, return that number.

 - if not, pick the next available number.

 - in all cases, return the possibly changed state of AssignedNumbers

Then in a larger data structure, it contains fields of type AssignedNumbers. Like

data MusicStuff = MusicStuff
 { oscillatorNumbers :: AssignedNumbers
 , tableNumbers :: AssignedNumbers
 , ... }

I'm using MusicStuff in a State monad, so I might write a function like

doSomeMusicStuff :: String -> String -> State MusicStuff (Int,Int)
doSomeMusicStuff aString1 aString2 = do
  ms <- get
  (o1,newOscNums) = getNumber aString1 (oscillatorNumbers ms)
  (t1,newTabNums) = getNumber aString2 (tableNumbers ms)
  put ms { oscillatorNumbers = newOscNums
         , tableNumbers = newTabNums }
  return (o1,t1)

For what it does, this is extremely verbose and filled with distracting visual content. And this is just a very simple example---my real problem is several times more state-ful. Is there a better way?

As a quick observation, you might consider changing getNumber to be something like:

nextNumber :: String -> NumberGroup -> State MusicStuff Int

where NumberGroup is something like

data NumberGroup = OscNums | TabNums |...

nextNumber updates the appropriate set of numbers in MusicStuff and returns the number. doSomeMusicStuff then becomes:

doSomeMusicStuff aString1 aString2 = (,) `liftM` nextNumber OscNums `ap` nextNumber TabNums

or better yet (applicatively)

doSomeMusicStuff aString1 aString2 = (,) <$> nextNumber OscNums <*> nextNumber TabNums
 

Oops, that's:

doSomeMusicStuff aString1 aString2 =
    (,) `liftM` nextNumber aString1 OscNums `ap` nextNumber aString2 TabNums

or:

doSomeMusicStuff aString1 aString2 =
    (,) <$> nextNumber aString1 OscNums <*> nextNumber aString2 TabNums