用于功能风格的循环

时间:2018-03-09 09:55:59

标签: loops for-loop functional-programming

在函数式编程书籍中,反复出现的主题是FP意味着告诉计算机该做什么,而不是怎么做。一些代码示例是给出的,如:

val newNumbers = oldNumbers.map(_ * 2)

我们解释说我们不使用传统的for-loop来实现这一点,但我们宁愿使用map

但是map内部没有为循环实现。所以我们刚刚将代码转移到其他部分。 那么究竟是什么让FP比命令式风格更好呢?

3 个答案:

答案 0 :(得分:1)

令人遗憾和势在必行

让我们继续你的榜样,让所有价值加倍。

  1. 势在必行
    • 计算机,从第一个索引到最后一个索引,每次小于数组长度时增加当前索引。如果当前索引较小,则将当前索引加倍并将其添加到新数组,否则我们将返回新数组。
      function double(arr) {
         newArray = []
         for(index = 0; index < arr.length; i++) {
             newArr.push(arr[index] * 2) 
         }
         return newArray
      }
      
  2. Declerative
    • 计算机,请给我一个新数组,其中所有值都加倍
      arr.map(double)
      
  3. Implemation

    map函数隐藏并抽象实现,而不是一遍又一遍地重新实现它。作为一个地图用户,我不知道函数是用循环,递归还是用完全不同的编程语言实现的。

      

    重点是必须正确地完成工作。

    const map = fn => xs => {
      let newArray = []
      for(let i = 0; i < xs.length; i++) {
        newArray.push( fn(xs[i]) )
      }
      return newArray
    }
    
    const double = x =>
      x * 2
    
    console.log(
      map (double) ([1, 2, 3, 4])
    )

    const map = fn => xs =>
      _map (fn) (xs) ([])
    
    const _map = fn => xs => ys =>
      xs.length === 0
        ? ys
        : _map (fn) (xs.slice(1)) (ys.concat( fn(xs[0]) ))
          
    const double = x =>
      x * 2
          
    console.log(
      map (double) ([1, 2, 3, 4])
    )

答案 1 :(得分:1)

  

但是不是内部实现for循环的map函数

通常不是(至少在函数式语言中)。在一些不那么多范式的函数式语言中,for循环甚至都不存在。

map的常见实现是:

map(f, []) = []
map(f, x::xs) = f(x) :: map(f, xs)
  

那么究竟是什么让FP比命令式风格更好?

因为即使map恰好用for循环实现,使用map的代码也可以在没有任何可变变量或数据结构的情况下编写。

因此,不是将列表放入可变变量并从循环内重新分配,或者更糟糕的是,使用可从for循环插入元素的可变列表,您可以拥有包含不可变列表的不可变变量无论幕后发生什么,它都能奏效。

答案 2 :(得分:0)

评论太长了,可能确实是一个答案。

我将借用Apocalisp的评论:

  

for循环在内部由跳转指令(goto)实现。但你不会放弃循环并在任何地方使用gotos只是因为这是循环的实现方式

由于以下原因,我们不再使用GOTO(或longjmp / setjmp):

  1. 跟随控制流程太难了。
  2. 它们容易出错并且难以正确使用。
  3. 相反,我们使用forwhile等等(是的,map)。我们意味着什么更清楚,而且用这些结构犯错也更难(尽管我仍然认为太容易了)。

      

    如何证明FP得分超过命令式?我可以很好地将命令式实现放在一个编码良好的方法中,并在我需要的任何地方调用它

    是的,你可以这样做。你不应该,出于同样的原因,你应该使用通常喜欢经过充分测试和流行的第三方库或内置语言结构自己滚动一切。即使你是一个很棒的开发者,你也无法与热门编译器的编写者(往往是该领域最优秀的人)或数百名贡献者在一个流行的开源库中修复边缘案例错误。

      

    我可以强制实现一个方法,它返回一个新的列表而不修改输入一个,并在我需要的地方调用这个方法

    是。再说一次,你可以做到这一点。但请考虑以下因素:

    function doubleEvery(arr) {
      let i;
      let l = arr.length;
      let result = [];
      for (i=0; i<length; ++i) {
        result.push(arr[i] * 2);
      }
    
      return result;
    }
    

    可能出现什么问题?

    function doubleEvery(arr) { // iterating is coupled to doubling
      let i; // unitialized variable
      let l = arr.length;
    
      // Have to allocate result object, no opportunity for compiler
      // to optimize it away or use structural sharing
      let result = [];
      for (i=0; i<length; ++i) { // possible off-by-one error
    
        // Here if it's a more complex operation than simply doubling
        // we can't just pull this part out to test it independently
        result.push(arr[i] * 2);
      }
    
      return result;
    }
    

    另外,原始版本是9行,与arr.map(x => x * 2)相比。这节省了90%的LoC,在工具,网络和磁盘上更容易。

    我认为Apocalisp指出的是你所提出的论点容易受到 reductio ad absurdum 的影响:它可以应用于任何抽象。什么样的男人?你写语法Real programmers write操作码直接二进制哟。