【问题标题】:F# tail recursion with XAMARIN MAC OS使用 XAMARIN MAC OS 的 F# 尾递归
【发布时间】:2017-03-15 08:06:31
【问题描述】:

我正在尝试从 http://fsharpforfunandprofit.com/posts/recursive-types-and-folds-2/

我在 Xamarin 6.1.5 for MAC OS 上运行

这里是代码

type Book = {title: string; price: decimal}

type ChocolateType = Dark | Milk | SeventyPercent
type Chocolate = {chocType: ChocolateType ; price: decimal}

type WrappingPaperStyle = 
    | HappyBirthday
    | HappyHolidays
    | SolidColor

type Gift =
    | Book of Book
    | Chocolate of Chocolate 
    | Wrapped of Gift * WrappingPaperStyle
    | Boxed of Gift 
    | WithACard of Gift * message:string

// A Book
let wolfHall = {title="Wolf Hall"; price=20m}
// A Chocolate
let yummyChoc = {chocType=SeventyPercent; price=5m}
// A Gift
let birthdayPresent = WithACard (Wrapped (Book wolfHall, HappyBirthday), "Happy Birthday")
// A Gift
let christmasPresent = Wrapped (Boxed (Chocolate yummyChoc), HappyHolidays)

let rec cataGift fBook fChocolate fWrapped fBox fCard gift :'r =
    let recurse = cataGift fBook fChocolate fWrapped fBox fCard
    match gift with 
    | Book book -> 
        fBook book
    | Chocolate choc -> 
        fChocolate choc
    | Wrapped (gift,style) -> 
        fWrapped (recurse gift,style)
    | Boxed gift -> 
        fBox (recurse gift)
    | WithACard (gift,message) -> 
        fCard (recurse gift,message) 

let totalCostUsingCata gift =
    let fBook (book:Book) = 
        book.price
    let fChocolate (choc:Chocolate) = 
        choc.price
    let fWrapped  (innerCost,style) = 
        innerCost + 0.5m
    let fBox innerCost = 
        innerCost + 1.0m
    let fCard (innerCost,message) = 
        innerCost + 2.0m
    // call the catamorphism
    cataGift fBook fChocolate fWrapped fBox fCard gift

let deeplyNestedBox depth =
    let rec loop depth boxSoFar =
        match depth with
        | 0 -> boxSoFar 
        | n -> loop (n-1) (Boxed boxSoFar)
    loop depth (Book wolfHall)


let rec totalCostUsingAcc costSoFar gift =
    match gift with 
    | Book book -> 
        costSoFar + book.price  // final result
    | Chocolate choc -> 
        costSoFar + choc.price  // final result
    | Wrapped (innerGift,style) -> 
        let newCostSoFar = costSoFar + 0.5m
        totalCostUsingAcc newCostSoFar innerGift 
    | Boxed innerGift -> 
        let newCostSoFar = costSoFar + 1.0m
        totalCostUsingAcc newCostSoFar innerGift 
    | WithACard (innerGift,message) -> 
        let newCostSoFar = costSoFar + 2.0m
        totalCostUsingAcc newCostSoFar innerGift 


let rec foldGift fBook fChocolate fWrapped fBox fCard acc gift :'r =
    let recurse = foldGift fBook fChocolate fWrapped fBox fCard 
    match gift with 
    | Book book -> 
        let finalAcc = fBook acc book
        finalAcc     // final result
    | Chocolate choc -> 
        let finalAcc = fChocolate acc choc
        finalAcc     // final result
    | Wrapped (innerGift,style) -> 
        let newAcc = fWrapped acc style
        recurse newAcc innerGift 
    | Boxed innerGift -> 
        let newAcc = fBox acc 
        recurse newAcc innerGift 
    | WithACard (innerGift,message) -> 
        let newAcc = fCard acc message 
        recurse newAcc innerGift


let totalCostUsingFold gift =  

    let fBook costSoFar (book:Book) = 
        costSoFar + book.price
    let fChocolate costSoFar (choc:Chocolate) = 
        costSoFar + choc.price
    let fWrapped costSoFar style = 
        costSoFar + 0.5m
    let fBox costSoFar = 
        costSoFar + 1.0m
    let fCard costSoFar message = 
        costSoFar + 2.0m

    // initial accumulator
    let initialAcc = 0m

    // call the fold
    foldGift fBook fChocolate fWrapped fBox fCard initialAcc gift 


[<EntryPoint>]
let main args =

       printfn "Arguments passed to function : %A" args
       let a = deeplyNestedBox 100000

       let res2= a |> totalCostUsingFold 


       // printfn "res2 = %A" res2
       0

在我看来这是尾递归,(就像网页上所说的那样) 但我确实在运行时遇到了 stackoverflow 错误

在项目的编译选项中,我确实选择了“生成尾调用”框

我的代码或 Xamarin 有什么问题吗?

【问题讨论】:

  • 这可能是 Mono 的限制,如果你搜索 SO,会弹出一些关于尾调用优化的问题。例如。 this ticket 仍然开放。

标签: xamarin f# mono stack-overflow tail-recursion


【解决方案1】:

我不确定这是否是堆栈溢出的原因,但cataGift 中的递归调用在我看来并不尾递归:

let rec cataGift fBook fChocolate fWrapped fBox fCard gift :'r =
    let recurse = cataGift fBook fChocolate fWrapped fBox fCard
    match gift with 
    | Book book -> 
        fBook book
    | Chocolate choc -> 
        fChocolate choc
    | Wrapped (gift,style) -> 
        fWrapped (recurse gift,style)
    | Boxed gift -> 
        fBox (recurse gift)
    | WithACard (gift,message) -> 
        fCard (recurse gift,message) 

在最后三种情况下,您调用recurse gift,这是一个递归调用,然后您将结果传递给另一个函数,如fCardfBox

要使其尾递归,您需要更改代码,以便对 recurse 的调用是匹配案例主体中的最后一件事(可能使用延续,或通过传递 fCard 函数以某种方式发送给recurse)。

【讨论】:

  • catagift 不是尾递归的。 foldGift 是。这是在totalCostUsingFold中调用的函数,在main部分中调用。 catagift 只是为了上下文,因为我从 http 链接中获取了源代码。我可以/应该从这个讨论中删除
  • 啊,感谢您的澄清 - 删除 let recurse 定义并直接调用 foldGift 会有所帮助吗?
  • 嗨,Thomas 删除 let recurse 并直接致电 foldGift 确实有效!但这很奇怪,代码不是还是按照原始语法尾递归的吗?
  • @FaguiCurtain 是的,代码仍然是尾递归的,但是使用内部函数,它使用.tail 指令编译。没有它,我认为编译器可以自己优化代码。
  • @FaguiCurtain 是的,我认为它仍然可以工作 - 但如果 .tail 的单声道出现问题,具有本地功能的版本将无法工作,但没有它的版本仍然可以。 ..
【解决方案2】:

我上次查看(几年前)Mono 不支持尾调用,因此您可以预期 Mono 上的尾递归函数会导致堆栈溢出。

是的,Mono 仍然不支持尾调用:

https://bugzilla.xamarin.com/show_bug.cgi?id=12635#c11

太可怕了!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-10
    相关资源
    最近更新 更多