传递地图功能时类型推断不起作用 [英] Type inference not working when passing map function

查看:22
本文介绍了传递地图功能时类型推断不起作用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

首先;感谢您花时间阅读我的问题.如果您需要更多信息或希望我更改某些内容,请告诉我.

First of all; thank you for taking the time to read my question. If there is any more information you need or would like me to change something please let me know.

当我传入一个数组处理函数时,类型推断不起作用,但是当我将该函数添加到模块而不是注入它时,它就起作用了.

When I pass in an array handler function the type inference does not work, but when I add the function to the module instead of injecting it then it does work.

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

Tried adding type annotation but that's just ignored and F# warns about code being less generic when calling it the first time and then errors out with wrong type the second time.

但如果我改变:

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# 理解类型.

Then it works just fine. Trying to remove upwards dependencies but can't get F# to understand the type.

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}

推荐答案

函数通用性是函数声明的一部分.当您将函数作为值传递时,它的通用性就会丢失.

Function genericity is part of the function declaration. When you pass a function as a value, its genericity is lost.

考虑以下最小重现:

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

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

This program will cause the same warning and same error you're getting. This is because, when I say f: 'a -> 'a list, the type variable 'a is a property of mkTwo, not property of f. We could make this clearer by declaring it explicitly:

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

这意味着,在每次执行 mkTwo 时,必须只有 one 'a.'amkTwo 执行期间不能改变.

This means that, on every given execution of mkTwo, there has to be only one 'a. The 'a cannot change during an mkTwo execution.

这对类型推断有一个含义:编译器第一次遇到表达式 f 42 时,它认为嘿,f 被调用int 参数在这里,所以 'a 必须是 int" - 并向您发出有用的警告说看,你说这应该是泛型的,但你实际上是将它与一个具体的类型 int 一起使用.这个构造使得这个函数没有声明的那么通用".

This has an implication for type inference: the first time the compiler comes across the expression f 42, it thinks "hey, f is called with an int argument here, so 'a must be int" - and issues you a helpful warning saying "look, you say this should be generic, but you're actually using it with a concrete type int. This construct makes this function less generic than declared".

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

Then, the compiler comes across the expression f "abc". Since the compiler has already decided that 'a = int, and therefore f : int -> int list, it complains that string is the wrong parameter type.

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

In your original code, the function is mapItems, and you're calling it with two different types of arguments: the first time with PausableStopWatchAction (and get a warning), and the second time with StopWatchAction (and get an error).

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

There are two general solutions to this problem:

let mkList x = [x]
let mkTwo f g = (f 42), (g "abc")
let two = mkTwo mkList mkList

在这里,我两次都传递了完全相同的函数 mkList.在每种情况下,函数都失去了通用性,但它以两种不同的方式失去了它:第一次变为 int ->int list,第二次变成string ->字符串列表.这样,mkTwo 将其视为两个不同类型的不同函数,因此可以将其应用于不同的参数.

Here, I pass the exact same function mkList both times. In each case the function loses genericity, but it loses it in two different ways: the first time it becomes int -> int list, and the second time it becomes string -> string list. This way, mkTwo sees it as two different functions, of different types, and so can apply it to different arguments.

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

Interface methods, unlike functions, do not lose genericity when the interface is passed as argument. So you can wrap your mapItems function in an interface and use it:

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

诚然,这比纯函数式代码体积更大,但它可以完成工作.

This is admittedly more bulky than pure functional code, but it gets the job done.

但在您的特定情况下,这甚至都不是必需的,因为您可以将 action 直接烘焙"到 handlerfn(这里我假设您实际上在 handlerfn 中使用了 action,即使您发布的代码没有显示):

But in your specific case, that is all not even required, because you could "bake" the action right into handlerfn (here I assume that you're actually using action inside handlerfn, even though the code you posted doesn't show that):

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

...

let handleAction
  (mapItems : 'a [] -> int -> ('a -> 'a) -> 'a [])
  state
  action =
    match action with
    |Pausable action -> //actions specific to pausable stopwatch
        let handler = 
          mapItems
            state
            action.index
        match action.``type`` with
         | Pause current ->
             handler//call handler with state
               (fun state ->
                     (handlePause current state))
    | StopWatch action -> //actions from stop watch
       let handler = 
         mapItems
           state
           action.index
       match action.``type`` with
         | Start current ->
             handler//call handler with state
               (fun state ->
                   //would use some of stopwatch handlers here
                   {state with
                     status ="started"
                   })

这篇关于传递地图功能时类型推断不起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆