传递map函数时类型推断不起作用

时间:2017-08-08 08:22:40

标签: generics dependency-injection f# type-inference

首先;感谢您抽出宝贵时间阅读我的问题。如果您需要或希望我更改某些信息,请告诉我们。

当我传入数组处理函数时,类型推断不起作用,但是当我将函数添加到模块而不是注入它时,它确实有效。

尝试添加类型注释,但这只是被忽略了,F#警告第一次调用时代码不那么通用,然后第二次错误输出错误。

但如果我改变:

let handleAction
  //following does not work, comment out next line
  (mapItems : 'a -> 'b [] -> int -> ('b -> 'a -> 'b) -> 'b [])

let handleAction
  //following does not work, comment out next line
  (notPassed : 'a -> 'b [] -> int -> ('b -> 'a -> 'b) -> 'b [])

然后它工作得很好。试图删除向上的依赖项,但无法让F#理解类型。

let mapItems
  action
  state 
  index
  handlerfn =
    state
      |> Array.indexed
      |> Array.map (
        fun (i, item) ->
          if index < 0 then
            handlerfn item action
          else if i = index then
            handlerfn item action
          else
            item)

//Mediator calling the handler for the action
let handleAction
  //following does not work, comment out next line
  (mapItems : 'a -> 'b [] -> int -> ('b -> 'a -> 'b) -> 'b [])
  //notPassedIn //uncomment this and it works 
                //even though mapItems here and mapItems
                //passed in are the exact same code
  state
  action =
    match action with
    |Pausable action -> //actions specific to pausable stopwatch
        let handler = 
          mapItems
            action //warning: less generic
            state
            action.index
        match action.``type`` with
          //... pausable actions (added to support pause/continue)
    | StopWatch action -> //actions from stop watch
        let handler = 
          mapItems
            action//error: wrong type
            state
            action.index
        match action.``type`` with
          //...handling stopwatch actions

完整代码在这里: https://github.com/amsterdamharu/programmingbook/tree/example8

(*
  stopwatch module
*)
//types
type SWActionType =
  | Start          of int
type StopWatchAction = {
  ``type``:SWActionType
  //there may be more than one stopwatch in the application
  index:int
}
type StartDate =
  | NoStartDate
  | Date of int
type SingleStopWatchState = {
  status:string
}
type StopWatchState = SingleStopWatchState []
//handlers for the stopwatch actions
let handleStart current state =
  {state with status = "started"}
//mediator for stopwatch
let StopWatchHandleAction 
  mapItems
  (state:StopWatchState)
  (action:StopWatchAction) =
    let handler = 
      mapItems
        action
        state
        action.index
    match action.``type`` with
      | Start current ->
          handler//call handler with state
            (fun
              (state:SingleStopWatchState)
              (action:StopWatchAction) ->
                (handleStart current state))
(*
  Pausable stopwatch that extends stopwatch and supports
  pause action
*)
type PActionType =
  | Pause          of int
type PausableStopWatchAction = {
  ``type``:PActionType
  index:int
}
type PAction =
  | StopWatch of StopWatchAction
  | Pausable of PausableStopWatchAction
type SinglePausableStopWatchState = {
  status:string
  isPaused:bool
}
type PausableStopWatchState = SinglePausableStopWatchState []
//handlers for pausable stopwatch
let handlePause current (state:SinglePausableStopWatchState) =
  {state with 
    status = "paused"
    isPaused = true
  }
//mediator for pausable stopwatch
let PausableHandleAction
  (mapItems : 'a -> 'b [] -> int -> ('b -> 'a -> 'b) -> 'b [])
  state
  action =
    match action with
    |Pausable action -> //actions specific to pausable stopwatch
        let handler = 
          mapItems
            //warning:This construct causes code to be less generic than indicated by the type annotations. The type variable 'a has been constrained to be type 'PausableStopWatchAction'.
            action
            state
            action.index
        match action.``type`` with
          | Pause current ->
              handler//call handler with state
                (fun
                  state
                  action ->
                    (handlePause current state))
    | StopWatch action -> //actions from stop watch
        let handler = 
          mapItems
            (*
              ERROR
              This expression was expected to have type
              'PausableStopWatchAction'    
              but here has type
              'StopWatchAction'
            *)
            action
            state
            action.index
        match action.``type`` with
          | Start current ->
              handler//call handler with state
                (fun
                  state
                  action -> //would use some of stopwatch handlers here
                    {state with
                      status ="started"
                    })
(*
  Application consuming stopwatch and pausable
*)
type ApplicationState = {
  stopwatch:StopWatchState
  pausablestopwatch:PausableStopWatchState
}
type Action =
  | StopWatch of StopWatchAction
  | PausableStopWatch of PAction
let ArrayHandler
  action
  state 
  index
  handlerfn =
    state
      |> Array.indexed
      |> Array.map (
        fun (i, item) ->
          if index < 0 then
            handlerfn item action
          else if i = index then
            handlerfn item action
          else
            item)
//application mediator:
let handleAction 
  (state : ApplicationState)
  action =
  match action with
    | StopWatch
        action ->
          {state with//return application state
            //set the stopwatch state with updated state
            //  provided by the mediator in stop watch
            stopwatch = 
              StopWatchHandleAction
                ArrayHandler state.stopwatch action}
    | PausableStopWatch 
        action ->
          {state with//return application state
            pausablestopwatch = 
              PausableHandleAction
                ArrayHandler state.pausablestopwatch action}

1 个答案:

答案 0 :(得分:6)

函数泛型是函数声明的一部分。将函数作为值传递时,其通用性将丢失。

考虑以下最小的重复:

let mkList x = [x]
let mkTwo (f: 'a -> 'a list) = (f 42), (f "abc")
let two = mkTwo mkList

此程序将导致相同的警告和您获得的相同错误。这是因为,当我说f: 'a -> 'a list时,类型变量'amkTwo的属性,而不是f的属性。我们可以通过明确声明来更清楚地说明这一点:

let mkTwo<'a> (f: 'a -> 'a list) = (f 42), (f "abc")

这意味着,在每次执行mkTwo时,必须只有一个 'a。在'a执行期间,mkTwo无法更改。

这暗示了类型推断:第一次编译器遇到表达式f 42时,它认为&#34; 嘿,f被调用{{1} }参数在这里,所以int必须是'a &#34; - 并向你发出一个有用的警告说&#34; 看起来,你说这应该是通用的,但你实际上使用的是具体类型int。此构造使此函数不如声明&#34;。

更通用

然后,编译器遇到表达式int。由于编译器已经决定f "abc",因此'a = int,它会抱怨f : int -> int list是错误的参数类型。

在原始代码中,函数为string,并且您使用两种不同类型的参数调用它:第一次使用mapItems(并获得警告),以及第二次使用PausableStopWatchAction(并收到错误)。

这个问题有两种通用解决方案:

一般解决方案1:将函数传递两次

StopWatchAction

在这里,我两次都传递完全相同的函数let mkList x = [x] let mkTwo f g = (f 42), (g "abc") let two = mkTwo mkList mkList 。在每种情况下,函数都会失去通用性,但它会以两种不同的方式失去:第一次变为mkList,第二次变为int -> int list。这样,string -> string list将它视为两个不同类型的不同函数,因此可以将它应用于不同的参数。

一般解决方案2:使用界面

与函数不同,接口方法在接口作为参数传递时不会失去通用性。因此,您可以将mkTwo函数包装在一个界面中并使用它:

mapItems

这无疑比纯功能代码更笨重,但它完成了工作。

代码的特定解决方案

但是在你的具体情况下,甚至都不需要,因为你可以&#34;烘烤&#34;将type MkList = abstract member mkList : 'a -> 'a list let mkList = { new MkList with member this.mkList x = [x] } let mkTwo (f: MkList) = (f.mkList 42), (f.mkList "abc") let two = mkTwo mkList 转到action (这里我假设你实际上在handlerfn内使用了action,即使你发布的代码没有表明这一点:

handlerfn