我正在从
Java背景学习Haskell.当我编程Java时,我觉得我非常了解对象如何布置在内存中以及后果.例如,我确切地知道java.lang.String和java.util.LinkedList如何工作,因此我知道我应该如何使用它们.与Haskell我有点迷失了.例如,(:)如何工作?我应该关心吗?是否指定在某处?
解决方法
简短的答案是否定的.在Haskell中进行编程时,您应该将数据结构视为纯数学对象,而不用担心它们在内存中的表现.这样做的原因是,在没有副作用的情况下,除了创建它的功能以及可以用来提取其构建的更简单的部分的函数之外,数据真的没有.
要查看有关数据构造函数的信息,如(:)或任何其他术语,请在GHCi中使用:type(或只是:t为简称)命令:
: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 a5 (Cons a4 (Cons a3 (Cons a2 (Cons a1 Nil)))))
这只是一棵树,没有分支,这就是清单的定义!而获取a1的唯一方法是依次弹出每个Conss;因此访问最后一个元素是O(n),而访问头是恒定的时间.一旦您可以根据自己的定义对数据结构进行这种推理,那么您就可以完成.