传递映射函数时类型推断不起作用
Type inference not working when passing map function
首先;感谢您花时间阅读我的问题。如果您需要任何更多信息或希望我更改某些内容,请告诉我。
当我传入一个数组处理函数时,类型推断不起作用,但是当我将该函数添加到模块而不是注入它时,它就起作用了。
尝试添加类型注释,但它只是被忽略了,并且 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}
函数通用性是函数声明的一部分。当您将函数作为值传递时,它的通用性就会丢失。
考虑以下最小重现:
let mkList x = [x]
let mkTwo (f: 'a -> 'a list) = (f 42), (f "abc")
let two = mkTwo mkList
此程序将导致与您收到的警告和错误相同。这是因为,当我说 f: 'a -> 'a list
时,类型变量 'a
是 mkTwo
的 属性,而不是 f
的 属性。我们可以通过显式声明使其更清楚:
let mkTwo<'a> (f: 'a -> 'a list) = (f 42), (f "abc")
这意味着,在 mkTwo
的每个给定执行中,必须只有 一个 'a
。 'a
在 mkTwo
执行期间不能更改。
这对类型推断有影响:编译器第一次遇到表达式 f 42
时,它认为“嘿,f
是用 int
参数,所以 'a
必须是 int
” - 并发出有用的警告说“ 看,你说这应该是通用的,但你'实际上,我们将它与具体类型 int
一起使用。这种构造使得此函数不如声明的 ".
通用。
然后,编译器遇到表达式 f "abc"
。由于编译器已经决定 'a = int
,因此 f : int -> int list
,它会抱怨 string
是错误的参数类型。
在您的原始代码中,该函数是 mapItems
,您使用两种不同类型的参数调用它:第一次使用 PausableStopWatchAction
(并收到警告),以及第二次使用 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
。这样,mkTwo
将其视为不同类型的两个不同函数,因此可以将其应用于不同的参数。
一般方案二:使用接口
接口方法与函数不同,当接口作为参数传递时不会失去通用性。因此,您可以将 mapItems
函数包装在一个接口中并使用它:
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
诚然,这比纯函数代码更庞大,但它完成了工作。
针对您的代码的特定解决方案
但在您的特定情况下,这甚至都不是必需的,因为您可以 "bake" action
直接进入 handlerfn
(这里我假设你实际上在 handlerfn
中使用 action
,即使你发布的代码没有显示):
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"
})
首先;感谢您花时间阅读我的问题。如果您需要任何更多信息或希望我更改某些内容,请告诉我。
当我传入一个数组处理函数时,类型推断不起作用,但是当我将该函数添加到模块而不是注入它时,它就起作用了。
尝试添加类型注释,但它只是被忽略了,并且 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}
函数通用性是函数声明的一部分。当您将函数作为值传递时,它的通用性就会丢失。
考虑以下最小重现:
let mkList x = [x]
let mkTwo (f: 'a -> 'a list) = (f 42), (f "abc")
let two = mkTwo mkList
此程序将导致与您收到的警告和错误相同。这是因为,当我说 f: 'a -> 'a list
时,类型变量 'a
是 mkTwo
的 属性,而不是 f
的 属性。我们可以通过显式声明使其更清楚:
let mkTwo<'a> (f: 'a -> 'a list) = (f 42), (f "abc")
这意味着,在 mkTwo
的每个给定执行中,必须只有 一个 'a
。 'a
在 mkTwo
执行期间不能更改。
这对类型推断有影响:编译器第一次遇到表达式 f 42
时,它认为“嘿,f
是用 int
参数,所以 'a
必须是 int
” - 并发出有用的警告说“ 看,你说这应该是通用的,但你'实际上,我们将它与具体类型 int
一起使用。这种构造使得此函数不如声明的 ".
然后,编译器遇到表达式 f "abc"
。由于编译器已经决定 'a = int
,因此 f : int -> int list
,它会抱怨 string
是错误的参数类型。
在您的原始代码中,该函数是 mapItems
,您使用两种不同类型的参数调用它:第一次使用 PausableStopWatchAction
(并收到警告),以及第二次使用 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
。这样,mkTwo
将其视为不同类型的两个不同函数,因此可以将其应用于不同的参数。
一般方案二:使用接口
接口方法与函数不同,当接口作为参数传递时不会失去通用性。因此,您可以将 mapItems
函数包装在一个接口中并使用它:
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
诚然,这比纯函数代码更庞大,但它完成了工作。
针对您的代码的特定解决方案
但在您的特定情况下,这甚至都不是必需的,因为您可以 "bake" action
直接进入 handlerfn
(这里我假设你实际上在 handlerfn
中使用 action
,即使你发布的代码没有显示):
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"
})