F#奇妙游(6):如何不用for循环以及循环

For循环

在F#中,for循环的语法如下:

for <identifier> = <start> to <end> do
    <body>

但是所有函数式编程语言都不鼓励使用for循环,而是使用递归来实现循环。

for循环的一个常见用法是遍历一个数组,例如:

let arr = [|1; 2; 3; 4; 5|]
for i = 0 to arr.Length - 1 do
    printfn "%d" arr.[i]

从语义上来看,为什么要用for循环呢?是因为我的很大哦不我的很多……遍历才是for循环的本质。那么,在函数式编程中我们该如何考虑这个问题呢?

函数式编程的核心之一是值,那么集合也应该是一个值。在F#中,数组是一个值,列表也是一个值。那么,我们可以把数组和列表看成是一个值,而不是一个变量。我们应该用函数来直接处理这个值,而不是用for循环来遍历这个值。

F#集合的类型

F#原生提供了几类集合,这几类集合的含义与传统意义上的集合完全相同,例如,数组就是长度固定,得到最后一个元素和长度的时间复杂度为O(1);列表、序列的长度和最后一个元素的时间复杂度为O(n)。诸如此类。一般的算法书中都会有介绍。

  • 列表:[1; 2; 3; 4; 5]
  • 数组:[|1; 2; 3; 4; 5|]
  • 序列:seq {1; 2; 3; 4; 5}
  • 集合:set [1; 2; 3; 4; 5]
  • 映射:map [("a", 1); ("b", 2); ("c", 3)]

按照文档上的原文,这几类的集合的特点如下:

  • List: 有序,不可变序列,元素类型相同,采用链表实现
  • Array: 固定长度,0-based,元素类型相同,元素可变
  • Seq: 惰性序列,元素类型相同
  • Set: 无序,不可变集合,元素类型相同,采用二叉树实现
  • Map: 不可变,字典,用键值key来索引

前面给出了相关字面量的表示方法。定义相应的值的方法如下:

let list = [1; 2; 3; 4; 5]
let arr = [|1; 2; 3; 4; 5|]
let seq = seq {1; 2; 3; 4; 5}
let set = set [1; 2; 3; 4; 5]
let map = map [("a", 1); ("b", 2); ("c", 3)]

既然是值,那么就应该用函数来作用到集合上面,并得到一个新的值。这是函数式编程的核心思想。

方法与管道

对应每个集合类型,F#提供了一系列函数,这些函数的形式如Type.appendType.add,区别与类和对象的成员函数,这些方法都是各个类型的静态函数。另外,这些函数的首字母都是小写的,这也符合.NET的命名规范。完整的方法列表可以参考MSDN

这些函数的定义有一个共同的特点,他们的最后一个参数都是集合类型,例如:

let list = [1; 2; 3; 4; 5]
List.add 6 list
let list2 = List.append [6; 7; 8] list
let list3 = List.filter (fun x -> x % 2 = 0) list
let list4 = List.map (fun x -> x * x) list

按照这样的方式定义,结合F#中偏应用、F#的管道操作符|>,我们可以很方便的对集合进行操作,例如:

let list = [1; 2; 3; 4; 5]
let sum_sqrt = list 
    |> List.add 6 
    |> List.append [6; 7; 8] 
    |> List.filter (fun x -> x % 2 = 0) 
    |> List.map (fun x -> x * x)
    |> List.fold (fun acc, elem -> acc + Math.Sqrt(float elem)) 0.0
    |> printfn "%f"

可以看到,什么高阶函数,什么map、reduce、filter,都是信手拈来。这就是函数式编程的魅力,针对值和映射编程,而不针对变量、对象、循环、状态编程。实际上F#也能不那么函数,但是这样就不好玩了。

还有一种循环

在程序设计中,还有一种循环,这种循环比较难以抽象为操作一个集合。就比如事件循环,需要操作的是一个队列,当队列里面没有事件的时候,就需要等待。这种循环在F#中可以用while来实现,例如:

while true do
    let input = Console.ReadLine()
    if input = "exit" then
        break
    else
        printfn "%s" input

这种循环怎么利用函数式编程的思想来实现呢?这就需要用到递归了。例如:

let rec loop () =
    let input = Console.ReadLine()
    if input = "exit" then
        ()
    else
        printfn "%s" input
        loop ()
loop ()

实际上这个问题我现在还是很困扰,虽然我也知道尾递归能够使得这种循环达到一定程度的效率,但是对于这个概念的函数式理解还是不够深刻。这个问题我会在后面的文章中继续探索。等我探索到了,感觉到了有趣,再写。

总结

  1. F#中的集合类型有列表、数组、序列、集合、映射
  2. F#中的集合类型首先是值,应该首选函数式的方式来操作集合
  3. F#中的集合类型的方法都是静态函数,可以用管道操作符|>来操作
  4. F#中的循环可以用递归来实现,这个我也没想明白
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大福是小强

除非你钱多烧得慌……

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值