问题
Working on a Haskell project, I'm dealing with the Event
data type from the FSNotify package. The constructors for Event
are all:
Added FilePath UTCTime
Modified FilePath UTCTime
Removed FilePath UTCTime
In my code, I'm only interested in extracting the FilePath
from the Event
and doing the same action regardless of the type constructor; because of this, I'm tempted to make a lambda.
Unfortunately, the code suffers reduced readability when I drop a case
expression into the lambda to pattern match against all three cases; is there some built-in idiom to extract just the FilePath
in one expression without having to manually pattern match, given the relative homogeneity of the type constructors?
I've tried passing this expression as an anonymous function in a call to the watchDir
action:
wm <- startManager
sw <- watchDir wm "." (\_ -> True) (\(_ f t) -> putStrLn f)
But, predictably, the don't-care value in the lambda's pattern match causes a parse error.
回答1:
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.
回答2:
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
来源:https://stackoverflow.com/questions/39456718/does-haskell-provide-an-idiom-for-pattern-matching-against-many-possible-data-co