【问题标题】:Can/does the (forward) pipe operator prevent tail call optimization?(前向)管道运算符可以/是否阻止尾调用优化?
【发布时间】:2016-06-13 20:21:30
【问题描述】:

对于工作中的参数优化问题,我编写了一个遗传算法来找到一些好的设置,因为暴力解决方案是不可行的。不幸的是,当我早上回来时,大部分时间我都会收到StackOverflowException

我使用 F# 已经有一段时间了,所以我知道 TCO 以及对带有累加器参数的函数的需求,并且通常使用这种形式。

经过大量搜索,我想我能够确定触发异常的代码:

breedPopulation alive |> simulate (generation + 1) lastTime ewma

breedPopulation 从当前alive 中的个体生成新一代。然后通过调用simulate 开始下一轮/生成。当我查看反汇编(完全新手)时,我发现了一些 popret,所以它看起来不像是常规(非尾)调用。

mov         rcx,qword ptr [rbp+10h]  
mov         rcx,qword ptr [rcx+8]  
mov         rdx,qword ptr [rbp-40h]  
cmp         dword ptr [rcx],ecx  
call        00007FFA3E4905C0  
mov         qword ptr [rbp-0F0h],rax  
mov         r8,qword ptr [rbp-0F0h]  
mov         qword ptr [rbp-80h],r8  
mov         r8,qword ptr [rbp-78h]  
mov         qword ptr [rsp+20h],r8  
mov         r8d,dword ptr [rbp+18h]  
inc         r8d  
mov         rdx,qword ptr [rbp+10h]  
mov         r9,qword ptr [rbp-20h]  
mov         rcx,7FFA3E525960h  
call        00007FFA3E4A5040  
mov         qword ptr [rbp-0F8h],rax  
mov         rcx,qword ptr [rbp-0F8h]  
mov         rdx,qword ptr [rbp-80h]  
mov         rax,qword ptr [rbp-0F8h]  
mov         rax,qword ptr [rax]  
mov         rax,qword ptr [rax+40h]  
call        qword ptr [rax+20h]  
mov         qword ptr [rbp-100h],rax  
mov         rax,qword ptr [rbp-100h]  
lea         rsp,[rbp-10h]  
pop         rsi  
pop         rdi  
pop         rbp  
ret

把管子扔掉,把养殖放到正常参数位置后,拆装就不一样了。

//    simulate (generation + 1) lastTime ewma (breedPopulation alive)
mov         ecx,dword ptr [rbp+18h]  
inc         ecx  
mov         dword ptr [rbp-30h],ecx  
mov         rcx,qword ptr [rbp-20h]  
mov         qword ptr [rbp-38h],rcx  
mov         rcx,qword ptr [rbp-80h]  
mov         qword ptr [rbp-0F0h],rcx  
mov         rcx,qword ptr [rbp+10h]  
mov         rcx,qword ptr [rcx+8]  
mov         rdx,qword ptr [rbp-48h]  
cmp         dword ptr [rcx],ecx  
call        00007FFA3E4605C0  
mov         qword ptr [rbp-0F8h],rax  
mov         rax,qword ptr [rbp-0F8h]  
mov         qword ptr [rbp+30h],rax  
mov         rax,qword ptr [rbp-0F0h]  
mov         qword ptr [rbp+28h],rax  
mov         rax,qword ptr [rbp-38h]  
mov         qword ptr [rbp+20h],rax  
mov         eax,dword ptr [rbp-30h]  
mov         dword ptr [rbp+18h],eax  
nop  
jmp         00007FFA3E47585B

这肯定更短,最后的 jmp 甚至比尾调用更好。

因此,我想了解 如果以及为什么|> 似乎是问题所在,以及它何时会产生影响——毕竟,这是多年来它第一次咬我。在什么情况下会发生,我们需要注意什么?


更新:Guy 指出我的listing 不是IL 而是assembly 之后,我首先改写了这个问题。这是我通过ILSpy 发现的:

使用 |> 运算符

看反编译的C#,代码好像来回跳转

internal static FSharpFunc<Types.Genome[], System.Tuple<System.Tuple<float, float>, LbpArea[]>[]> simulate@265-1(Universe x, System.Threading.ManualResetEvent pleaseStop, int generation, System.DateTime lastTime, FSharpOption<double> ewma)
{
    return new $Universe.simulate@267-2(x, pleaseStop, generation, lastTime, ewma);
}

// internal class simulate@267-2
public override System.Tuple<System.Tuple<float, float>, LbpArea[]>[] Invoke(Types.Genome[] population)
{
    LbpArea[][] array = ArrayModule.Parallel.Map<Types.Genome, LbpArea[]>(this.x.genomeToArray, population);
    FSharpFunc<System.Tuple<System.Tuple<float, float>, LbpArea[]>, float> accessFitness = this.x.accessFitness;
    System.Tuple<System.Tuple<float, float>, LbpArea[]>[] array2 = ArrayModule.Filter<System.Tuple<System.Tuple<float, float>, LbpArea[]>>(new $Universe.alive@274(accessFitness), ArrayModule.Parallel.Map<LbpArea[], System.Tuple<System.Tuple<float, float>, LbpArea[]>>(new $Universe.alive@273-1(this.x), array));
    if (array2 == null)
    {
        throw new System.ArgumentNullException("array");
    }
    System.Tuple<System.Tuple<float, float>, LbpArea[]>[] array3 = ArrayModule.SortWith<System.Tuple<System.Tuple<float, float>, LbpArea[]>>(new $Universe.alive@275-2(), array2);
    this.x.Population = array3;
    System.Tuple<System.DateTime, FSharpOption<double>> tuple = this.x.printProgress<float, LbpArea[]>(this.lastTime, this.ewma, this.generation, array3);
    System.DateTime item = tuple.Item1;
    FSharpOption<double> item2 = tuple.Item2;
    if (this.pleaseStop.WaitOne(0))
    {
        return array3;
    }
    Types.Genome[] func = this.x.breedPopulation(array3);
    return $Universe.simulate@265-1(this.x, this.pleaseStop, this.generation + 1, item, item2).Invoke(func);
}

new 调用的 IL 中没有找到tail. 操作。另一方面,Invoke 的最后几行的 IL 读取

IL_00d3: call class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<class BioID.GeneticLbp.Types/Genome[], class [mscorlib]System.Tuple`2<class [mscorlib]System.Tuple`2<float32, float32>, valuetype [BioID.Operations.Biometrics]BioID.Operations.Biometrics.LbpArea[]>[]> '<StartupCode$BioID-GeneticLbp>.$Universe'::'simulate@265-1'(class BioID.GeneticLbp.Universe, class [mscorlib]System.Threading.ManualResetEvent, int32, valuetype [mscorlib]System.DateTime, class [FSharp.Core]Microsoft.FSharp.Core.FSharpOption`1<float64>)
IL_00d8: ldloc.s 7
IL_00da: tail.
IL_00dc: callvirt instance !1 class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<class BioID.GeneticLbp.Types/Genome[], class [mscorlib]System.Tuple`2<class [mscorlib]System.Tuple`2<float32, float32>, valuetype [BioID.Operations.Biometrics]BioID.Operations.Biometrics.LbpArea[]>[]>::Invoke(!0)
IL_00e1: ret

我不知道该怎么做。

没有 |> 运算符

另一个版本确实很不一样。以

开头
internal static System.Tuple<System.Tuple<float, float>, LbpArea[]>[] simulate@264(Universe x, System.Threading.ManualResetEvent pleaseStop, Unit unitVar0)
{
    FSharpFunc<int, FSharpFunc<System.DateTime, FSharpFunc<FSharpOption<double>, FSharpFunc<Types.Genome[], System.Tuple<System.Tuple<float, float>, LbpArea[]>[]>>>> fSharpFunc = new $Universe.simulate@265-2(x, pleaseStop);
    (($Universe.simulate@265-2)fSharpFunc).x = x;
    (($Universe.simulate@265-2)fSharpFunc).pleaseStop = pleaseStop;
    System.Tuple<System.Tuple<float, float>, LbpArea[]>[] population = x.Population;
    Types.Genome[] func;
    if (population != null && population.Length == 0)
    {
        func = x.lengthRandomlyIncreasing(x.laws@53.PopulationSize@);
        return FSharpFunc<int, System.DateTime>.InvokeFast<FSharpOption<double>, FSharpFunc<Types.Genome[], System.Tuple<System.Tuple<float, float>, LbpArea[]>[]>>(fSharpFunc, 0, System.DateTime.Now, null).Invoke(func);
    }
    FSharpFunc<LbpArea[], Types.Genome> arrayToGenome = x.arrayToGenome;
    func = ArrayModule.Parallel.Map<System.Tuple<System.Tuple<float, float>, LbpArea[]>, Types.Genome>(new $Universe.simulate@296-3(arrayToGenome), population);
    return FSharpFunc<int, System.DateTime>.InvokeFast<FSharpOption<double>, FSharpFunc<Types.Genome[], System.Tuple<System.Tuple<float, float>, LbpArea[]>[]>>(fSharpFunc, 0, System.DateTime.Now, null).Invoke(func);
}

它去

// internal class simulate@265-2
public override System.Tuple<System.Tuple<float, float>, LbpArea[]>[] Invoke(int generation, System.DateTime lastTime, FSharpOption<double> ewma, Types.Genome[] population)
{
    return $Universe.simulate@265-1(this.x, this.pleaseStop, generation, lastTime, ewma, population);
}

最后

internal static System.Tuple<System.Tuple<float, float>, LbpArea[]>[] simulate@265-1(Universe x, System.Threading.ManualResetEvent pleaseStop, int generation, System.DateTime lastTime, FSharpOption<double> ewma, Types.Genome[] population)
{
    while (true)
    {
        // Playing evolution...
        if (pleaseStop.WaitOne(0))
        {
            return array3;
        }
        // Setting up parameters for next loop...
    }
    throw new System.ArgumentNullException("array");
}

tl;博士

当然,管道运算符的使用极大地改变了程序流程。我的猜测是这两个函数之间的来回是最终导致异常的原因。

我已经阅读了Tail Calls in F#,但我认为它不适用于这种情况,因为我没有使用一流的函数返回单位作为值(在我的 F# 代码中)。

所以问题仍然存在:为什么管道操作符在这里会产生这种破坏性影响?我怎么能事先知道/我需要注意什么?


更新 2:

您可以在GitHub 找到该示例的简化版本。请亲自查看inline 运算符|&gt; 更改了生产IL,这不是我所期望的。

在简化示例的同时,幸运的是,我能够找到异常的真正来源。您可以查看branch 以获取更多最小变体。毕竟,它与管道没有任何关系,但我仍然不明白,因为恕我直言,尾递归。

但我最初的问题仍然存在。我只是添加一个更多。 :)

【问题讨论】:

  • 那是汇编级代码而不是 .NET IL 代码。你怎么得到的?构建说明是什么?你是不是说错了 IL?
  • 你也可以使用Ildasm.exe (IL Disassembler)
  • 你见过吗:Tail calls in F#
  • 您能否发布您的完整 F# 代码(或者,更好的是,一个较小的代表复制)? (|&gt;) 被定义为 inline 函数,所以我不认为它会影响 TCO。
  • 如果我从 GitHub 以 64 位运行您的最小示例,则会导致堆栈溢出。如果我从 GitHub 以 32 位运行您的最小示例,它就可以工作。我想我知道一种解决方法,现在正在检查它。

标签: f# stack-overflow tail-recursion cil tail-call-optimization


【解决方案1】:

根据所提供的最小情况,如果代码在 64 位的发布模式下运行,则会因堆栈溢出而失败。如果代码在 32 位模式下以 release 模式运行,则成功。

注意:在 32 位和 64 位之间进行选择的选项是Prefer 32-bit,如下图所示。

增加堆栈大小将导致代码成功进入 64 位的发布模式。这是通过使用Thread constructor 完成的。

[<EntryPoint>]
let main _ =

    let test () =
        let r = KissRandom()
        let n = r.Normal()
        Seq.item 20000 n |> printfn "%f"

    /// The greatest maximum-stack-size that should be used
    /// with the 'runWithStackFrame' function.
    let STACK_LIMIT = 16777216

    /// Run a function with a custom maximum stack size.
    /// This is necessary for some functions to execute
    /// without raising a StackOverflowException.
    let runWithCustomStackSize maxStackSize fn =
        // Preconditions
        if maxStackSize < 1048576 then
            invalidArg "stackSize" "Functions should not be executed with a \
                maximum stack size of less than 1048576 bytes (1MB)."
        elif maxStackSize > STACK_LIMIT then
            invalidArg "stackSize" "The maximum size of the stack frame should \
                not exceed 16777216 bytes (16MB)."

        /// Holds the return value of the function.
        let result = ref Unchecked.defaultof<'T>

        // Create a thread with the specified maximum stack size,
        // then immediately execute the function on it.
        let thread = System.Threading.Thread ((fun () -> result := fn()), maxStackSize)
        thread.Start ()

        // Wait for the function/thread to finish and return the result.
        thread.Join ()
        !result

    /// Runs a function within a thread which has an enlarged maximum-stack-size.
    let inline runWithEnlargedStack fn =
        runWithCustomStackSize STACK_LIMIT fn


//    test ()       // Fails with stack overflow in 64-bit mode, Release
                    // Runs successfully in 32-bit mode, Release

    runWithEnlargedStack test
    
    printf "Press any key to exit: "
    System.Console.ReadKey() |> ignore
    printfn ""

    0

此代码来自FSharp-logic-examples,尤其是 Anh-Dung Phan

虽然我没有检查根本原因,但我怀疑是因为 64 位的项目大小大于 32 位的项目大小,即使放入的项目数量堆栈和堆栈大小对于两个版本保持相同,项目大小的增加会推动堆栈所需的内存超过 1 兆字节的限制。

TL;DR

这是一个有趣且有启发性的问题。我很高兴有人问。

最初问题似乎与|&gt; 和 TCO 的使用有关,因为这仍然很有价值,所以我将其留在答案中。我还要感谢 OP 的响应和帮助,很高兴帮助与您合作而不是反对您的人。

在以下递归代码中,|&gt; 在 Visual Studio 中以调试模式运行会导致 StackOverflow。

如果它是从bin\release 目录的命令行启动的,它不会导致 StackOverflow。

使用 Visual Studio 15 社区

[<EntryPoint>]
let main argv = 

    let largeList = 
        printfn "Creating large list"
        [
            for i in 1 .. 100000000 do
                yield i
        ]

    // causes StackOverflow in Debug
    // No StackOverflow in Release
    let sum4 l =
        printfn "testing sum4"
        let rec sumInner4 l acc =
            match l with
            | h::t -> 
                let acc = acc + h
                acc |> sumInner4 t
            | [] -> acc
        sumInner4 l 0

    let result4 = sum4 largeList
    printfn "result4: %A" result4

在 Visual Studio 工具栏中设置“发布”或“调试”的位置

调试模式下项目的选项是

Release模式下项目的选项是

tldr;

在测试过程中,我创建了 16 个不同的测试并在调试和发布模式下构建它们,并验证它们是否运行完成或引发堆栈溢出。这 16 个被分解成一组 4 个,每组 4 个。情况 1,5,9,13 是否定的并产生堆栈溢出以确保可以创建堆栈溢出。案例 2,6,10,14 是肯定的,表明尾调用正在工作并且没有导致堆栈溢出。案例 3、7、11、15 显示了一个尾调用,其操作在与尾调用相同的语句中完成,并且使用|&gt; 与测试用例分开一个因子;这些按预期工作。案例 4、8、12、16 使用 |&gt; 并显示它何时在调试模式下工作和不工作,这可能让许多人感到惊讶。案例 1-4 和 9-12 使用 f x y 形式的函数,案例 8-11 使用 f x 形式的函数,案例 12-16 使用 f x y z 形式的函数。我最初做了前 8 个测试用例,但在 Keith 的评论之后又做了 4 个不使用列表但仍然使用来自 f x y 的函数并呈现意外结果,然后又做了 4 个使用表单的函数f x y z.

要运行测试,您必须注释掉除计划运行的一个测试之外的所有测试,并在调试模式下构建一次,然后可以在 Visual Studio 中运行,然后在发布模式下再次构建它,运行。我从命令行运行它以确保我运行的是发行版。

[<EntryPoint>]
let main argv = 

    let largeList = 
        printfn "Creating large list"
        [
            for i in 1 .. 100000000 do
                yield i
        ]

    // causes StackOverflow in Debug
    // causes StackOverflow in Release
    //   Negative confirmation
    //   A supposed tail call that DOES cause a stack overflow in both debug and release mode
    //   options: f x y
    let sum1 l = 
        printfn "test 01: "
        let rec sum1Inner l acc =
            match l with
            | h::t -> 
                let acc = acc + h
                1 + sum1Inner t acc
            | [] -> acc
        sum1Inner l 0
        
    // No StackOverflow in Debug
    // No StackOverflow in Release
    //   Positive confirmation
    //   A tail call that DOES NOT cause a stack overflow in both debug and release mode
    //   options: f x y
    let sum2 l =
        printfn "test 02: "
        let rec sum2Inner l acc =
            match l with
            | h::t -> 
                let acc = acc + h
                sum2Inner t acc
            | [] -> acc
        sum2Inner l 0
        
    // No StackOverflow in Debug
    // No StackOverflow in Release
    //   A test case
    //   options: f x y and no |>
    let sum3 l =
        printfn "test 03: "
        let rec sum3Inner l acc =
            match l with
            | h::t -> 
                sum3Inner t (acc + h)
            | [] -> acc
        sum3Inner l 0
        
    // causes StackOverflow in Debug
    // No StackOverflow in Release
    //   A test case
    //   options: f x y and |>
    let sum4 l =
        printfn "test 04: "
        let rec sum4Inner l acc =
            match l with
            | h::t -> 
                let acc = acc + h
                acc |> sum4Inner t
            | [] -> acc
        sum4Inner l 0
        
    // causes StackOverflow in Debug
    // causes StackOverflow in Release
    //   Negative confirmation
    //   A supposed tail call that DOES cause a stack overflow in both debug and release mode
    //   options: f x
    let sum5 () =
        printfn "test 05: "
        let rec sum5Inner x =
            match x with 
            | 10000000 -> x
            | _ -> 
                let acc = x + 1
                1 + sum5Inner acc
        sum5Inner 0
        
    // No StackOverflow in Debug
    // No StackOverflow in Release
    //   Positive confirmation
    //   A tail call that DOES NOT cause a stack overflow in both debug and release mode
    //   options: f x
    let sum6 () =
        printfn "test 06: "
        let rec sum6Inner x =
            match x with 
            | 10000000 -> x
            | _ -> 
                let acc = x + 1
                sum6Inner acc
        sum6Inner 0
        
    // No StackOverflow in Debug
    // No StackOverflow in Release
    //  A test case
    //  options: f x and no |>
    let sum7 l =
        printfn "test 07: "
        let rec sum7Inner x =
            match x with 
            | 10000000 -> x
            | _ -> sum7Inner (x + 1)
        sum7Inner 0
        
    // No StackOverflow in Debug
    // No StackOverflow in Release
    //   A test case
    //   options: f x and |>
    let sum8 () =
        printfn "test 07: "
        let rec sumInner8 x =
            match x with
            | 10000000 -> x
            | _ -> 
                let acc = x + 1
                acc |> sumInner8 
        sumInner8 0

    // causes StackOverflow in Debug
    // causes StackOverflow in Release
    //   Negative confirmation"
    //   A supposed tail call that DOES cause a stack overflow in both debug and release mode"
    //   options: f x y"
    let sum9 () = 
        printfn "test 09: "
        let rec sum9Inner x y =
            match y with
            | 10000000 -> y
            | _ -> 
                let acc = x + y
                1 + sum9Inner x acc
        sum9Inner 1 0   
        
    // No StackOverflow in Debug
    // No StackOverflow in Release
    //   Positive confirmation
    //   A tail call that DOES NOT cause a stack overflow in both debug and release mode
    //   options: f x y
    let sum10 () =
        printfn "test 10: "
        let rec sum10Inner x y =
            match y with
            | 10000000 -> y
            | _ -> 
                let acc = x + y
                sum10Inner x acc
        sum10Inner 1 0

    // No StackOverflow in Debug
    // No StackOverflow in Release
    //   A test case
    //   options: f x y and no |>
    let sum11 () =
        printfn "test 11: "
        let rec sum11Inner x y =
            match y with
            | 10000000 -> y
            | _ -> 
                sum11Inner x (x + y) 
        sum11Inner 1 0
        
    // causes StackOverflow in Debug
    // No StackOverflow in Release
    //   A test case
    //   options: f x y and |>
    let sum12 () =
        printfn "test 12: "
        let rec sum12Inner x y =
            match y with
            | 10000000 -> y
            | _ -> 
                let acc = x + y
                acc |> sum12Inner x
        sum12Inner 1 0

    // causes StackOverflow in Debug
    // No StackOverflow in Release
    //   A test case"
    //   options: f x y and |>"
    let sum12 () =
        printfn "test 12: "
        let rec sum12Inner x y =
            match y with
            | 10000000 -> y
            | _ -> 
                let acc = x + y
                acc |> sum12Inner x
        sum12Inner 1 0

    // causes StackOverflow in Debug
    // causes StackOverflow in Release
    //   Negative confirmation"
    //   A supposed tail call that DOES cause a stack overflow in both debug and release mode"
    //   options: f x y"
    let sum13 () = 
        printfn "test 13: "
        let rec sum13Inner x z y =
            match y with
            | 10000000 -> y
            | _ -> 
                let acc = x + y
                1 + sum13Inner x z acc 
        sum13Inner 1 "z" 0
        
    // No StackOverflow in Debug
    // No StackOverflow in Release
    //   Positive confirmation"
    //   A tail call that DOES NOT cause a stack overflow in both debug and release mode"
    //   options: f x y"
    let sum14 () =
        printfn "test 14: "
        let rec sum14Inner x z y =
            match y with
            | 10000000 -> y
            | _ -> 
                let acc = x + y
                sum14Inner x z acc
        sum14Inner 1 "z" 0

    // No StackOverflow in Debug
    // No StackOverflow in Release
    //   A test case"
    //   options: f x y and no |>"
    let sum15 () =
        printfn "test 15: "
        let rec sum15Inner x z y =
            match y with
            | 10000000 -> y
            | _ -> 
                sum15Inner x z (x + y) 
        sum15Inner 1 "z" 0

    // causes StackOverflow in Debug
    // No StackOverflow in Release
    //   A test case"
    //   options: f x y and |>"
    let sum16 () =
        printfn "test 16: "
        let rec sum16Inner x z y =
            match y with
            | 10000000 -> y
            | _ -> 
                let acc = x + y
                acc |> sum16Inner x z
        sum16Inner 1 "z" 0

    let result1 = sum1 largeList
    printfn "result1: %A" result1

    let result2 = sum2 largeList
    printfn "result2: %A" result2

    let result3 = sum3 largeList
    printfn "result3: %A" result3

    let result4 = sum4 largeList
    printfn "result4: %A" result4

    let result5 = sum5 ()
    printfn "result5: %A" result5

    let result6 = sum6 ()
    printfn "result6: %A" result6

    let result7 = sum7 ()
    printfn "result7: %A" result7

    let result8 = sum8 ()
    printfn "result8: %A" result8

    let result9 = sum9 ()
    printfn "result9: %A" result9

    let result10 = sum10 ()
    printfn "result10: %A" result10

    let result11 = sum11 ()
    printfn "result11: %A" result11

    let result12 = sum12 ()
    printfn "result12: %A" result12

    let result13 = sum13 ()
    printfn "result13: %A" result13

    let result14 = sum14 ()
    printfn "result14: %A" result14

    let result15 = sum15 ()
    printfn "result15: %A" result15

    let result16 = sum16 ()
    printfn "result16: %A" result16
    
    printf "Press any key to exit: "
    System.Console.ReadKey() |> ignore
    printfn ""

    0 // return an integer exit code

附加的新信息

编辑:Github 上的This thread 有 F# 的创建者 Don Syme,specifically mention that

[...] 其次,您是对的,我们不保证优化 f &lt;| xx |&gt; f 或任何类似于首次调用尾调用的使用,即使 f x 是尾调用。

【讨论】:

  • 我认为结果是,如果你有y |&gt; f x,那么在调试模式下,编译器可能会在两个离散的步骤中应用函数f(首先是x,然后是y),这可以防止编译器将直接递归调用变成循环。如果你有f x y,那么编译器会将直接递归调用编译成一个循环(在调试或发布模式下)。
  • 我在 Release 模式下进行了所有测试以及 IL/反汇编检查,我知道 Debug 中的情况有所不同。
  • 您在回答中付出了很多努力,我很想接受它。但即使有 16 mb 堆栈,我仍然会遇到异常(请参阅 master 分支)。并且由于管道而导致的完全不同代码的(原始)问题仍然存在。也许我应该将管道问题提取到一个新的(更简单的)问题而不是重写所有内容。
  • @primfaktor 我仍然得到异常(参见主分支)。然后,您重现问题的最小代码示例不正确,或者是由于不同的问题,因此我不认为这是拒绝接受的理由。并且由于管道而导致的完全不同代码的(原始)问题仍然存在。我没有看到问题,在您没有代码示例的原始问题中,我给出了一个带有重现问题的工作代码的答案,并展示了|&gt; 会和不会起作用的不同方式,例如f xf x y 被 kvb 指出。
  • 也许我应该将管道问题提取到一个新的(更简单的)问题,而不是重写所有内容。最初的问题是 Can/does the (forward) pipe operator prevent tail call optimization?Under what circumstances does it happen and what do we have to watch out for? 我在答案中显示。然后你改变了问题并给出了一个最小的例子,在答案中我展示了如何解决并给出了一个理由。如果您在被问到问题后不断更改问题,人们会注意到并回避您的问题。
猜你喜欢
  • 1970-01-01
  • 2010-09-11
  • 1970-01-01
  • 2020-02-20
  • 2020-07-17
  • 2013-10-08
  • 1970-01-01
  • 2020-10-21
  • 2014-06-09
相关资源
最近更新 更多