Working on a Haskell project, I\'m dealing with the Event
data type from the FSNotify package. The constructors for Event
are all:
The simplest way would be to actually reflect the homogenity of the alternatives in the type declaration, instead of just observing it:
data Action = Added | Modified | Removed
data Event = FileEvent Action FilePath UTCTime | OtherEvent ...
f :: Event -> FilePath
f (FileEvent _ path _) = path
In general, Haskell has no way to know that all your constructor alternatives have the same number of arguments and the same type, so no, you can't abstract over the choice of alternative.
Arguably, this ADT would have better been implemented as
data Event = Event {
eventAction :: EventAction
, eventPath :: FilePath
, eventTime :: UTCTime
}
data EventAction = Addition | Modification | Removal
But those record accessors are actually just ordinary functions:
eventTime :: Event -> UTCTime
eventPath :: Event -> FilePath
...which of course you could have easily written yourself. But in fact the FSNotify package provides these precise functions, so in your case, the problem is solved.
...Or is it?
Actually, a record accessor is a bit more powerful than a getter function: it also allows you to modify entries, like
delayEvent :: Event -> Event
delayEvent ev = ev{eventTime = addUTCTime 60 $ eventTime ev}
You can't easily get that functionality if the type is not actually a record type.
Or can you?
Records as such have never really worked quite satisfyingly in Haskell: they are ad-hoc syntax that's not very consistent with the rest of the language, and they have scoping issues. In the last years there has been a movement away from these record accessors toward something much more fancy: lenses.
Actually, in newer libraries you would even more likely find the ADT thus:
import Control.Lens
import Control.Lens.TH
data Event = Event {
_eventAction :: EventAction
, _eventPath :: FilePath
, _eventTime :: UTCTime
}
makeLenses ''Event
In case you haven't come across lenses yet, these are some examples of what they allow you to do:
delayEvent :: Event -> Event
delayEvent = eventTime %~ addUTCTime 60
syncEventTo :: Event -> Event -> Event
syncEventTo tref = eventTime .~ tref^.eventTime
Now, unlike record accessors, lenses can be implemented 100% within Haskell without any need to special syntax from the compiler:
data Event = Added FilePath UTCTime
| Modified FilePath UTCTime
| Removed FilePath UTCTime
deriving (Eq, Show)
eventTime :: Lens' Event UTCTime
eventTime = lens get_t set_t
where get_t (Added _ t) = t
get_t (Modified _ t) = t
get_t (Removed _ t) = t
set_t (Added p _) t = Added p t
set_t (Modified p _) t = Modified p t
set_t (Removed p _) t = Removed p t