views:

803

answers:

5

Suppose that I have two data types Foo and Bar. Foo has fields x and y. Bar has fields x and z. I want to be able to write a function that takes either a Foo or a Bar as a parameter, extracts the x value, performs some calculation on it, and then returns a new Foo or Bar with the x value set accordingly.

Here is one approach:

class HasX a where
    getX :: a -> Int
    setX :: a -> Int -> a

data Foo = Foo Int Int deriving Show

instance HasX Foo where
    getX (Foo x _) = x
    setX (Foo _ y) val = Foo val y

getY (Foo _ z) = z
setY (Foo x _) val = Foo x val

data Bar = Bar Int Int deriving Show

instance HasX Bar where
    getX (Bar x _) = x
    setX (Bar _ z) val = Bar val z

getZ (Bar _ z) = z
setZ (Bar x _) val = Bar x val

modifyX :: (HasX a) => a -> a
modifyX hasX = setX hasX $ getX hasX + 5

The problem is that all those getters and setters are painful to write, especially if I replace Foo and Bar with real-world data types that have lots of fields.

Haskell's record syntax gives a much nicer way of defining these records. But, if I try to define the records like this

data Foo = Foo {x :: Int, y :: Int} deriving Show
data Bar = Foo {x :: Int, z :: Int} deriving Show

I'll get an error saying that x is defined multiple times. And, I'm not seeing any way to make these part of a type class so that I can pass them to modifyX.

Is there a nice clean way of solving this problem, or am I stuck with defining my own getters and setters? Put another way, is there a way of connecting the functions created by record syntax up with type classes (both the getters and setters)?

EDIT

Here's the real problem I'm trying to solve. I'm writing a series of related programs that all use System.Console.GetOpt to parse their command-line options. There will be a lot of command-line options that are common across these programs, but some of the programs may have extra options. I'd like each program to be able to define a record containing all of its option values. I then start with a default record value that is then transformed through a StateT monad and GetOpt to get a final record reflecting the command-line arguments. For a single program, this approach works really well, but I'm trying to find a way to re-use code across all of the programs.

+2  A: 

You could use code such as

data Foo = Foo { fooX :: Int, fooY :: Int } deriving (Show)
data Bar = Bar { barX :: Int, barZ :: Int } deriving (Show)

instance HasX Foo where
  getX = fooX
  setX r x' = r { fooX = x' }

instance HasX Bar where
  getX = barX
  setX r x' = r { barX = x' }

What are you modeling in your code? If we knew more about the problem, we could suggest something less awkward than this object-oriented design shoehorned into a functional language.

Greg Bacon
Thanks for the response. Your approach simplifies things somewhat. I've modified my original question to explain more about the real problem I'm trying to solve. I agree that using an OO approach with a functional language isn't usually the best way. I'm new to functional languages and still trying to break out of my OO thinking. :-)
Clint Miller
A: 

Hi Clint, You might want to have a look at this http://limpet.net/mbrubeck/2009/10/30/compleat.html

"Compleat is an easy, declarative way to add smart tab completion for any command". You can probably have a look at the technique used to achieve the generality.

narasim_7
+1  A: 

If you make the types instances of Foldable you get a toList function that you can use as the basis of your accessor.

If Foldable doesn't by you anything, then maybe the right approach is to define the interface you want as a type class and figure out a good way to autogenerate the derived values.

Perhaps by deriving from doing

deriving(Data)

you could use gmap combinators to base your access off.

Jonathan Fischoff
+3  A: 

You want extensible records which, I gather, is one of the most talked about topics in Haskell. It appears that there is not currently much consensus on how to implement it.

In your case it seems like maybe instead of an ordinary record you could use a heterogeneous list like those implemented in HList.

Then again, it seems you only have two levels here: common and program. So maybe you should just define a common record type for the common options and a program-specific record type for each program, and use StateT on a tuple of those types. For the common stuff you can add aliases that compose fst with the common accessors so it's invisible to callers.

Dan
Interesting idea about having a common record type and a program-specific type. I might have more groupings of commonality besides just common and program (not sure yet), but I could easily extend your approach to support this. (For example, I could pass as associative list of records through the StateT, and each option would know how to lookup its corresponding record in the list by name.)
Clint Miller
+1  A: 

Seems to me like a job for generics. If you could tag your Int with different newtypes, then you would be able to write (with uniplate, module PlateData):

data Foo = Foo Something Another deriving (Data,Typeable)
data Bar = Bar Another Thing deriving (Data, Typerable)

data Opts = F Foo | B Bar

newtype Something = S Int
newtype Another = A Int
newtype Thing = T Int

getAnothers opts = [ x | A x <- universeBi opts ]

This would extract all Another's from anywhere inside the Opts.

Modification is possible as well.

ADEpt