Domanda

Consider the following data definition:

data Foo = A{field::Int}
         | B{field::Int}
         | C
         | D

Now let's say we want to write a function that takes a Foo and increases field if it exists, and leave it unchanged otherwise:

incFoo :: Foo -> Foo
incFoo A{field=n} = A{field=n+1}
incFoo B{field=n} = B{field=n+1}
incFoo x = x

This naive approach leads to some code duplication. But the fact that both A and B shares field allows us to rewrite it:

incFoo :: Foo -> Foo
incFoo x | hasField x, n <- field x = x{field=n+1}
incFoo x = x

hasField A{} = True
hasField B{} = True
hasField _ = False

Less elegant, but that's defiantly easier to maintain when the actual manipulation is complex. The key feature here is x{field=n+1} - record syntax allows us to "update" field without specifying x's type. Considering this, I'd expect something similar to the following syntax (which is not supported):

incFoo :: Foo -> Foo
incFoo x{field=n} = x{field=n+1}
incFoo x = x

I've considered using View Patterns, but since field is a partial function (field C raises an error) it'll require wrapping it in more boilerplate code.

So my question is: why there's no support for the above syntax, and is there any elegant way of implementing a similar behavior?

Thanks!

È stato utile?

Soluzione

The reason why you can't do this is because in Haskell, records are inherently second class. They always must be wrapped in a constructor. So in order to have this work as intended you either must use an individual case for each constructor, or use a record substitute.

One possible solution is to use lenses. I'll use the implementation of lenses lens since lens-family-th doesn't seem to handle duplicate field names.

import Control.Lens

data Foo = A {_f :: Int}
         | B {_f :: Int}
         deriving Show
makeLenses ''Foo

foo :: Foo -> Foo
foo = f %~ (+1)

And then we can use it like this

> foo (A 1)
  A{_f = 1}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top