An endless list makes the REPL work for a long time and this is intentional (updated title)
In Haskell, because of lazy evaluation, this is a legitimate expression:

Daml doesn’t have lazy evaluation, as I was told recently. The same expression sends the REPL into an infinite loop:

Is that intentional? (My guess is it isn’t.)
BTW, why is it that Daml doesn’t have lazy evaluation?
Is that intentional? (My guess is it isn’t.)
It is.
BTW, why is it that Daml doesn’t have lazy evaluation?
I think the motives have changed over time, but nowadays I would say that proving properties of programs is easier if you do not have to cope with “actually nonterminating but kind of not if you really think about it” constructs like your sample. Additionally, all contract data must be strict, so the usefulness of lazy evaluation would be limited to ephemeral data structures, if it was supported at all.
I understand the second part, but not why it is desirable to send the REPL into an infinite loop instead of throwing an error.
why it is desirable to send the REPL into an infinite loop instead of throwing an error.
This is less of a deliberate decision and more a consequence of the fact that we make no attempts at checking for termination. In general, this is of course undecidable (halting problem and all that). You could detect some specific cases like [1..] but so far it hasn’t seemed worth the effort and arguably might lead to a false sense of security where users expect that we always detect this.
Ok, understood, thank you.
BTW, why is it that Daml doesn’t have lazy evaluation?
In addition to what @Stephen already mentioned, I think there are two reasons that still apply:
- A lot more programmers are used to strict evaluation. The barrier of entry for learning Daml is already relatively high so matching the evaluation model people are used to has some advantages in that regard. It does cause confusion for people coming from Haskell but at least at this stage, that’s not how most people come towards Daml.
- Implementing efficient runtimes/interpreters for lazy languages is usually significantly more complex and much less well explored than doing the same for strict languages. Outside of the Haskell runtime, there are relatively few examples that do this well. Interpreters for strict languages on the other hand exist in countless numbers and even implementing a reasonably efficient one is still a relatively well-explored field.
Yes, sounds reasonable, thank you
Just one more question, going back to my starting point.
The signature of the take function is like this:
: Int → [a] → [a]
Take the first n elements of a list.
This means, the REPL typechecks the [1..] expression as a list when interpreting the take 3 [1..] expression, is that correct given the fact that without lazy evaluation the [1..] part doesn’t make any sense?
Typechecking is a static property independent of execution. [1..] is perfectly well typed as [Int].
Ranges like [1..], [1,3,..], [1..5], [1,3..11] are just syntactic sugar for enumFrom, enumFromThen, enumFromTo, and enumFromThenTo, respectively.
So take 3 [1..] is the same as take 3 (enumFrom 1). enumFrom : a -> [a] so [1..] does indeed have type [Int].
If you look at how this is implemented, you see that [1..] == enumFrom 1 == enumFromTo 1 maxBound == eftInt 1 maxBound where
eftInt : Int -> Int -> [Int]
-- [x1..x2]
eftInt x y | x > y = []
| x == y = [x]
| otherwise = x :: eftInt (x + 1) y
maxBound = 0x7FFFFFFFFFFFFFFF
In other words, your expressions will terminate. It’ll just take a while. You recurse roughly 10^19 times.
Oh yes, thank you. In this case the correct description is not that the REPL gets into an infinite loop, but that it will work for a long time.
Now I understand it.
In other words, your expressions will terminate. It’ll just take a while. You recurse roughly 10^19 times.
Did it pretty quickly on my machine (in that the heap blew up before it terminated). More interestingly it:
- Ran
- Appeared to return to console without any output (but started ignoring any input)
- Obvious a process was still running because my laptop fan was going wild
- Then the heap blew up
- Then gave me a terminal where I couldn’t actually do anything anymore
$ daml repl
daml> take 3 [1..]
daml> Uncaught error from thread [Repl-akka.actor.default-dispatcher-11]: Java heap space, shutting down JVM since 'akka.jvm-exit-on-fatal-error' is enabled for ActorSystem[Repl]
java.lang.OutOfMemoryError: Java heap space
at java.base/java.util.Arrays.copyOf(Arrays.java:3690)
at java.base/java.util.ArrayList.grow(ArrayList.java:237)
at java.base/java.util.ArrayList.grow(ArrayList.java:242)
at java.base/java.util.ArrayList.add(ArrayList.java:485)
at java.base/java.util.ArrayList.add(ArrayList.java:498)
at com.daml.lf.speedy.Speedy$KPushTo.execute(Speedy.scala:1130)
at com.daml.lf.speedy.Speedy$Machine.run(Speedy.scala:391)
at com.daml.lf.engine.script.Runner.stepToValue$1(Runner.scala:465)
at com.daml.lf.engine.script.Runner.$anonfun$runWithClients$64(Runner.scala:829)
at com.daml.lf.engine.script.Runner$$Lambda$903/0x000000080179f040.apply(Unknown Source)
at scala.concurrent.Future.$anonfun$flatMap$1(Future.scala:307)
at scala.concurrent.Future$$Lambda$273/0x0000000801409840.apply(Unknown Source)
at scala.concurrent.impl.Promise.$anonfun$transformWith$1(Promise.scala:41)
at scala.concurrent.impl.Promise$$Lambda$274/0x000000080140a840.apply(Unknown Source)
at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:64)
at akka.dispatch.BatchingExecutor$AbstractBatch.processBatch(BatchingExecutor.scala:56)
at akka.dispatch.BatchingExecutor$BlockableBatch.$anonfun$run$1(BatchingExecutor.scala:93)
at akka.dispatch.BatchingExecutor$BlockableBatch$$Lambda$271/0x00000008013f0c40.apply$mcV$sp(Unknown Source)
at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)
at scala.concurrent.BlockContext$.withBlockContext(BlockContext.scala:85)
at akka.dispatch.BatchingExecutor$BlockableBatch.run(BatchingExecutor.scala:93)
at akka.dispatch.TaskInvocation.run(AbstractDispatcher.scala:48)
at akka.dispatch.ForkJoinExecutorConfigurator$AkkaForkJoinTask.exec(ForkJoinExecutorConfigurator.scala:48)
at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)
daml> take 3 [1..]
daml> take 3 [1,2,3,4]
daml>
I’ll file a bug report. I think the REPL should either fail completely or give me back a usable terminal but not this. It’s deceptive in the output but the line daml> Uncaught error... actually was daml> before the heap blew up and started printing to the terminal.
Edit: Opened an issue
github.com/digital-asset/damldaml repl doesn't recover when a non-terminating function fails
Inspired by [György](https://discuss.daml.com/t/an-endless-lit-makes-the-repl-wo…