Does Haskell provide an idiom for pattern matching against many possible data constructors?

余生颓废 提交于 2020-05-08 14:28:05

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!