编写Haskell无限Fibonacci级数函数的C#版本

编写Haskell无限Fibonacci级数函数的C#版本,c#,haskell,functional-programming,fibonacci,C#,Haskell,Functional Programming,Fibonacci,注意:这个问题更多的是从好奇心的角度出发的。出于好奇,我想知道是否有可能将Haskell实现翻译成功能性C#等价物 因此,在解决问题时,我遇到了这个漂亮的Haskell实现: fibs :: [Integer] fibs = 1:1:zipWith (+) fibs (tail fibs) 当然,我很想写这样的C版本,所以: 如果我这样做: IEnumerable<int> fibs = Enumerable.Zip(Enumerable.Concat(new int[]

注意:这个问题更多的是从好奇心的角度出发的。出于好奇,我想知道是否有可能将Haskell实现翻译成功能性C#等价物

因此,在解决问题时,我遇到了这个漂亮的Haskell实现:

fibs :: [Integer]
fibs = 1:1:zipWith (+) fibs (tail fibs)
当然,我很想写这样的C版本,所以:

  • 如果我这样做:

    IEnumerable<int> fibs =
        Enumerable.Zip(Enumerable.Concat(new int[] { 1, 1 }, fibs),
                                                           //^^error
                                              fibs.Skip(1), (f, s) => f + s);
    
    它因堆栈溢出异常而中断!所以我来到这里

  • 问题:

  • 有人能想到一个功能性的C#等价物吗
  • 我想了解一下为什么我的解决方案不起作用

  • 第一个问题的答案是:这是如何在C#中实现的:

    与Eric Lippert的答案中提供的C#版本不同,该F#版本避免了重复计算元素,因此与Haskell的效率相当:

    let rec fibs =
        seq {
            yield 1
            yield 1
            for (a, b) in Seq.zip fibs (Seq.skip 1 fibs) do
                yield a + b
        }
        |> Seq.cache // this is critical for O(N)!
    

    我必须警告您,我是在尝试修复您的尝试,而不是生成一个高效的代码。 而且,这个解决方案有助于让我们的大脑爆炸,也许电脑也会爆炸

    在您的第一个代码片段中,您尝试调用递归字段或局部变量,这是不可能的。相反,我们可以尝试使用更类似于此的lambda。我们从教会知道,这也是不可能的,至少在传统方式下是不可能的。Lambda表达式未命名;您不能(在实现内部)按名称调用它们。但是可以使用不动点进行递归。如果你头脑清醒,很有可能不知道那是什么,无论如何,你应该在继续做这件事之前尝试一下

    fix :: (a -> a) -> a
    fix f = f (fix f)
    
    这将是c#实现(这是错误的)

    C#中最相似的翻译是:

    Func a=()=>4
    

    实际上,haskell实现涉及的内容要多得多,但这就是为什么如果您想用两种语言编写类似的解决问题的方法看起来如此不同的原因

    以Eric的答案为例,该答案的性能与haskell相当,但仍然存在其他问题(线程安全,无法释放内存)

    private static List fibs=new List(){1,1};
    静态IEnumerable F()
    {
    foreach(fibs中的var fib)
    {
    收益率fib;
    }
    INTA,b;
    while(true)
    {
    a=fibs.Last();
    b=fibs[fibs.Count()-2];
    加上(a+b);
    收益率a+b;
    }
    }
    
    这是针对Java的,具体取决于:

    final Stream fibs=new F2(){
    公共流f(最终整数a、最终整数b){
    返回cons(a,curry().f(b).lazy().f(a+b));
    }
    }.f(1,2);
    


    对于C#,您可以依赖于

    如果您使用F#,从Haskell环境到.NET环境的转换要容易得多。F#是微软类似于Haskell的函数声明性语言。

    我不确定C#的语言语义,但Haskell解决方案之所以有效是因为懒惰。它只计算必要的内容。在c#中也是这样吗?只在特殊情况下看一下@satvik。有一个叫做“Lazy”的类有懒惰,也有像linq这样的东西是懒惰的。此外,还可以使用“yield”之类的关键词,查看中已接受的答案(使用yield,直到需要时,您才能找到代码)是的。可枚举项是懒惰的。我可以写一个无限数字列表的
    IEnumberable
    。然后做
    无穷多。以(10)
    为例,就像haskell在C#中一样。你可能会更容易将haskell代码翻译成,因为它们都是函数式、声明式编程语言。嗯。与Haskell版本不同,此解决方案似乎不会缓存已计算的序列元素。因此,对于每个斐波那契数,它从一开始就开始,然后再对于它需要的每个斐波那契数。这就爆炸成指数级的复杂性。因此,除了前几个斐波那契数之外,这似乎是无用的。有没有办法解决这个问题?@Yitz-
    Zip
    Skip
    Take
    都是linq扩展方法。@Yits-没错@吉迪恩-与我答案中的F#版本进行比较。运行
    Seq.toArray(Seq.take 40 fibs)
    带和不带
    Seq.cache
    并观察差异..谢谢@EricLippert yep,我知道整个递归过程不是解决C#问题的正确方法,我只是出于好奇,想知道我是否可以翻译代码。更新:正是@Yitz!Eric-使用数组的想法很好(尽管BigInteger要高得多)。一个问题是,为什么不将prev设置为0,并消除额外收益回报的需要?也许我遗漏了什么。Haskell的
    zipWith
    基本上是F#的
    map2
    ,因此for循环可以替换为:
    yield!Seq.map2(+)fibs(Seq.skip 1 fibs)
    毫不奇怪,在F#中更简单。但在C#中也应该有一些方法来实现这一点。@Yitz可以使用C#中的
    Seq.cache
    ,它位于
    FSharp.Core.dll
    中。要自己正确地实施可能有点痛苦。哦,伙计,你简直太棒了。谢谢你的邀请answer@Bogdan非常感谢你的回答。它非常有洞察力,老实说,我不得不不断地重读它,并阅读更多关于haskell的实现和您添加的链接的内容。还应该指出,haskell版本实际上是
    fix f=let a=fa in a
    而不是
    fix f=f(fix f)
    因为前者可以避免空间泄漏,当
    f
    在结构上使用它的参数时,后者肯定会受到影响。很高兴知道,爱德华,在我写评论的那一刻,f(fix f)是实现,很好,您指出了基本包中的更改。不幸的是,这不是递归的,因此将结果记录下来的唯一好处是对F的多个外部调用。还有一个小问题:fibs是一个列表,所以请使用.Count属性,而不是.Count()IEnumerable扩展方法。这个
    static IEnumerable<BigInteger> Fib()
    {
        BigInteger prev = 0;
        BigInteger curr = 1;
        while (true)
        {
            yield return curr;
            var next = curr + prev;
            prev = curr;
            curr = next;
        }
    }
    
    let rec fibs =
        seq {
            yield 1
            yield 1
            for (a, b) in Seq.zip fibs (Seq.skip 1 fibs) do
                yield a + b
        }
        |> Seq.cache // this is critical for O(N)!
    
    fix :: (a -> a) -> a
    fix f = f (fix f)
    
    A fix<A>(Func<A,A> f) {
        return f(fix(f));
    }
    
    A fix<A>(Func<Func<A>,A> f) {
        return f(()=>fix(f));
    }
    
    static IEnumerable<T> Concat<T>(IEnumerable<T> xs,Func<IEnumerable<T>> f) {
       foreach (var x in xs)
         yield return x;
       foreach (var y in f())
         yield return y;
    }
    
    void play() {
        Func<Func<Func<IEnumerable<int>>>, Func<IEnumerable<int>>> F = fibs => () => 
                Concat(new int[] { 1, 1 }, 
                        ()=> Enumerable.Zip (fibs()(), fibs()().Skip(1), (a,b)=> a + b));
    
        //let's see some action
        var n5      = fix(F)().Take(5).ToArray();       // instant 
        var n20     = fix(F)().Take(20).ToArray();      // relative fast
        var n30     = fix(F)().Take(30).ToArray();      //this will take a lot of time to compute
        //var n40   = fix(F)().Take(40).ToArray();      //!!! OutOfMemoryException 
    }
    
    a:: Int
    a = 4
    
    Func<Int> a = () => 4
    
       private static List<int> fibs = new List<int>(){1,1};
       static IEnumerable<int> F()
       {
         foreach (var fib in fibs)
         {
          yield return fib;
         }
         int a, b;
         while (true)
         {
          a = fibs.Last();
          b = fibs[fibs.Count() - 2];
          fibs.Add(a+b);
          yield return a + b;
         }
       }
    
    final Stream<Integer> fibs = new F2<Integer, Integer, Stream<Integer>>() {
      public Stream<Integer> f(final Integer a, final Integer b) {
        return cons(a, curry().f(b).lazy().f(a + b));
      }
    }.f(1, 2);