共享是指如果数据将被多次使用,则会存储临时数据。也就是说,函数只评估其参数一次。一个例子是:
let x = sin x in x * x
其他哪些特性有助于“共享”,它们将如何与需要执行IO的实际程序进行交互?
共享是指如果数据将被多次使用,则会存储临时数据。也就是说,函数只评估其参数一次。一个例子是:
let x = sin x in x * x
其他哪些特性有助于“共享”,它们将如何与需要执行IO的实际程序进行交互?
分享是一种平等,即指针平等。在 Haskell 的值领域(语义解释)中,没有这样的分享。只有当它们具有 Eq
的实例并且“相等性”被定义为二元关系 (==)
时,值才能相等。
因此,分享通过引用这种基础的指针平等来逃避语义解释,由于实现而存在,而不是语义。但有时这是一个有用的副作用。不幸的是,由于 Haskell 是由其语义解释来定义的,使用共享是未定义的行为。它与特定的实现有关。一些库使用共享,因此与 GHC 相关联。
然而,有一种内置的共享机制。这是通过 StableName
接口公开的。我们使用 makeStableName :: a -> IO (StableName a)
生成 StableName a
对象,并有一个 instance Eq (StableName a)
——因此 StableName
为 任何 类型引入了某种形式的相等性。
StableName
的相等性几乎等同于指针相等性。引用Haddock文档的话来说,如果sn1 :: StableName
和sn2 :: StableName
以及sn1 == sn2
,那么sn1
和sn2
是通过对同一对象调用makeStableName
而创建的。请注意,这只是一个“if”语句,而不是“if and only if”。有时,两个东西可以是“指针等效”的,但仍然具有不同的稳定名称,这是由Haskell留给GC的灵活性所迫使的,并且是一个漏洞,即使在实现中根本没有“指针相等性”,StableName
也可以存在于任何Haskell实现中。这些StableName
仍然没有语义意义,但因为它们是在“IO”单子中引入的,所以“没问题”。相反,它们可能被认为是在任何类型上实现最小(最具体)等式关系的(具有讽刺意味的)不稳定子集。在函数式编程中,共享的最明显的例子来自于基于图重写的Clean语言。在那里,一项计算涉及到DAG,因此我们可以将表达式(sin x) * (sin x)
视为
(*)
/ \
sin x sin x
(*)
/ \
\ /
sin x
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))
where sinx = sin x
的例子没有必要引入共享。纯度保证了无论被乘的两个值是否“指针相等”,表达式的最终值都是相同的。现在,共享这个值显然是一种优化,GHC有时会“向上浮动”来引入这种共享。话虽如此,如果我们想要的话,我们也可以计算 sin x
,将其复制到两个地址,然后再进行乘法运算。在Haskell的语义中,共享是不可观察的。StableName
是你能够接近而不涉及实现细节的最接近方法。 - J. Abrahamsonsin x
)的能力。 - isturdylet
和 where
似乎可以保证它。 - J. Abrahamson你的例子并不是共享的例子--它只是将一个值乘以自身(然后丢弃原始值)。
共享是指一个数据结构的某部分在较大的数据结构中出现两次,或者在不同的数据结构中出现两次。例如:
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