简体   繁体   English

Haskell是否为许多可能的数据构造函数提供了模式匹配的习惯用法?

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

Working on a Haskell project, I'm dealing with the Event data type from the FSNotify package. 在Haskell项目上工作,我正在处理FSNotify包中的Event数据类型。 The constructors for Event are all: Event的构造函数都是:

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; 在我的代码中,我只对从Event中提取FilePath并执行相同的操作感兴趣,而不管类型构造函数如何; because of this, I'm tempted to make a lambda. 因此,我很想做一个lambda。

Unfortunately, the code suffers reduced readability when I drop a case expression into the lambda to pattern match against all three cases; 不幸的是,当我将一个case表达式放入lambda以对所有三种情况进行模式匹配时,代码的可读性会降低; 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? 考虑到类型构造函数的相对同质性,是否有一些内置的习惯用法在一个表达式中只提取FilePath而不必手动模式匹配?

I've tried passing this expression as an anonymous function in a call to the watchDir action: 我已经尝试在调用watchDir操作时将此表达式作为匿名函数传递:

 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. 但是,可以预见,lambda模式匹配中的无关值会导致解析错误。

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. 一般来说,Haskell无法知道所有构造函数的替代方法都具有相同数量的参数和相同的类型,所以不,你不能抽象选择替代方案。

Arguably, this ADT would have better been implemented as 可以说,这个ADT最好实现为

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. 但实际上FSNotify包提供了这些精确的功能,因此在您的情况下,问题就解决了。

...Or is it? ......或者是吗?

Actually, a record accessor is a bit more powerful than a getter function: it also allows you to modify entries, like 实际上,记录访问器比getter函数更强大:它还允许您修改条目,例如

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. 这样的记录在Haskell中从未真正令人满意地工作过:它们是特殊的语法,与语言的其他部分不一致,并且它们具有范围问题。 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: 实际上,在较新的库中,您甚至更有可能找到ADT:

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: 现在,与记录访问器不同,镜头可以在Haskell中100%实现,而无需编译器的特殊语法:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM