Discuss Scratch
- Discussion Forums
- » Advanced Topics
- » Snap! user discussion
- bharvey
- Scratcher
1000+ posts
Snap! user discussion
It's not just circular structures. You should read Chapter 3. Inserting into a binary search tree is way faster and simpler if you mutate the branch node just above a leaf node. With lazy evaluation you can have circular data structures without mutation. (And lazy evaluation messes up imperative code anyway, so you can't/shouldn't mutate objects to make circular data structures.)
I mean, if you're going to think everything-looks-like-a-nail, better you should have a functional hammer than an imperative one, I agree. But if you're writing an interactive program, like Eliza or something, you really don't want to mess around with monads. What you can do, often, is divide the program into an interactive module and a computation module, and the latter can often be written functionally.
P.S. The place for “should” judgments is in programming language design. A general purpose programming language should support functional programming. (I said “general purpose” partly to avoid getting into a fight about what Scratch should do.)
When I developed Berkeley Logo, I said that I'm an amateur at programming languages, and the interpreter is slow and klunky, but in my mind it should serve as a minimum standard – Logo purveyors whose products don't do at least what Berkekely Logo does should be embarassed.
And that's how I feel about Scheme with respect to languages in general. A good language should at least do everything Scheme does.
Last edited by bharvey (May 23, 2016 01:57:09)
- Jonathan50
- Scratcher
1000+ posts
Snap! user discussion
I know, it's even better, but you can do circular structures.It's not just circular structures. With lazy evaluation you can have circular data structures without mutation. (And lazy evaluation messes up imperative code anyway, so you can't/shouldn't mutate objects to make circular data structures.)
Not yet a Knight of the Mu Calculus.
- Hardmath123
- Scratcher
1000+ posts
Snap! user discussion
Ahahaha I'm completely serious about that one. It's due to Paul Hankin—see http://paulhankin.github.io/Fibonacci/You're being sarcastic, right? It gives me a headache. I don't even want to think about convincing myself it can work.def fib(n):
return (4 << n*(3+n)) // ((4 << 2*n) - (2 << n) - 1) & ((2 << n) - 1)
It's based on generating functions; imagine a power series P such the coefficient of x^n is the nth Fibonacci number. Then, by matching coefficients, we have P(x)-x-1 = x(P(x)-1)+x^2 P(x) so P(x) = 1/(1-x-x^2).
Indeed, P(10^-3) = F1 + F2*0.001 + F2*0.000 001 + F3*0.000 000 001 + … = 1.001002003005008013021034055089144233377610988599588187…
The rest is just encoding this in binary and bitwise operations to extract the nth set of digits.
- bharvey
- Scratcher
1000+ posts
Snap! user discussion
Okay, you win, I think it's beautiful. I never did really understand generating functions; the only time I tried to figure one out, I ended up proving that 1=0. So instead of suffering from a headache I'm now suffering from envy that I'm not smart enough to have invented that. It's based on generating functions; imagine a power series P such the coefficient of x^n is the nth Fibonacci number.
PS @bobbybee: You do have to be skillful enough at calculus to be able to work out generating functions. (Do as I say, not as I do.)
Last edited by bharvey (May 23, 2016 02:36:43)
- -ScratchOs
- Scratcher
71 posts
Snap! user discussion
Thanks for this this is really helpfulCertainly not a loop; when one script is waiting, the rest of the program and the IDE still have work to do. For the same reason, it's not a setTimeout either. Snap I was just wondering, how do you implement the wait()secs in JS? ! includes its own scheduler that runs scripts in rotation. The wait block remembers when it started, and every time the scheduler gets to it, it sees whether enough time has passed. If not, it just yields and the scheduler runs the next thread:
(is it a loop or a setTimeout …)Process.prototype.doWait = function (secs) {
if (!this.context.startTime) {
this.context.startTime = Date.now();
}
if ((Date.now() - this.context.startTime) >= (secs * 1000)) {
return null;
}
this.pushContext('doYield');
this.pushContext();
};
- BookOwl
- Scratcher
1000+ posts
Snap! user discussion
What's wrong with imperative code? For some problems, it is better than functional programming. Take a look at this python code: Why would anyone usewhen you can usefunction fib(n) { var a = 1; var b = 0; while(n > 1) { n--; var temp = a; a += b; b = temp; } return a; }?function fib(n) { return n <= 2 ? 1 : fib(n - 1) + fib(n - 2); }
And tail recursion is better than looping because loops are imperative. For a while loop to terminate there needs to be state.
And you don't need ugly ‘temp’ variables for a tail recursive version!
def fib(n): a, b = 0, 1 for i in range(n): a, b = b, a + b return b
def fib_iter(): a, b = 0, 1 while True: yield b a, b = b, a + b
who needs signatures
- joefarebrother
- Scratcher
500+ posts
Snap! user discussion
Some problems are best solved with a mixture of functional and imperative code.
For example, right now I'm working on a Snap! project which deals with a partially immutable data structure, which I use process recursively in a functional style. However, some of it needs to cache the results of previous computations, which is most elegantly and most efficiently done imperatively.
For example, right now I'm working on a Snap! project which deals with a partially immutable data structure, which I use process recursively in a functional style. However, some of it needs to cache the results of previous computations, which is most elegantly and most efficiently done imperatively.
- BookOwl
- Scratcher
1000+ posts
Snap! user discussion
Here is a tail-recursive, functional fibonacci generator:
IMO, this looks a lot uglier than the imperative version.
EDIT: @jonathan50, your recursive solution is NOT tail recursive, it's tree recursive.
def fib_rec(n): def fib_helper(n2, a, b): if n2 == n: return b return fib_helper(n2 + 1, b, a + b) return fib_helper(0, 0, 1)
EDIT: @jonathan50, your recursive solution is NOT tail recursive, it's tree recursive.
Last edited by BookOwl (May 23, 2016 15:44:07)
who needs signatures
- bharvey
- Scratcher
1000+ posts
Snap! user discussion
So, since everyone is picking on Jonathan, I feel inspired to come to his defence.
The kernel of truth in his position is that the most elegant implementation of fib is clearly the one that follows the definition: fib(n) = fib(n-1)+fib(n-2). (Plus the base case of course.) Unlike all the others, it makes it perfectly clear how the code is an implementation of the underlying idea. “Each Fibonacci number is the sum of the two before it.” Can't beat that.
This would be the end of the story, game over, fp wins, except for the unfortunate fact that, as written, it takes exponential time. Second best would be if we could write fib(n) = memoize(fib(n-1)+fib(n-2)). That might be possible in a self-reflective language, but not (for example) in Scheme, which doesn't let you pull out the body of a procedure as data. (I'm not saying you can't memoize a function in Scheme; you can. But it's not quite this elegant. See Chapter 3.)
So once you care at all about efficiency, you've opened Pandora's box and you are going to have to settle for something less than perfect elegance. Personally I don't think the difference between parallel assignments and a temp variable is what matters; this business of setting the next A to the previous B, or the other way around, is not easy for me to read. The iterative code for Euclid's GCD algorithm, for example, is almost identical to the iterative code for Fib. You have to look closely at the details to work out which is which, if you name the functions F and G instead of GCD and Fib. The natural recursive-function definitions, otoh, are quite different.
Once we've agreed to abandon the most elegant definition for efficiency reasons, it seems silly to me to get into heated arguments about which is the second most elegant. The others are all pretty inelegant. Get over it, as they say in Webland.
The kernel of truth in his position is that the most elegant implementation of fib is clearly the one that follows the definition: fib(n) = fib(n-1)+fib(n-2). (Plus the base case of course.) Unlike all the others, it makes it perfectly clear how the code is an implementation of the underlying idea. “Each Fibonacci number is the sum of the two before it.” Can't beat that.
This would be the end of the story, game over, fp wins, except for the unfortunate fact that, as written, it takes exponential time. Second best would be if we could write fib(n) = memoize(fib(n-1)+fib(n-2)). That might be possible in a self-reflective language, but not (for example) in Scheme, which doesn't let you pull out the body of a procedure as data. (I'm not saying you can't memoize a function in Scheme; you can. But it's not quite this elegant. See Chapter 3.)
So once you care at all about efficiency, you've opened Pandora's box and you are going to have to settle for something less than perfect elegance. Personally I don't think the difference between parallel assignments and a temp variable is what matters; this business of setting the next A to the previous B, or the other way around, is not easy for me to read. The iterative code for Euclid's GCD algorithm, for example, is almost identical to the iterative code for Fib. You have to look closely at the details to work out which is which, if you name the functions F and G instead of GCD and Fib. The natural recursive-function definitions, otoh, are quite different.
Once we've agreed to abandon the most elegant definition for efficiency reasons, it seems silly to me to get into heated arguments about which is the second most elegant. The others are all pretty inelegant. Get over it, as they say in Webland.
- BookOwl
- Scratcher
1000+ posts
Snap! user discussion
@bharvey, you forgot the fact that fib(n) = fib(n-1)+fib(n-2) is not tail recursive, so even in Scheme you won't be able to calculate large Fibonacci numbers.
EDIT: But I do agree that it is the most elegant.
EDIT: But I do agree that it is the most elegant.
Last edited by BookOwl (May 23, 2016 15:55:27)
who needs signatures
- BookOwl
- Scratcher
1000+ posts
Snap! user discussion
Maybe this means that they are related somehow… The iterative code for Euclid's GCD algorithm, for example, is almost identical to the iterative code for Fib
who needs signatures
- bharvey
- Scratcher
1000+ posts
Snap! user discussion
Nah, space grows linearly, time grows exponentially. You'll die of old age before you run out of memory, is my guess. @bharvey, you forgot the fact that fib(n) = fib(n-1)+fib(n-2) is not tail recursive, so even in Scheme you won't be able to calculate large Fibonacci numbers.
Maybe this means that they are related somehow…Yes, this is a more interesting contribution! We'll ask hm; that's easier than thinking about it.
- bharvey
- Scratcher
1000+ posts
Snap! user discussion
Okay, here's my candidate for most elegant efficient implementation:
(define fib-stream
(cons-stream 0
(cons-stream 1
(stream-map + fib-stream
(stream-cdr fib-stream)))))
(define (fib n) (stream-ref fib-stream n))
Last edited by bharvey (May 23, 2016 16:16:34)
- BookOwl
- Scratcher
1000+ posts
Snap! user discussion
Is cons-stream standard? When I try that in Chicken (which is R5RS complaint) it says that cons-stream is unbound.
who needs signatures
- BookOwl
- Scratcher
1000+ posts
Snap! user discussion
Project loading really needs to be made async. Every time I try to load a block library or project, my web browser says that snap! is locked up, and if I let it continue, it locks up my whole browser for one or two minutes, depending on how big the project is.
who needs signatures
- BookOwl
- Scratcher
1000+ posts
Snap! user discussion
Or in Snap Okay, here's my candidate for most elegant efficient implementation:!:(define fib-stream
(cons-stream 0
(cons-stream 1
(stream-map + fib-stream
(stream-cdr fib-stream)))))
(define (fib n) (stream-ref fib-stream n))
I still don't quite get how it works.
Last edited by BookOwl (May 23, 2016 16:45:37)
who needs signatures
- joefarebrother
- Scratcher
500+ posts
Snap! user discussion
cons-stream isn't standard, but it can be defined like:
(Note: Not tested. I probably missed off a few closing brackets or something.
;; Doesn't implement all stream functionality (most notably, finite streams), but it's enough for Brian's example (define-syntax cons-stream (syntax-rules () (_ first rest) (cons first (delay rest)))) (define stream-car car) (define (stream-cdr str) (force (cdr str))) (define (stream-map f strs...) (stream-cons (apply f (map stream-car strs)) (stream-map f (map stream-cdr strs)))) (define (stream-ref str n) (if (= n 0) (stream-car str) (stream-ref (stream-cdr str) (- n 1)) (define fib-stream (cons-stream 0 (cons-stream 1 (stream-map + fib-stream (stream-cdr fib-stream))))) (define (fib n) (stream-ref fib-stream n))
(Note: Not tested. I probably missed off a few closing brackets or something.
Last edited by joefarebrother (May 23, 2016 17:55:06)
- joefarebrother
- Scratcher
500+ posts
Snap! user discussion
really needs to be made async. Every time I try to load a block library or project, my web browser says that snap! is locked up, and if I let it continue, it locks up my whole browser for one or two minutes, depending on how big the project is.I don't think Jens likes async things - even the http request block is synchronous (he only gets away with that by pushing it to Snap!'s scheduler and checking the ready state whenever Snap!'s scheduler gets around to it.) Project loading
Last edited by joefarebrother (May 23, 2016 16:53:16)
- joefarebrother
- Scratcher
500+ posts
Snap! user discussion
Through a lot of reading the source code and trial and error, I fixed my try/catch block!
Project link
I couldn't figure out how to get it to run more than one block in the handler, so I settled for a rather unsatisfactory “put the whole script in a run block”.
(calling new Context() didn't work)
When Jens gets back (when will that be?) he'd probably know how to improve it
Project link
I couldn't figure out how to get it to run more than one block in the handler, so I settled for a rather unsatisfactory “put the whole script in a run block”.
(calling new Context() didn't work)
When Jens gets back (when will that be?) he'd probably know how to improve it
- bharvey
- Scratcher
1000+ posts
Snap! user discussion
I fixed my try/catch block!My example still fails.
On the other hand, I figured from reading your code that changing the name of the ERROR upvar would break it, but it doesn't.
Do you hate my proposed name change?
;; Doesn't implement all stream functionality (most notably, finite streams)All you have to do is (define the-empty-stream '()) and test for it in MAP.
Last edited by bharvey (May 23, 2016 18:30:09)