【问题标题】:F# - Turn simple for loop into more functional constructF# - 将简单的 for 循环变成更实用的构造
【发布时间】:2017-11-05 12:48:02
【问题描述】:

我有一个带有“终身”属性的简单“投资”类型:

type Investment = {
PurchasePayment: float;
Lifetime: float;
TaxWriteoff: float;
ResidualValue: float;
}

let CoffeMachine = {
    PurchasePayment = 13000.0;
    Lifetime = 4.0;
    TaxWriteoff = 2000.0;
    ResidualValue = 500.0;
}

我想迭代多年的投资并为每一年进行一些计算:

for i = 1 to int CoffeMachine.Lifetime do
    printf "%i" i
    // doSomething

有没有办法避免使用 for 循环并以更实用的风格编写它?

干杯,

沃伊泰克

【问题讨论】:

  • 这取决于 Lifetime 将如何在生产代码中生成,但使用 int 函数可能不是一个好主意。从数据库计算或检索,或其他意外,可能会导致 3.9... 而不是 4.0,然后在使用 int 后,您将有 3 年而不是 4 年。也许你应该改用round

标签: .net f# functional-programming


【解决方案1】:

我很快找到了答案:

[1..int CoffeMachine.Lifetime] |> List.iter (printf "%i") 

【讨论】:

    【解决方案2】:

    对列表中的每个项目执行计算的首选方法称为map。具体来说,对于您的情况,您可以创建一个从 1 到 Lifetime 的数字列表,然后使用 List.map 对每个数字执行计算:

    let calculation year = year * 2 // substitute your calculation here
    
    let calcResults = [1..int CoffeMachine.Lifetime] |> List.map calculation
    

    也就是说,我认为您将“功能性”与“难以理解”(或者,也许是“炫耀”)混淆了。 “函数式编程”的重点不是全是数学,外行也无法理解。 “函数式编程”的重点是“用函数编程”。这有一些实际含义,例如“不可变数据”和“无副作用”,只要您的程序满足这些,您就可以认为它是“功能性的”,无论它看起来多么简单。事实上,我认为它看起来越简单越好。软件可维护性是一个非常有价值的目标。

    特别是,如果你只是想打印出年份,那么你的代码就可以了:打印出来本身就是一个“副作用”,所以只要这是一个要求,就没有办法让它更具“功能性” ”。但是,如果您的目标是执行一些计算(如我上面的示例),那么可以使用列表理解更清晰地表达:

    let calcResults = [for year in 1..int CoffeMachine.Lifetime -> calculation year]
    

    【讨论】:

      【解决方案3】:

      与其他答案不同的方法是使用尾递归。

      例如,尾递归有什么好处?

      [1..int CoffeMachine.Lifetime] |> List.iter (printf "%i") 
      

      或者:

      for i = 1 to int CoffeMachine.Lifetime do
          printf "%i" i
      

      从性能和内存的角度来看,List.iterfor loop 更差,因为第一个创建了一个单链表(F# 不可变列表实际上是单链表)并对其进行迭代。在许多情况下,增加的 CPU 和内存使用量无关紧要,但在其他情况下却是相关的。

      Seq 这样的惰性集合可以缓解这种情况,但不幸的是Seq 目前在 F# 中效率不高。 Nessos Streams 会是更好的选择。

      F# 中 for loop 的问题在于它不能过早地被打破(breakcontinue 在 F# 中不存在)。

      此外,for loop 模式在聚合结果时经常迫使我们进入可变变量模式。

      尾递归允许我们在不依赖可变变量的情况下聚合结果并支持中止。另外,尾递归循环可以返回for loop不能返回的值(表达式结果总是unit

      尾递归在 F# 中也很有效,因为 F# 检测尾递归函数并将其展开到引擎盖下的循环。

      这就是上面代码的尾递归循环的样子:

      let rec loop i l = if i <= l then printf "%i" i; loop (i + 1) l
      loop 1 (int CoffeMachine.Lifetime)
      

      使用ILSpy 可以看到它被编译成一个while循环:

      internal static void loop@3(int i, int l)
      {
        while (i <= l)
        {
          PrintfFormat<FSharpFunc<int, Unit>, TextWriter, Unit, Unit> format = new PrintfFormat<FSharpFunc<int, Unit>, TextWriter, Unit, Unit, int>("%i");
          PrintfModule.PrintFormatToTextWriter<FSharpFunc<int, Unit>>(Console.Out, format).Invoke(i);
          int arg_28_0 = i + 1;
          l = l;
          i = arg_28_0;
        }
      }
      

      【讨论】:

        【解决方案4】:

        几个递归方法的简短示例。不过,通常您只需要使用 .iter 或 .map。

        let rec map f = function 
          | [] -> [] 
          | h::t -> f h::map f t  
        
        map (fun x->x+1) [1;2;3]
        
        let rec iter f = function 
          | [] -> () 
          | h::t -> 
              f h
              iter f t
        

        签名:

        for iter, f:('a -> unit)
        
        for map, f:('a -> 'b)
        

        你也可以得到标准的循环语法:

        for i in [0 .. 4] do 
          printfn "%i" i
        
        for i = 0 to 4 do
          printfn "%i" i
        

        要以函数式的方式使用 for 循环,这种语法会很方便:

        [for i in [0..10]->i]
        
        [for i = 0 to 10 do yield i]
        

        注意

        List.map f [0..10] 等价于 [for i in [0..10]->i]

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-09-30
          • 1970-01-01
          • 2015-12-31
          • 2018-03-05
          • 2017-05-30
          • 1970-01-01
          • 2011-08-07
          • 1970-01-01
          相关资源
          最近更新 更多