我正在尝试学习F#并且在看到一些奇怪的东西(至少对我来说)时正在观看视频。有问题的视频是here,相关部分从2:30开始,感兴趣的人。但基本上,这个人说F#使得使用数组变得很尴尬,设计人员故意这样做,因为列表更容易“添加和追加”。
立即浮现在脑海中的问题:在不可改变的语言中添加一些应该不赞成的东西并不容易吗?具体来说,我正在考虑C#的列表,您可以在其中执行List.Add(obj);
之类的操作并改变列表。使用数组,您必须创建一个全新的数组,但这也是在不可变语言中需要发生的事情。
那么为什么F#的设计师更喜欢列表呢?列表和数组之间的不可变环境的根本区别是什么?我错过了什么? F#中的列表是真正链接的列表吗?
答案 0 :(得分:48)
我不同意“F#使得使用数组变得尴尬”。实际上,与大多数语言相比,F#使得使用数组非常好。
例如,F#具有文字数组构造:let arr = [|1;2;3;4;|]
。在阵列上甚至可能更酷,模式匹配:
match arr with
| [|1;2;_;_|] -> printfn "Starts with 1;2"
| [|_;_;3;4|] -> printfn "Ends with 3;4"
| _ -> printfn "Array not recognized"
至于为什么不可变的单链表在F#这样的函数式编程中是首选的,有很多话要说,但简短的回答是它允许O(1)提前效率,并允许实现共享节点,所以它很容易记忆。例如,
let x = [2;3]
let y = 1::x
这里y是通过在x之前加1来创建的,但x既不修改也不复制,因此y非常便宜。我们可以看到这有多可能,因为x指向最初构造的列表的头部2,并且只能向前移动,并且因为它指向的列表的元素不能被突变,所以它不会与y共享节点的问题。
答案 1 :(得分:30)
在功能语言中,列表通常是单链表。即没有必要复制完整列表。相反,前置(通常称为cons)是一个O(1)操作,你仍然可以使用旧列表,因为列表是不可变的。
答案 2 :(得分:13)
首先,数组是相当低级的数据结构,只有在创建数组时才知道数组的长度,它们才真正有用。情况并非如此,这就是C#程序员使用System.Collections.Generic.List<T>
和F#程序员使用F#list<T>
的原因。
F#更喜欢自己的功能列表而不是使用.NET List<T>
的原因是函数式语言更喜欢不可变类型。您可以通过编写list.Add(x)
来创建包含添加到前端的项目的新列表,而不是通过调用let newList = x::list
来修改对象。
我也同意Stephen的观点,即在F#中使用数组并不尴尬。如果您知道正在使用的元素数量,或者您正在转换某些现有数据源,那么使用数组非常简单:
/ You can create arrays using `init`
let a = Array.init 10 (fun i -> (* calculate i-th element here *) )
// You can transform arrays using `map` and `filter`
a |> Array.map (fun n -> n + 10)
|> Array.filter (fun n -> n > 12)
// You can use array comprehensions:
let a2 = [| for n in a do
if n > 12 then yield n + 10 |]
这与处理列表基本相同 - 您可以使用列表推导[ ... ]
和列表处理函数,例如List.map
等。差异确实出现在初始化列表/数组时。
答案 3 :(得分:9)
F#使得使用数组
变得尴尬
F#提供了许多功能,使得使用数组比使用其他语言更容易,包括数组文字,数组模式和高阶函数。
立即浮现在脑海中的问题:在不可改变的语言中添加一些不应该被忽视的东西并不容易吗?
我相信你误解了这句话的含义。当人们谈论在纯功能数据结构的上下文中进行前置和附加时,他们指的是创建一个新集合,该集合是使用现有集合派生(并与其大部分内部共享)。
那么为什么F#的设计师更喜欢列表?
F#从OCaml继承了一些与列表相关的功能,这些功能从标准ML和ML继承它们,因为单链接的不可变列表在其应用程序域(元编程)的上下文中非常有用,但我不会说F#的设计者更喜欢列表。
列表和数组之间不可变环境的根本区别是什么?
在F#中,列表提供O(1)前置和附加以及O(n)随机访问,而阵列提供O(n)前置和附加以及O(1)随机访问。数组可以变异,但列表不能。
我错过了什么?
纯功能数据结构的基础知识。阅读冈崎。
F#中的列表真的是链接列表吗?
是。具体来说,单链接的不可变列表。实际上,在某些ML中,list
类型可以定义为:
type 'a list =
| ([])
| (::) of 'a * 'a list
这就是::
运算符是构造函数而不是函数的原因,因此您不能像(::)
那样编写(+)
。
答案 4 :(得分:5)
F#列表更像是以下数据结构 - 单个链表:
public class List<T> {
public List(T item, List<T> prev) { /*...*/ }
public T Item { get; }
public List<T> Prev { get; }
}
因此,当创建新列表时,它实际上是创建一个单一节点,其中引用了上一个列表的第一个元素,而不是复制整个数组。