我来自Java背景,正在学习Haskell。在编写Java代码时,我感觉对对象在内存中的布局以及其相关后果有着深刻理解。例如,我精确知道java.lang.String
和java.util.LinkedList
是如何工作的,因此我知道应该如何使用它们。但是在Haskell中,我有些迷茫。例如,(:)
是如何工作的?我应该关心吗?是否有规定说明它的用法呢?
我来自Java背景,正在学习Haskell。在编写Java代码时,我感觉对对象在内存中的布局以及其相关后果有着深刻理解。例如,我精确知道java.lang.String
和java.util.LinkedList
是如何工作的,因此我知道应该如何使用它们。但是在Haskell中,我有些迷茫。例如,(:)
是如何工作的?我应该关心吗?是否有规定说明它的用法呢?
:Prelude> :type (:)
(:) :: a -> [a] -> [a]
这告诉你,(:)
构造函数(读作“cons”)接受任何类型的值和相同类型的列表,并返回相同类型的列表。您还可以通过使用 :info
命令获取更多信息。这将显示数据定义的外观:
Prelude> :info (:)
data [] a = ... | a : [a] -- Defined in GHC.Types
infixr 5 :
这告诉你(:)
是构造函数,它将元素添加到现有列表中。
我强烈推荐使用Hoogle,不仅可以通过名称查找内容,还可以进行相反类型的搜索;您知道要查找的函数的签名,并想找出是否已经有人为您编写了该函数。 Hoogle很好用,因为它提供描述和示例用法。
我上面说过,知道数据在内存中的表示方式并不重要... 但是,您应该了解正在处理的数据的形状,以避免做出性能差的决策。 Haskell中的所有数据都是归纳定义的,意味着它具有类似树形的形状,可以递归地展开。您可以通过查看其定义来确定数据的形状;一旦您知道如何阅读此内容,那么关于其性能特征就真的没有什么隐藏的东西了:
data MyList a = Nil | Cons a (MyList a)
从定义中可以看出,获得新的MyList
的唯一方式是使用Cons
构造函数。如果您多次使用此构造函数,最终会得到大致如下形状的内容:
(Cons a5 (Cons a4 (Cons a3 (Cons a2 (Cons a1 Nil)))))
列表就是一棵没有分支的树!访问最后一个元素的唯一方法是逐个弹出每个Cons
,因此访问最后一个元素的时间复杂度为O(n),而访问头部的时间复杂度为常数时间。一旦你能够根据数据结构的定义进行这种推理,你就可以了解它们。
简短回答是“不需要”,你不需要了解数据布局,但了解复杂度的知识会很有用。
然而,要编写高度优化的Haskell程序,对堆中数据结构的形状有良好的工作知识是必不可少的。一个帮助的工具是“vacuum”,它可以呈现Haskell数据结构的图表,以展示它们的布局。
以下是一些例子:
$ view "hello"
$ view (IntMap.fromList $ zip [1..10] [1..])
这是一个使用IT技术的工具,以下是使用方法的简短视频:http://www.youtube.com/watch?v=X4-212uMgy8