generics - 无法定义 'a->' b 函数列表

标签 generics module f#

我有 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 列表都是同一类型,您将能够根据需要使用 addStopWatchWrapperaddClockWrapper 函数.

附注为什么要使用一个作为全局函数的mutable包装器列表?对我来说,这有点代码味道:你将得到“幽灵般的远距离 Action ”,以及与改变全局状态相关的所有其他问题。代码的不同部分之间将存在依赖关系,具体取决于哪个部分首先运行(并改变全局状态),哪个部分第二个运行(并看到突变的全局状态),但这些依赖关系将被隐藏:仅仅通过阅读代码你不会知道它们在那里。最好将该状态作为函数参数传递;这样你的代码依赖关系是明确的,并且以后你会遇到更少的意外。 (在编程中,“惊喜”=“错误”)。但是修复您的设计以使其不具有可变状态是一个更大的主题,因此我将在这个答案中限制自己解释您的 wrappers 列表发生了什么。

关于generics - 无法定义 'a->' b 函数列表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46031506/

相关文章:

c# - C#中的通用缓存

java - 具有可比界面和通用列表的未经检查的转换警告

java - 从包含的 jar 中读取属性文件

reflection - 在 F# 中,如何获取函数的参数?

class - 我可以在顶层实例化包含具有副作用的值的类吗?

asynchronous - 选择第一个异步结果

c# - 如何定义类型 T 必须具有字段 "ID"

c# - C# 编译器如何使用泛型?

python - Python 模块/包系统对大型项目的可扩展性

reactjs - 使用 rollupJs 构建 ES6 模块时 Material-UI ThemeProvider Invalid hook call