【问题标题】:Null Reference Exception in Recursive Function递归函数中的空引用异常
【发布时间】:2022-01-23 06:34:18
【问题描述】:

以下代码来自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 $40k" 
            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

【问题讨论】:

  • 您可以通过简单地从tree 和以下三个定义中删除recand 来修复它 - 将它们简化为let - 并重新排序四个以使其全部编译.
  • 有很多多余的括号是不需要的,并且使代码更难阅读。

标签: f#


【解决方案1】:

首先,这是一个 F# 编译器错误Initialization of mutually recursive values can sometimes leave fields uninitialized #6750

你可以用这个做什么?

  1. 不要使用相互递归的值。使用 let 绑定并按照 Bent 的建议重新排序(我会这样做)

  2. 不要使用相互递归的值。按照 Phillip 的建议使用一个大的初始化

  3. 使用lazy 表达式定义延迟

  4. 使用引用递归定义的值的函数表达式

在我们跳转到示例之前,我建议更改决策的定义以使结果明确并摆脱字符串:

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 $40k"
            Test = fun cl -> cl.Income > 40000
            Positive = checkCriminalRecord
            Negative = checkYearsInJob }

使用lazy 延迟(请注意,Positive 和 Negative 字段都应声明为 Lazy&lt;Decision&gt;

let rec checkIncome =
    Query { Title = "More than $40k"
            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 }

在测试树时强制一个案例执行一个值

testClientTree(client, case.Force())

最后,使用函数。在这里,我将更改查询定义以直接从测试返回决策:

type QueryInfo =
  { Title : string
    Test : Client -> Decision }

您仍然可以返回布尔测试结果,但专注于贷款报价会使代码更简洁:

let rec checkIncome =
    Query { Title = "More than $40k"
            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 }

【讨论】:

    【解决方案2】:

    不知道书上为什么会有这个初始化代码,但是失败的原因是因为数据实际上没有初始化。

    你需要这样做:

    let rec tree = 
        Query({ Title = "More than $40k" 
                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") })})})
    

    出于这样的原因,如果可以的话,最好不要定义一堆相互递归的东西。

    【讨论】:

      猜你喜欢
      • 2012-07-25
      • 1970-01-01
      • 2015-02-20
      • 2021-07-12
      • 1970-01-01
      • 1970-01-01
      • 2012-04-30
      • 1970-01-01
      • 2014-09-05
      相关资源
      最近更新 更多