当具有相同名称但针对不同集合的函数产生不同的副作用时,它是函数语言中的库错误吗?
Is it a library bug in a functional language when a function with the same name but for different collections produces different side effects?
我正在使用 Scala 2.13.1 并在工作表中评估我的示例。
首先,我定义了两个函数,return a 到 (z-1) 的范围为流或分别是惰性列表。
def streamRange(a: Int, z: Int): Stream[Int] = {
print(a + " ")
if (a >= z) Stream.empty else a #:: streamRange(a + 1, z)
}
def lazyListRange(a: Int, z: Int): LazyList[Int] = {
print(a + " ")
if (a >= z) LazyList.empty else a #:: lazyListRange(a + 1, z)
}
然后我调用这两个函数,获取 3 个元素的 Stream/LazyList 并将它们转换为列表:
streamRange(1, 10).take(3).toList // prints 1 2 3
lazyListRange(1, 10).take(3).toList // prints 1 2 3 4
这里我再做一次:
val stream1 = streamRange(1, 10) // prints 1
val stream2 = stream1.take(3)
stream2.toList // prints 2 3
val lazyList1 = lazyListRange(1,10) // prints 1
val lazyList2 = lazyList1.take(3)
lazyList2.toList // prints 2 3 4
打印1是因为访问了函数,打印语句在开头。不出意外。
但我不明白为什么额外的 4 是为惰性列表而不是流打印的。
我的假设是,在 3 与下一个函数调用连接的点上,LazyList 版本访问该函数,而在 Stream 版本中不访问该函数。否则 4 不会被打印出来。
这似乎是意外行为,至少是意外的。但是,这种副作用的差异是否会被视为错误或只是 Stream 和 LazyList 评估中的详细差异。
Stream
使用 Deferer
:
实现 #::
implicit def toDeferrer[A](l: => Stream[A]): Deferrer[A] = new Deferrer[A](() => l)
final class Deferrer[A] private[Stream] (private val l: () => Stream[A]) extends AnyVal {
/** Construct a Stream consisting of a given first element followed by elements
* from another Stream.
*/
def #:: [B >: A](elem: B): Stream[B] = new Cons(elem, l())
/** Construct a Stream consisting of the concatenation of the given Stream and
* another Stream.
*/
def #:::[B >: A](prefix: Stream[B]): Stream[B] = prefix lazyAppendedAll l()
}
其中 Cons
:
final class Cons[A](override val head: A, tl: => Stream[A]) extends Stream[A] {
而 LazyList
用它自己的 Deferer
实现 #::
:
implicit def toDeferrer[A](l: => LazyList[A]): Deferrer[A] = new Deferrer[A](() => l)
final class Deferrer[A] private[LazyList] (private val l: () => LazyList[A]) extends AnyVal {
/** Construct a LazyList consisting of a given first element followed by elements
* from another LazyList.
*/
def #:: [B >: A](elem: => B): LazyList[B] = newLL(sCons(elem, l()))
/** Construct a LazyList consisting of the concatenation of the given LazyList and
* another LazyList.
*/
def #:::[B >: A](prefix: LazyList[B]): LazyList[B] = prefix lazyAppendedAll l()
}
其中 sCons
:
@inline private def sCons[A](hd: A, tl: LazyList[A]): State[A] = new State.Cons[A](hd, tl)
和Cons
:
final class Cons[A](val head: A, val tail: LazyList[A]) extends State[A]
这意味着在定义级别上:
Steam
懒惰地评估它尾巴的 creation
LazyList
懒惰地评估其尾巴的 content
在 side-effects 中差异很明显...如果这些都不是为这些而设计的。
如果你想处理潜在的无穷无尽的计算序列,请使用合适的流媒体库:Akka Streams、FS2、ZIO Streams。 Build-in streams/lazy 列表是为纯计算而制作的,如果您进入不纯的目录,您应该假设没有提供关于副作用的保证。
我正在使用 Scala 2.13.1 并在工作表中评估我的示例。
首先,我定义了两个函数,return a 到 (z-1) 的范围为流或分别是惰性列表。
def streamRange(a: Int, z: Int): Stream[Int] = {
print(a + " ")
if (a >= z) Stream.empty else a #:: streamRange(a + 1, z)
}
def lazyListRange(a: Int, z: Int): LazyList[Int] = {
print(a + " ")
if (a >= z) LazyList.empty else a #:: lazyListRange(a + 1, z)
}
然后我调用这两个函数,获取 3 个元素的 Stream/LazyList 并将它们转换为列表:
streamRange(1, 10).take(3).toList // prints 1 2 3
lazyListRange(1, 10).take(3).toList // prints 1 2 3 4
这里我再做一次:
val stream1 = streamRange(1, 10) // prints 1
val stream2 = stream1.take(3)
stream2.toList // prints 2 3
val lazyList1 = lazyListRange(1,10) // prints 1
val lazyList2 = lazyList1.take(3)
lazyList2.toList // prints 2 3 4
打印1是因为访问了函数,打印语句在开头。不出意外。
但我不明白为什么额外的 4 是为惰性列表而不是流打印的。
我的假设是,在 3 与下一个函数调用连接的点上,LazyList 版本访问该函数,而在 Stream 版本中不访问该函数。否则 4 不会被打印出来。
这似乎是意外行为,至少是意外的。但是,这种副作用的差异是否会被视为错误或只是 Stream 和 LazyList 评估中的详细差异。
Stream
使用 Deferer
:
#::
implicit def toDeferrer[A](l: => Stream[A]): Deferrer[A] = new Deferrer[A](() => l)
final class Deferrer[A] private[Stream] (private val l: () => Stream[A]) extends AnyVal {
/** Construct a Stream consisting of a given first element followed by elements
* from another Stream.
*/
def #:: [B >: A](elem: B): Stream[B] = new Cons(elem, l())
/** Construct a Stream consisting of the concatenation of the given Stream and
* another Stream.
*/
def #:::[B >: A](prefix: Stream[B]): Stream[B] = prefix lazyAppendedAll l()
}
其中 Cons
:
final class Cons[A](override val head: A, tl: => Stream[A]) extends Stream[A] {
而 LazyList
用它自己的 Deferer
实现 #::
:
implicit def toDeferrer[A](l: => LazyList[A]): Deferrer[A] = new Deferrer[A](() => l)
final class Deferrer[A] private[LazyList] (private val l: () => LazyList[A]) extends AnyVal {
/** Construct a LazyList consisting of a given first element followed by elements
* from another LazyList.
*/
def #:: [B >: A](elem: => B): LazyList[B] = newLL(sCons(elem, l()))
/** Construct a LazyList consisting of the concatenation of the given LazyList and
* another LazyList.
*/
def #:::[B >: A](prefix: LazyList[B]): LazyList[B] = prefix lazyAppendedAll l()
}
其中 sCons
:
@inline private def sCons[A](hd: A, tl: LazyList[A]): State[A] = new State.Cons[A](hd, tl)
和Cons
:
final class Cons[A](val head: A, val tail: LazyList[A]) extends State[A]
这意味着在定义级别上:
Steam
懒惰地评估它尾巴的 creationLazyList
懒惰地评估其尾巴的 content
在 side-effects 中差异很明显...如果这些都不是为这些而设计的。
如果你想处理潜在的无穷无尽的计算序列,请使用合适的流媒体库:Akka Streams、FS2、ZIO Streams。 Build-in streams/lazy 列表是为纯计算而制作的,如果您进入不纯的目录,您应该假设没有提供关于副作用的保证。