Haskell的GHCi打印惰性序列,���Scala的REPL不会。

3
我希望您能输出一串数字,但以下代码只输出序列中的第一个数字:
for ( n <- Stream.from(2) if n % 2 == 0 ) yield println(n)
2
res4: scala.collection.immutable.Stream[Unit] = Stream((), ?)

在Haskell中,以下代码会一直打印数字直到被打断,我希望在Scala中实现类似的行为:
naturals = [1..]
[n | n <- naturals, even n]
[2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,

基本上,这个表达式构造了一个 Stream()。因此,你只会得到一个值 2 的输出,这是因为 Scala REPL 需要打印 res4: .... = Stream((), ?),而这只需要计算一个 ()。如果你删除 yield,它将成为一个不同的循环,你应该会得到一个无限流的偶数输出。 - Sassa NF
3
你的Haskell示例不会打印数字。你可以通过从源代码编译它来演示这一点,而不是使用repl——它似乎什么也没做。Haskell repl只是为了方便而显示内容,而Scala repl则不会。在Haskell中,用于打印无限偶数序列的代码看起来更像是forM [n | n <- naturals, even n] (putStrLn . show),其中组合函数(putStrLn . show)包含实际将数字打印到屏幕上的逻辑(与Scala中的println类似)。 - KChaloux
我从所有答案中找到了答案:(1) 在REPL中,Scala不会默认打印无限流,因此您需要显式地要求其打印。(2) 要显式要求它打印(无论是在REPL还是在程序中):如果s是一个Stream,那么使用s foreach println逐行打印s的元素。(3) 但是Scala解释器很愚蠢,按下Ctrl-C会终止解释器(而不是正在运行的循环),因此如果s太长(或无限),您可能只想打印有限数量的项。 - ShreevatsaR
3个回答

4

与其只使用println(为什么要获取无限序列的Unit?),我们可以使用yield:

for ( n <- Stream.from(2) if n % 2 == 0 ) println(n)

如果您真的想要那个无限序列单元,强制结果:
val infUnit = for ( n <- Stream.from(2) if n % 2 == 0 ) yield println(n)
infUnit.force // or convert to any other non-lazy collection

尽管最终它会导致程序崩溃(由于材料化序列的长度过大)。

结果的类型应该是List[Int],但我正在尝试使用println来查看发生了什么。请您更新代码,以便输出一个整数列表,就像Haskell代码一样。 - user3248346
@I.K. 你真的想要一个“无限”的列表吗?它应该被强制执行吗? - om-nom-nom
我想我搞混了问题。澄清一下:用 Haskell,我不需要指定任何附加语义来生成一个无限的整数列表。枚举 [1..] 很好地完成了这个任务。当我在列表推导式中使用该惰性列表时,它会一直生成整数,直到 Haskell 解释器被中断。我希望在 Scala 中也有同样的行为。我以为 Stream.from(1) 会给我那个无限惰性序列(没有强制计算,或指定任何附加语义),但它似乎并不是这样。 - user3248346
最终,Scala代码应该复制Haskell的行为并返回List[Int]。 - user3248346
1
我原以为 Stream.from(1) 会给我一个无限的惰性序列(不需要强制或指定任何其他语义),这真的让我困惑。Stream.from ... 确实会给你一个惰性序列——它是如此惰性,以至于它不会完全打印自己。现在,为了将其打印出来,您需要消除惰性,换句话说,强制它。是的,它们之间确实存在一些区别——Haskell 默认是惰性的,Scala 默认是急切的,并且与 Haskell 不同,它在 急切 列表(又名 List)和 惰性 列表(又名 Stream)之间进行区分。 - om-nom-nom

2
一个for推导式的结果类型是与第一个从句中集合类型相同的集合。 请参阅flatMap函数签名
因此,一个

for ( n <- Stream.from(2) ..... 

它是一个类型为 Stream[_] 的集合,采用惰性计算方式,因此你需要提取元素值或操作。

看一下结果类型:

scala> :type for( n <- Stream.from(2)) yield n
scala.collection.immutable.Stream[Int]

scala> :type for( n <- List(1,2,3)) yield n
List[Int]

scala> :type for( n <- Set(1,2,3)) yield n
scala.collection.immutable.Set[Int]

要打印数字直到中断,请尝试以下操作:
Stream.from(2).filter(_ % 2 == 0) foreach println

它的类型保证了它会工作:

scala> :type Stream.from(2).filter(_ % 2 == 0) foreach println
Unit

1
我想你的意思是:

我认为你的意思是:

for (n <- Stream.from(2) if n % 2 == 0) yield n

这是您想要的集合。然而,与Haskell不同,Scala在打印懒列表(Stream)时不会评估所有成员。但是,您可以使用.toList将其转换为非懒列表。但是,在Scala中,您将看不到与Haskell相同的无限打印行为,因为它会在打印任何内容之前尝试构建整个(无限)列表。

基本上,使用内置的toString基础结构打印无限列表时,Scala无法获得与Haskell相同的语义和行为组合。

P.S.

for (n <- Stream.from(2) if n % 2 == 0) yield n

可以更简短地表示为

Stream.from(2).filter(_ % 2 == 0)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接