问题描述
首先;感谢您花时间阅读我的问题.如果您需要更多信息或希望我更改某些内容,请告诉我.
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/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
,类型变量'a
是mkTwo
的属性,不是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
.'a
在 mkTwo
执行期间不能改变.
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屋!
更多推荐
[db:关键词]
发布评论