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

前端 未结 2 1723
梦如初夏
梦如初夏 2021-01-14 07:45

Working on a Haskell project, I\'m dealing with the Event data type from the FSNotify package. The constructors for Event are all:

         


        
相关标签:
2条回答
  • 2021-01-14 08:01

    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.

    0 讨论(0)
  • 2021-01-14 08:01

    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
    
    0 讨论(0)
提交回复
热议问题