共享表示如果临时数据将被多次使用,则会存储该数据。也就是说,函数只评估它的参数一次。一个例子是:
let x = sin x in x * x
其他哪些功能有助于共享以及它们如何与实际程序执行IO的需求相互作用?
答案 0 :(得分:7)
共享是一种平等:指针平等。在Haskell的价值土地(语义解释)中,没有共享这样的东西。如果它们具有Eq
的实例,则值只能相等,然后将“相等”定义为二元关系(==)
。
共享因此通过引用由于实现而不是语义而存在的底层指针相等来逃避语义解释。不过,这有时会产生副作用。不幸的是,由于Haskell是由其语义解释定义的,因此共享的使用是未定义的行为。它与特定的实现有关。一些图书馆使用共享,因此行为与GHC有关。
但是有一种内置的共享机制。这由StableName
接口公开。我们使用StableName a
生成makeStableName :: a -> IO (StableName a)
个对象并拥有instance Eq (StableName a)
- 因此StableName
为任何类型引入了某种相等性。
StableName
等于几乎指针相等。引用Haddock文档
如果
sn1 :: StableName
和sn2 :: StableName
以及sn1 == sn2
,则sn1
和sn2
是通过调用同一对象上的makeStableName
创建的。
请注意,这只是一个 if 语句,而不是当且仅当。事实上,两个东西可以是“指针等价”,但仍然有不同的稳定名称有时是(a)强迫Haskell留给GC的灵活性和(b)允许StableName
s存在于任何Haskell中的漏洞即使在实现中没有“指针相等”这样的东西也是如此。
这些StableName
仍然没有语义含义,但是因为它们是在IO
monad中引入的,所以“OK”。相反,它们可能被认为是在任何类型上实现可能的最小(最具体)平等关系的(具有讽刺意味的)不稳定子集。
答案 1 :(得分:5)
共享功能编程的最明显的例子来自Clean,它基于图形重写。在那里,计算是指DAG,因此我们可以将表达式(sin x) * (sin x)
视为
(*)
/ \
sin x sin x
图形重写系统有一个明确的共享概念,因此我们可以将该计算表示为
(*)
/ \
\ /
sin x
将乘法节点指向同一节点,从而共享sin x
的计算。术语重写系统没有如此明确的共享概念,但优化仍然是相关的。在GHC中,有时可以表达与局部变量的共享,例如重写
f x = (sin x) * (sin x)
到
f x = sinx * sinx
where sinx = sin x
但是由于这两者在语义上是等价的,所以编译器可以自由地以相同的方式实现,无论是否共享。根据我的理解,GHC通常会保留与局部变量一起引入的共享,有时会引入它(向第一个添加共享),但是在术语重写系统中没有正式的共享表达,要么行为依赖于实现(参见tel的评论和回答)。
共享触摸IO,因为无法共享副作用值。如果我们考虑一种不纯的语言,那么
之间就存在差异(string-append (read-line)
(read-line))
和
(let ((s (read-line)))
(string-append s s))
第一个执行IO动作两次,从用户请求两行并附加它们;第二个“共享”IO动作,执行一次并将其附加到自身。通常,共享纯计算可以在不改变程序结果的情况下减少执行时间,同时共享副作用值(可能随时间变化或与用户交互的值)会改变结果。为了让编译器自动共享计算,它需要知道它们是纯粹的,因此减少评估的数量并不重要。 Clean以独特的类型做到这一点; IO操作具有类型属性UNQ,它告诉编译器不应该共享它。 Haskell对IO monad的做法有所不同。
答案 2 :(得分:1)
您的示例不是共享的示例 - 它只是将值与自身相乘(然后将原始值抛弃)。
共享是指数据结构的某些部分在较大的数据结构或不同的数据结构中出现两次的情况。例如:
p = (1, 2)
pp = (p, p) -- p is shared between the first and second component of pp
xs = [1, 2, 3]
ys = 0::1::xs
zs = 5::xs -- ys and zs share the same tail
在内存中,这种共享将导致DAG结构。