Develop the three great virtues of a programmer: laziness, impatience, and hubris
raganwald.com
raganwald.com
I don't see that "types" solve this problem, as there are far more varieties of function than lazy and eager, which themselves are not always consistent. Perhaps Eiffel is a better guide for the sort of pre-condition / post-condition assumptions of a function, if you want to go down this route.
https://en.wikipedia.org/wiki/Eiffel_(programming_language)#...
The program must verify the property at runtime and create a witness to the proof, which it can then pass to functions to say that it has done the necessary work.
So I guess the answer is yes and no. I would say the whole point of dependently-typed languages is that we can statically determine that somebody is maintaining correctness, be it at compile time or at run time.
If they are enforced at compile time, they are static.
If you in fact want to receive a iterable, then you should not signal that you expect a list.
The keyword yield is in fact a nice language integrated mechanism to create a iterable interface.
In languages that lacking this it resorts to using much more verbose mechanism.
Some systems can figure out by themselves when lazy evaluation is useful. Consider this SQL:
SELECT * FROM tab ORDER BY score DESC LIMIT 1;
Assume there's no index for "score". The brute-force approach is to sort "tab" by "score", then take the first element. This is O(N log N) and requires generating a temporary file. Most SQL implementations are smarter than that, and will make one linear pass, for O(N) time. Some will do that for small LIMIT values greater than 1.Also, if others would like to learn more about the ORDER BY...LIMIT optimization, it's called a top-K sort.
When dealing with functional programming (e.g. pure functions, immutable data-structures, referential transparency), sooner or later you need to deal with recursive algorithms and data-structures. Usually tail-recursive, so they take constant stack space, but recursive nonetheless. And when dealing with recursive algorithms or data-structures, you end up wanting laziness, because otherwise you cannot express the algorithms that you want to express.
You mentioned LIMIT. This operation can be expressed in terms of "foldRight", a really, really useful operation for functional programming. But here's the catch: if it's not lazy, then it's not going to work for infinite streams (and it should), it will need O(n) memory and depending on implementation, it will probably blow up your stack. My current language is Scala. And in Scala the "foldRight" implemented on the standard collections is totally useless. Which is a pity really.
Going back to your original assertion, laziness isn't really about preventing a result to be evaluated, although sometimes that's a useful side-effect. No, lazy evaluation is about being able to short-circuit the iteration ;-)
This might be tangential to your point, but tail recursion doesn't really work with laziness, e.g. foldl in Haskell takes linear space.
Tail-recursive algorithms are those that can use constant memory (stack or heap) and which in a language like Scala can be translated to usage of a loop or a trampoline, whereas the actually recursive algorithms are those that really need some sort of stack to work and that grows directly proportional to the input size. Usage of a stack is the definition of recursivity from algorithms books, like Cormen et al. For example doing an in-depth traversal of a balanced tree will really, really need a O(log2 n) stack, regardless if the evaluation is strict or lazy or whatever language tricks you can pull.
Does that make any sense? :-)
Since I wanted to talk about laziness and not priority queues, I eschewed trying to write the fastest possible implementation in favour of the simplest implementation that is still recognizable as “cross off every nth number.”
If you feel that the implementation is inefficient, I agree. But if you feel it isn’t actually the Sieve of Eratosthenes in some fundamental way, please explain the difference in a little more detail, it would be instructive to share with HN and me.
Sometimes, Y and Z would just get in the way. But then again, sometimes their omission distracts the reader and provokes a lot of bikeshedding.
There is no easy answer, and I often get this balance wrong.
This is, I think, what makes some writers so brilliant: They find a way to present “X” in a clear and strong way without making an absolute hash of “Y” and “Z.”
Such writing is a treasure.
Your code maintains a nested "stack" of iterators: the outer iterator is a nullEveryNth() that consumes from a nullEveryNth()...that eventually consumes from a range(). There's one for each prime encountered so far. Each iterator passes through a number if it's not divisible by the corresponding prime. That's trial division.
You should be able to observe that your lazy implementation is asymptotically slower than the eager implementation (not just slower by a constant factor, but the running time grows more quickly).
The paper by O'Neill is a good read (although a little dense), you should check it out! https://www.cs.hmc.edu/~oneill/papers/Sieve-JFP.pdf
N.B. I think you added skipFirst to nullEveryNth since publishing this post, but it doesn't help get the algorithm down to the O(n log n log log n) performance that O'Neill describes.
The thing is... I was aiming for the most literal translation of the human-readable instructions, which conveniently set things up to introduce the bug in using n eager version of `compact`, which segued into discussing the problem of mixing eager and lazy code in a language that uses the exact same interface for both.
The code I gave mimics what happens when a child is taught the method. They look at the list of numbers and count one TWO (cross off) one TWO (cross off)... one two THREE (cross off) one two THREE (cross off)... one two three four FIVE (cross off) one two three four FIVE (cross off) and so on.
It’s quite right and proper to advance from there to talk about how you can do multiplication in a faster way to derive FIVE, TEN, FIFTEEN, TWENTY and so on, and to line up the grid of numbers in such a way that you have faster access to an arbitrary number than traversing the list sequentially, and then you have the performance discussed.
But all of that would encumber the story I was telling.
As for the skipFirst, I added that when I decided to cite O’Neill’s paper, as it made for a literal translation of the introductory explanation she gives of the algorithm.
---
So now, I’m going to write a’proper' version in JavaScript, I look forward to your feedback.
(Disclaimer; haven't read past the free preview)
edit: (also, D's lazy keyword, which performs the transformation described in the article automatically)
numbers = [0,1,2,3,4,5,6,7,8,9]
take 5 numbers
-- [0,1,2,3,4]
numbers = [0..]
take 5 numbers
-- [0,1,2,3,4]
evenNumbers = map (* 2) numbers
take 5 evenNumbers
-- [0,2,4,6,8]
last evenNumbers
-- <<infinite loop>>
I used simple list stuff in this illustration, but everything is lazy. I/O most notably (and sometimes problematically). Nothing runs until it's forced[0], and none of this requires any special annotation or plumbing.----
[0] Yes, I know. But it's subtle, and we're in public here. https://wiki.haskell.org/Lazy_vs._non-strict
In larger Haskell programs, I've found that the most challenging issue to debug: "why does my program use way too much memory?".
So, don't create a list of numbers if you intend to sum it, use a non- lazy data structure.
The trick is that Haskell's common default structures are lazy.
[0] http://www.oreilly.com/openbook/opensources/book/larry.html
disclaimer: Perl fan ha
For example with UI/UX and lazy loading, you can use lazy initialization to get your UI/UX on the screen faster for the user, by deferring a bunch of content initialization to after the UI/UX.
For example with heuristics and lazy calculations, you can use techniques such as successive approximations, caching of recent similar results, eventual consistency, and partial filling, so you can provide decent information to the user.
Showing what had been, and then updating the action of a display location, creates a UX of "shifting sands".
Often annoyingly occurs with popup ads, but in an iOS app itself is inexcusable.
Lazy evaluation is probably better named "deferred evaluation", and is more analogous to procrastination, not laziness.
It depends. Haskell is lazy, but I think it would still constant fold that expression.
https://www.reddit.com/r/haskell/comments/1e8k3k/three_examp...
The funny thing about laziness is you have to work hard to then reach a state of meaningful and productive laziness that isn't cutting corners or creating the right balance between risk and technical debt.
if False and (fibonacci(1000)/fibonacci(1000)):
print "Nope"
It will immediately evaluate to False and pring "Nope" rather than checking the (fibonacci(1000)/fibonacci(1000)) portion of the conditional.EDIT: Side note, is there a shortcut for displaying code snippets within HN posts? Extra returns (what I usually use for clarification by formatting) do not display very well.
(if isset($foo) && $foo == 14)
If $foo is not set the second boolean expression is not evaluated (short circuit evaluation).
In fact, checking if isset followed by another boolean expression is very idiomatic in PHP.
Indent your code snippets with a minimum of four spaces and they will be rendered as preformatted, monospaced text. Vertically adjacent lines thus indented will be rendered as a single block.
I gave a slightly rough talk about "Thinking with Laziness"[1] which tries to capture how expressive it can be. I'm a big fan and sad that so many people are willing to categorically dismiss it as a wart in Haskell.
[1]: https://begriffs.com/posts/2015-06-17-thinking-with-laziness...
for (const element of list) {
should actually be
for (let element of list) {
?
the former one raises "SyntaxError: invalid for/in left-hand side"
And I mean doing so with their permission, and at your request, but without any prior instruction, and you're not allowed to intervene or supply hints.
ifThen(1 === 0, 5)
No need to calculate 2 + 3!and if instead of addition it was a function call...
function compare(list, val){
return list.some(el => el === val);
}
Read the documentation (preferably from MDN [1]) for Array methods. You'll get your "laziness" to the next level: .filter(), .some(), .every(), .reduce(), .concat() [vs .push()], etc.[1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...