Toggle quote (19 lines)
> 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:
Toggle snippet (12 lines)
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]>
“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.
Toggle quote (3 lines)
> 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.
Toggle quote (6 lines)
> ;; 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!
Toggle quote (6 lines)
> ;; 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.
Toggle quote (5 lines)
> ;; 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.
Toggle quote (4 lines)
> (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”.
Toggle quote (3 lines)
> 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.
Toggle quote (3 lines)
> […] Now I realize that ((thunk "the") "the")
> works.
((thunk 124) "the") has the same return value, because you’re ignoring
the first “x”.
Toggle quote (6 lines)
> 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.
Toggle quote (22 lines)
> 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