Hi Joshua,
> Interestingly, I had wrongly assumed that > > #+BEGIN_SRC scheme > (thunk "test\n") ;; I assumed program execution would stop here > (display "Hello World\n") > #+END_SRC > > program execution would stop at (thunk "test\n"). But it actually > caries on with execution of the program: > > #+BEGIN_SRC scheme > <stdin>:5:0: warning: possibly wrong number of arguments to `thunk' > ice-9/boot-9.scm:1669:16: In procedure raise-exception: > Wrong number of arguments to #<procedure thunk ()> > > Entering a new prompt. Type `,bt' for a backtrace or `,q' to continue. > Hello World > #+END_SRC It doesn’t actually carry on. From those Org-mode blocks I cannot tell how you’re feeding the expressions to Guile. If you’re doing this in a file or in a REPL session manually you’ll see this: --8<---------------cut here---------------start------------->8--- scheme@(guile-user)> (define (thunk) (lambda (x) x)) scheme@(guile-user)> (thunk "test\n") ;;; <stdin>:3640:0: warning: possibly wrong number of arguments to `thunk' ice-9/boot-9.scm:1669:16: In procedure raise-exception: Wrong number of arguments to #<procedure thunk ()> Entering a new prompt. Type `,bt' for a backtrace or `,q' to continue. scheme@(guile-user) [1]> (display "Hello World\n") Hello World scheme@(guile-user) [1]> --8<---------------cut here---------------end--------------->8--- “Entering a new prompt” is what happens. Consider it a special debug mode in which you’re given the option to inspect the current state of the environment. Granted, you won’t see much when you type “,bt” or “,bt #:full? 'yup”, because the error isn’t all that complicated; the error didn’t happen in some deeply nested callee, it happened right there when you called “thunk” with an argument. > For fun I also thought about how else I could write thunk. Continue > reading at your own peril. I feel the need to point out that the name “thunk” has a conventional meaning, but your examples (here and before) don’t correspond to that meaning. A thunk is a procedure of no arguments. When called it returns a value — it does not matter whether that value is another procedure, a record, or a primitive value. What you seem to be calling “thunk” is really just a higher order procedure, i.e. a procedure that returns another procedure. Note that this may or may not be a thunk as everybody else calls it. It is a thunk when it is a procedure that takes exactly zero arguments. > ;; This procedure doesn't work the way I thought it would. The way to > ;; print a string with this procedure is to do this: > ;; ((thunk "the") "the") > (define (thunk x) > (lambda (x) x)) You’re making things difficult for you here, because you’re using “x” three times, but it means two different things dependent on where you look at the value you bound to “x”. This is also not a thunk (neither the procedure “thunk” nor the value it returns), so let’s rewrite this: (define (moo x) (peek 'moo-x x) (lambda (x) (peek 'lambda-x x))) Nobody cares about the value you pass to “moo”. It is bound to the name “x”, but nobody uses it. Then comes along a lambda and it takes an argument that is also known as “x” inside the body of that lambda. They are *not* the same “x”. The “x” bound by the lambda shadows the outer “x”. You could even have yet another “x”: (define x 100) (peek 'top-level-x x) (define (moo x) (peek 'moo-x x) (lambda (x) (peek 'lambda-x x))) Excess xes! > ;; obvious. This is equivalent to > ;; (define (thunk x) x) > (define thunk > (lambda (x) > x)) Correct. Not a thunk, though. It’s just the identity function. > ;; This ones nice because neither (thunk) nor (thunk "the") result in a > ;; runtime error. > (define* (thunk #:optional x) > x) This is a procedure that will return #false (when no argument is provided) or the value of the argument it was given. > (define* (thunk #:optional x) > (lambda* (#:optional x) > x)) This is again like the “moo” example earlier. Two different values are bound to variables that are known as “x” in their own scope, with the later “x” shadowing the earlier “x”. > Are there some other really weird and convoluted ways of writing thunk > that I'm missing? I'm guessing so. I don’t know what you mean because your definitions above are not all doing the same thing. A giraffe is not a convoluted variant of a lion. > […] Now I realize that ((thunk "the") "the") > works. ((thunk 124) "the") has the same return value, because you’re ignoring the first “x”. > I suppose the moral of the story is that scheme is so expressive and > flexible that there are ways of creating programs that can fail in weird > ways. And it's really hard if not impossible to catch all possible > runtime errors at compile time. This is because scheme values are only > know at run-time AND NOT compile time. This is not 100% correct, but perhaps that doesn’t matter. Some types are in fact known at compile time. > I was actually listening to a scheme talk recently about typed racket. > The gentleman giving the talk explained that dynamic typing used to be > all the rage, but there seems to be some people advocating for static > typing because the compiler eliminates many trivial bugs. However, some > elegant and correct dynamic programs would be eliminated by the > compiler as causing errors. > > typed scheme | untyped scheme > ---------------------------------------------------------------- > - potentially faster | - potentially slower > - a procedure's inputs | - more expressive > and outputs are obvious | - more concise > - catches trivial errors | > - helps refactoring | > - eliminates "correct" > dynamic programs > > I suppose that what I am wanting (forcing the compiler to eliminate > trivial bugs) may only be possible in a typed scheme. Is that correct? > > What are your thoughts? Typed or un-typed scheme? I learned functional programming with Haskell, which is more type than language ;) Type systems allow you to encode certain assumptions in a way that the compiler can check for you. Note that “potentially slower” doesn’t mean much unless you look at actual implementations. Types allow the compiler to perform certain optimizations because it can trust that certain assumptions will hold at runtime. But these optimizations would actually have to be implemented in the compiler; you don’t get them for free just because you have type declarations. Likewise, you can have a fast untyped Scheme and a slow untyped Scheme dependent on the optimizations that are implemented. You can also get fast or slow compilers… The point about types eliminating “correct” dynamic programs is noteworthy. Types are a constraint and dependent on how strictly they are enforced they can make it impossible to do things that are sensible. It is, however, possible to declare an “Any” type and write programs that are transformations of values of the “Any” type, which would be correctly typed; but the effect is to bypasses the type checker, which renders it useless. So… yeah, it’s not that clear cut. Sometimes I do miss types in my Scheme code, especially in more complex programs with lots of higher-order functions where it’s easy to get lost. But most of the time I don’t care for types. -- Ricardo