HMR*_*HMR 1 generics f# dependency-injection type-inference
首先; 感谢您抽出宝贵时间阅读我的问题.如果您需要或希望我更改某些信息,请告诉我们.
当我传入一个数组处理函数时,类型推断不起作用,但是当我将该函数添加到模块而不是注入它时,它确实有效.
尝试添加类型注释,但这只是被忽略了,F#警告第一次调用它时代码不太通用,然后第二次错误类型错误输出.
但如果我改变:
let handleAction
//following does not work, comment out next line
(mapItems : 'a -> 'b [] -> int -> ('b -> 'a -> 'b) -> 'b [])
Run Code Online (Sandbox Code Playgroud)
至
let handleAction
//following does not work, comment out next line
(notPassed : 'a -> 'b [] -> int -> ('b -> 'a -> 'b) -> 'b [])
Run Code Online (Sandbox Code Playgroud)
然后它工作得很好.试图删除向上依赖项但无法让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
Run Code Online (Sandbox Code Playgroud)
完整代码在这里: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}
Run Code Online (Sandbox Code Playgroud)
函数泛型是函数声明的一部分.将函数作为值传递时,其通用性将丢失.
考虑以下最小的repro:
let mkList x = [x]
let mkTwo (f: 'a -> 'a list) = (f 42), (f "abc")
let two = mkTwo mkList
Run Code Online (Sandbox Code Playgroud)
此程序将导致相同的警告和您获得的相同错误.这是因为,当我说f: 'a -> 'a list,类型变量'a是属性mkTwo,而不是属性f.我们可以通过明确声明来更清楚地说明这一点:
let mkTwo<'a> (f: 'a -> 'a list) = (f 42), (f "abc")
Run Code Online (Sandbox Code Playgroud)
这意味着,在每次执行时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
Run Code Online (Sandbox Code Playgroud)
在这里,我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
Run Code Online (Sandbox Code Playgroud)
这无疑比纯功能代码更笨重,但它完成了工作.
但是在你的具体情况下,这甚至都不需要,因为你可以"烘烤" action正确的handlerfn
(这里我假设你实际上action在内部使用handlerfn,即使你发布的代码没有显示):
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"
})
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
167 次 |
| 最近记录: |