Discuss Scratch

bharvey
Scratcher
1000+ posts

Snap! user discussion

Jonathan50 wrote:

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.)
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.

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

bharvey wrote:

Jonathan50 wrote:

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.)
It's not just circular structures.
I know, it's even better, but you can do circular structures.

Not yet a Knight of the Mu Calculus.
Hardmath123
Scratcher
1000+ posts

Snap! user discussion

bharvey wrote:

Hardmath123 wrote:

def fib(n):
return (4 << n*(3+n)) // ((4 << 2*n) - (2 << n) - 1) & ((2 << n) - 1)
You're being sarcastic, right? It gives me a headache. I don't even want to think about convincing myself it can work.
Ahahaha I'm completely serious about that one. It's due to Paul Hankin—see http://paulhankin.github.io/Fibonacci/

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

Hardmath123 wrote:

It's based on generating functions; imagine a power series P such the coefficient of x^n is the nth Fibonacci number.
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.

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

bharvey wrote:

-ScratchOs wrote:

I was just wondering, how do you implement the wait()secs in JS?
(is it a loop or a setTimeout …)
Certainly 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! 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:
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();
};
Thanks for this this is really helpful
BookOwl
Scratcher
1000+ posts

Snap! user discussion

Jonathan50 wrote:

Why would anyone use
function fib(n) {
  var a = 1;
  var b = 0;
  while(n > 1) {
    n--;
    var temp = a;
    a += b;
    b = temp;
  }
  return a;
}
when you can use
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!
What's wrong with imperative code? For some problems, it is better than functional programming. Take a look at this python code:
def fib(n):
    a, b = 0, 1
    for i in range(n):
        a, b = b, a + b
    return b
I find this code quite elegant. No ugly temp vars or while loops. And if you want an infinite stream of every Fibonacci number, you can just do
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.


And it was delicious! Play TBGs! Check out my Scheme Interpreter!
;
BookOwl
Scratcher
1000+ posts

Snap! user discussion

Here is a tail-recursive, functional fibonacci generator:
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)
IMO, this looks a lot uglier than the imperative version.
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.

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.

Last edited by BookOwl (May 23, 2016 15:55:27)


who needs signatures
BookOwl
Scratcher
1000+ posts

Snap! user discussion

bharvey wrote:

The iterative code for Euclid's GCD algorithm, for example, is almost identical to the iterative code for Fib
Maybe this means that they are related somehow…

who needs signatures
bharvey
Scratcher
1000+ posts

Snap! user discussion

BookOwl wrote:

@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.
Nah, space grows linearly, time grows exponentially. You'll die of old age before you run out of memory, is my guess.

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

bharvey wrote:

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))
Or in Snap!:

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:

;; 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)



And it was delicious! Play TBGs! Check out my Scheme Interpreter!
;
joefarebrother
Scratcher
500+ posts

Snap! user discussion

BookOwl wrote:

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.
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.)

Last edited by joefarebrother (May 23, 2016 16:53:16)



And it was delicious! Play TBGs! Check out my Scheme Interpreter!
;
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


And it was delicious! Play TBGs! Check out my Scheme Interpreter!
;
bharvey
Scratcher
1000+ posts

Snap! user discussion

joefarebrother wrote:

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)


Powered by DjangoBB