我有 2 个模块:秒表和时钟,秒表发出开始和停止 Action ,时钟不发出任何 Action 。
秒表处理启动、停止和更新操作
单击仅处理更新操作,但更新操作与秒表不同。
秒表 Action 类型
type ActionType =
| Start of StartActionData
| Stop of int
| Update of int
时钟操作类型
type ActionType =
| Update of (int * TimeZone)
我的应用程序同时使用秒表和时钟,因此应用程序操作 tpe 是:
type Action =
| StopWatch of StopWatchAction
| Clock of ClockAction
当模块(秒表或时钟)创建一个操作时,它将将该操作包装到一个包装函数中,该函数将返回应用程序可以使用的联合类型。
例如秒表创建一个停止 Action :
(wrapper:(StopWatchAction -> 'a)) {
``type`` = (Stop startTime)
index = index
}
'a
是因为模块不知道将使用它们的应用程序,但 'a
是 Application.Action 联合类型。
当操作到达应用程序处理程序时,它将使用该联合类型将操作委托(delegate)给模块处理程序:
match action with
| ApplicationTypes.StopWatch action ->
{state with//return application state
//set the stopwatch state with updated state
// provided by the mediator in stop watch
StopWatch =
StopWatchMediator.handleAction
state.StopWatch
action
}
问题是我有一个计时器模块,它采用 (StopWatch.Action->Application.Action)
和 (Clock.Action->Application.Action)
等包装器>
它每秒都会发出一个更新操作,更新操作还需要秒表和时钟的 createAction 函数,但这是稍后关注的。
我遇到的问题是我无法创建 ('a->'b)
函数列表,计时器中的代码是:
let mutable wrappers:(('a -> 'b) list) = []
let addWrapper wrapper =
wrappers <- wrapper::wrappers
在我的主应用程序文件中,我尝试像这样调用它:
addWrapper ApplicationTypes.StopWatch
addWrapper ApplicationTypes.Clock
计时器模块应该能够使用 ('a->'b)
包装定期分派(dispatch)任何类型的操作。
包装器和 Action 创建器都是由应用程序注入(inject)的,因为应用程序知道哪些模块需要计时器、如何包装 Action 以及使用什么函数来创建 Action 。
最佳答案
您遇到了 F#“值限制”错误。如果您不熟悉它,您应该阅读https://blogs.msdn.microsoft.com/mulambda/2010/05/01/finer-points-of-f-value-restriction/在继续本答案的其余部分之前。我会尽力解释。
在 F# 中,函数可以是通用的,但值必须是特定的。列表实际上不能容纳两个不同的值;也就是说,您不能拥有 'a -> 'b
函数列表,其中 'a
对于列表中的不同项目意味着不同的内容。您只能有一个 int -> string
函数列表,或一个 Clock -> ClockResult
函数列表,等等。如果您想要一个函数列表,其中一些函数的类型为 StopWatch.Action -> Application.Action
,而其他函数的类型为 Clock.Action -> Application.Action
,那么您需要以某种方式将它们组合起来,使它们成为相同的类型,然后才能将它们存储在列表中。例如,
type ActionWrapper =
| StopWatch of StopWatch.Action -> Application.Action
| Clock of Clock.Action -> Application.Action
let mutable wrappers : ActionWrapper list = []
let addWrapper (wrapper : ActionWrapper) =
wrappers <- wrapper :: wrappers
let addStopWatchWrapper (wrapper : StopWatch.Action -> Application.Action) =
addWrapper (StopWatch wrapper)
let addClockWrapper (wrapper : Clock.Action -> Application.Action) =
addWrapper (Clock wrapper)
现在,您的 wrappers
列表都是同一类型,您将能够根据需要使用 addStopWatchWrapper
或 addClockWrapper
函数.
附注为什么要使用一个作为全局函数的mutable
包装器列表?对我来说,这有点代码味道:你将得到“幽灵般的远距离 Action ”,以及与改变全局状态相关的所有其他问题。代码的不同部分之间将存在依赖关系,具体取决于哪个部分首先运行(并改变全局状态),哪个部分第二个运行(并看到突变的全局状态),但这些依赖关系将被隐藏:仅仅通过阅读代码你不会知道它们在那里。最好将该状态作为函数参数传递;这样你的代码依赖关系是明确的,并且以后你会遇到更少的意外。 (在编程中,“惊喜”=“错误”)。但是修复您的设计以使其不具有可变状态是一个更大的主题,因此我将在这个答案中限制自己解释您的 wrappers
列表发生了什么。
关于generics - 无法定义 'a->' b 函数列表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46031506/