递归函数中的空引用异常
Null Reference Exception in Recursive Function
下面的代码来自https://www.manning.com/books/real-world-functional-programming
的第8章
当我 运行 代码时,我在 testClientTree 中收到空引用异常。我检查了本书的勘误表,但没有找到任何内容。
type Client =
{ Name : string
Income : int
YearsInJob : int
UsesCreditCard : bool
CriminalRecord : bool }
let john =
{ Name = "John Doe"
Income = 40000
YearsInJob = 1
UsesCreditCard = true
CriminalRecord = false }
type ClientTests =
{ Check : Client -> bool
Report : Client -> unit }
type QueryInfo =
{ Title : string
Test : Client -> bool
Positive : Decision
Negative : Decision }
and Decision =
| Result of string
| Query of QueryInfo
let rec tree =
Query({ Title = "More than k"
Test = (fun cl -> cl.Income > 40000)
Positive = moreThan40; Negative = lessThan40 })
and moreThan40 =
Query({ Title = "Has criminal record"
Test = (fun cl -> cl.CriminalRecord)
Positive = Result("NO"); Negative = Result("YES") })
and lessThan40 =
Query({ Title = "Years in job"
Test = (fun cl -> cl.YearsInJob > 1)
Positive = Result("YES"); Negative = usesCredit })
and usesCredit =
Query({ Title = "Uses credit card"
Test = (fun cl -> cl.UsesCreditCard)
Positive = Result("YES"); Negative = Result("NO") })
let rec testClientTree(client, tree) =
match tree with
| Result(msg) ->
printfn " OFFER A LOAN: %s" msg
| Query(qi) ->
let s, case = if (qi.Test(client)) then "yes", qi.Positive
else "no", qi.Negative
printfn " - %s? %s" qi.Title s
testClientTree(client, case)
[<EntryPoint>]
let main argv =
testClientTree(john, tree)
0
不知道书上为什么会有这个初始化代码,但是失败的原因是数据实际上没有初始化。
您需要这样做:
let rec tree =
Query({ Title = "More than k"
Test = (fun cl -> cl.Income > 40000)
Positive =
Query({ Title = "Has criminal record"
Test = (fun cl -> cl.CriminalRecord)
Positive = Result("NO")
Negative = Result("YES") })
Negative =
Query({ Title = "Years in job"
Test = (fun cl -> cl.YearsInJob > 1)
Positive = Result("YES");
Negative =
Query({ Title = "Uses credit card"
Test = (fun cl -> cl.UsesCreditCard)
Positive = Result("YES"); Negative = Result("NO") })})})
出于这样的原因,如果可以的话,最好不要定义一堆相互递归的东西。
首先,这是一个 F# 编译器错误 Initialization of mutually recursive values can sometimes leave fields uninitialized #6750
你可以用它做什么?
不要使用相互递归的值。使用 let
绑定并按照 Bent 的建议重新排序(我会这样做)
不要使用相互递归的值。按照菲利普的建议使用一个大的初始化
延迟定义 lazy
表达式
使用引用递归定义的值的函数表达式
在我们跳到示例之前,我建议更改 Decision 的定义以使结果明确并删除字符串:
Decision =
| Approved
| Declined
| Query of QueryInfo
此外,我会建议根据查询的内容来命名查询,而不是根据导致新查询的先前查询的部分来命名查询。 IE。而不是 lessThan40
使用 checkYearsInJob
.
所以,最简单的方法 - 重新排序 let
绑定
let checkCriminalRecord =
Query { Title = "Has criminal record"
Test = fun cl -> cl.CriminalRecord
Positive = Declined
Negative = Approved }
let checkCreditCardUsage =
Query { Title = "Uses credit card"
Test = fun cl -> cl.UsesCreditCard
Positive = Approved
Negative = Declined }
let checkYearsInJob =
Query { Title = "Years in job"
Test = fun cl -> cl.YearsInJob > 1
Positive = Approved
Negative = checkCreditCardUsage }
let checkIncome =
Query { Title = "More than k"
Test = fun cl -> cl.Income > 40000
Positive = checkCriminalRecord
Negative = checkYearsInJob }
使用 lazy
延迟(请注意,Positive 和 Negative 字段都应声明为 Lazy<Decision>
)
let rec checkIncome =
Query { Title = "More than k"
Test = fun cl -> cl.Income > 40000
Positive = lazy checkCriminalRecord
Negative = lazy checkYearsInJob }
and checkCriminalRecord =
Query { Title = "Has criminal record"
Test = fun cl -> cl.CriminalRecord
Positive = lazy Declined
Negative = lazy Approved }
and checkYearsInJob =
Query { Title = "Years in job"
Test = fun cl -> cl.YearsInJob > 1
Positive = lazy Approved
Negative = lazy checkCreditCardUsage }
and checkCreditCardUsage =
Query { Title = "Uses credit card"
Test = fun cl -> cl.UsesCreditCard
Positive = lazy Approved
Negative = lazy Declined }
测试树时强制一个case执行一个值
testClientTree(client, case.Force())
最后,使用函数。在这里,我会将查询定义更改为 return decision directly from test:
type QueryInfo =
{ Title : string
Test : Client -> Decision }
您仍然可以 return 测试的布尔结果,但专注于贷款报价可以使代码更清晰:
let rec checkIncome =
Query { Title = "More than k"
Test = fun c -> if c.Income > 40000 then checkCriminal else checkYearsInJob }
and checkCriminal =
Query { Title = "Has criminal record"
Test = fun c -> if c.CriminalRecord then Declined else Approved }
and checkYearsInJob =
Query { Title = "Years in job"
Test = fun c -> if c.YearsInJob > 1 then Approved else checkCreditCardUsage }
and checkCreditCardUsage =
Query { Title = "Uses credit card"
Test = fun c -> if c.UsesCreditCard then Approved else Declined }
下面的代码来自https://www.manning.com/books/real-world-functional-programming
的第8章当我 运行 代码时,我在 testClientTree 中收到空引用异常。我检查了本书的勘误表,但没有找到任何内容。
type Client =
{ Name : string
Income : int
YearsInJob : int
UsesCreditCard : bool
CriminalRecord : bool }
let john =
{ Name = "John Doe"
Income = 40000
YearsInJob = 1
UsesCreditCard = true
CriminalRecord = false }
type ClientTests =
{ Check : Client -> bool
Report : Client -> unit }
type QueryInfo =
{ Title : string
Test : Client -> bool
Positive : Decision
Negative : Decision }
and Decision =
| Result of string
| Query of QueryInfo
let rec tree =
Query({ Title = "More than k"
Test = (fun cl -> cl.Income > 40000)
Positive = moreThan40; Negative = lessThan40 })
and moreThan40 =
Query({ Title = "Has criminal record"
Test = (fun cl -> cl.CriminalRecord)
Positive = Result("NO"); Negative = Result("YES") })
and lessThan40 =
Query({ Title = "Years in job"
Test = (fun cl -> cl.YearsInJob > 1)
Positive = Result("YES"); Negative = usesCredit })
and usesCredit =
Query({ Title = "Uses credit card"
Test = (fun cl -> cl.UsesCreditCard)
Positive = Result("YES"); Negative = Result("NO") })
let rec testClientTree(client, tree) =
match tree with
| Result(msg) ->
printfn " OFFER A LOAN: %s" msg
| Query(qi) ->
let s, case = if (qi.Test(client)) then "yes", qi.Positive
else "no", qi.Negative
printfn " - %s? %s" qi.Title s
testClientTree(client, case)
[<EntryPoint>]
let main argv =
testClientTree(john, tree)
0
不知道书上为什么会有这个初始化代码,但是失败的原因是数据实际上没有初始化。
您需要这样做:
let rec tree =
Query({ Title = "More than k"
Test = (fun cl -> cl.Income > 40000)
Positive =
Query({ Title = "Has criminal record"
Test = (fun cl -> cl.CriminalRecord)
Positive = Result("NO")
Negative = Result("YES") })
Negative =
Query({ Title = "Years in job"
Test = (fun cl -> cl.YearsInJob > 1)
Positive = Result("YES");
Negative =
Query({ Title = "Uses credit card"
Test = (fun cl -> cl.UsesCreditCard)
Positive = Result("YES"); Negative = Result("NO") })})})
出于这样的原因,如果可以的话,最好不要定义一堆相互递归的东西。
首先,这是一个 F# 编译器错误 Initialization of mutually recursive values can sometimes leave fields uninitialized #6750
你可以用它做什么?
不要使用相互递归的值。使用
let
绑定并按照 Bent 的建议重新排序(我会这样做)不要使用相互递归的值。按照菲利普的建议使用一个大的初始化
延迟定义
lazy
表达式使用引用递归定义的值的函数表达式
在我们跳到示例之前,我建议更改 Decision 的定义以使结果明确并删除字符串:
Decision =
| Approved
| Declined
| Query of QueryInfo
此外,我会建议根据查询的内容来命名查询,而不是根据导致新查询的先前查询的部分来命名查询。 IE。而不是 lessThan40
使用 checkYearsInJob
.
所以,最简单的方法 - 重新排序 let
绑定
let checkCriminalRecord =
Query { Title = "Has criminal record"
Test = fun cl -> cl.CriminalRecord
Positive = Declined
Negative = Approved }
let checkCreditCardUsage =
Query { Title = "Uses credit card"
Test = fun cl -> cl.UsesCreditCard
Positive = Approved
Negative = Declined }
let checkYearsInJob =
Query { Title = "Years in job"
Test = fun cl -> cl.YearsInJob > 1
Positive = Approved
Negative = checkCreditCardUsage }
let checkIncome =
Query { Title = "More than k"
Test = fun cl -> cl.Income > 40000
Positive = checkCriminalRecord
Negative = checkYearsInJob }
使用 lazy
延迟(请注意,Positive 和 Negative 字段都应声明为 Lazy<Decision>
)
let rec checkIncome =
Query { Title = "More than k"
Test = fun cl -> cl.Income > 40000
Positive = lazy checkCriminalRecord
Negative = lazy checkYearsInJob }
and checkCriminalRecord =
Query { Title = "Has criminal record"
Test = fun cl -> cl.CriminalRecord
Positive = lazy Declined
Negative = lazy Approved }
and checkYearsInJob =
Query { Title = "Years in job"
Test = fun cl -> cl.YearsInJob > 1
Positive = lazy Approved
Negative = lazy checkCreditCardUsage }
and checkCreditCardUsage =
Query { Title = "Uses credit card"
Test = fun cl -> cl.UsesCreditCard
Positive = lazy Approved
Negative = lazy Declined }
测试树时强制一个case执行一个值
testClientTree(client, case.Force())
最后,使用函数。在这里,我会将查询定义更改为 return decision directly from test:
type QueryInfo =
{ Title : string
Test : Client -> Decision }
您仍然可以 return 测试的布尔结果,但专注于贷款报价可以使代码更清晰:
let rec checkIncome =
Query { Title = "More than k"
Test = fun c -> if c.Income > 40000 then checkCriminal else checkYearsInJob }
and checkCriminal =
Query { Title = "Has criminal record"
Test = fun c -> if c.CriminalRecord then Declined else Approved }
and checkYearsInJob =
Query { Title = "Years in job"
Test = fun c -> if c.YearsInJob > 1 then Approved else checkCreditCardUsage }
and checkCreditCardUsage =
Query { Title = "Uses credit card"
Test = fun c -> if c.UsesCreditCard then Approved else Declined }