On 3/8/23 20:38, Bruno Barbier wrote:
Hi Zelphir,

Zelphir Kaltstahl <zelphirkaltstahl@posteo.de> writes:

On 3/7/23 20:52, Bruno Barbier wrote:

      
Also thanks for the idea with sessions + separate import source block. I thought 
that should work, but apparently that also has the same error, when running for 
the first time:

...
Oh, I see. I tested something way simpler :-)

First, one block to open and configure the guile session.

     #+begin_src scheme :session "!guile" :results silent
     (import (except (rnrs base) error vector-map)
              (only (guile)
                    lambda*
                    λ)
              ;; let-values
              (srfi srfi-11))
     #+end_src

Then, you can get to work and evaluate as many blocks as you like in
that session:

     #+begin_src scheme :session "!guile" :results output replace drawer :var x=1 :var y=2
     (let-values ([(a b) (values x y)])
        (simple-format #t "~a ~a\n" a b))
     #+end_src

     #+RESULTS:
     :results:
     1 2
     :end:

Bruno
Hello Bruno and hello mailing list!

I just tested it a little more:

If there is any (import ...) at all in the code block that makes use of the (let-values ...), it seems to somehow disturb already imported libraries. For example the following also does not work:

~~~~START~~~~
#+name: scheme-time-imports
#+begin_src scheme :eval query-export :noweb strip-export :session scheme-time :results output replace drawer"
(import
 (except (rnrs base) error vector-map)
 (only (guile)
       lambda*
       λ)
 ;; let-values
 (srfi srfi-11))
#+end_src

#+RESULTS: scheme-time-imports

#+name: scheme-time
#+begin_src scheme :eval query-export :noweb strip-export :session scheme-time :results output replace drawer :var x=1 :var y=2
<<scheme-time-imports>>
(import (srfi srfi-1))
(let-values ([(a b) (values x y)])
   (simple-format #t "~a ~a\n" a b))
#+end_src
~~~~~END~~~~~

So that means, even if the import has nothing to do with the actual import which would provide the let-values form, it disturbs it.

I am not sure (let ...) is a correct wrapper for noweb included source blocks. What, if I write a (define ...) in my source block and want to use that source block via noweb in another source block? Expected behavior I think would be to be able to access those variables in other source blocks, since they are defined on a top level in an earlier source block, but if they are wrapped in a (let ...), that would make them only available in the (let ...)? It seems to me, that the simple wrapping with a (let ...) might not be the right thing to do. Testing that:

~~~~START~~~~
#+name: scheme-defs
#+begin_src scheme :eval query-export :noweb strip-export :session myguile :results output replace drawer :var x=1 :var y=2
(define a x)
(define b y)
#+end_src

#+name: scheme-time
#+begin_src scheme :eval query-export :noweb strip-export :session myguile :results output replace drawer
<<scheme-defs>>
(simple-format #t "~a ~a\n" a b)
#+end_src
~~~~~END~~~~~

Indeed, that also does not work.

I guess I did never hit this problem earlier, because I "oursourced" my imports and in imports I do not need any :var header arguments.

I've asked on the Guile IRC channel and something interesting is the case here (thanks for clearing it up flatwhatson!) and I understand it as follows:

Imports inside (let ...) work. It is just that let-values is a macro and macros are expanded before execution time. However, Guile gets to the body of the wrapping (let ...) at execution time. That means, that when Guile gets to evaluate the body of the let, it does not expand the let-values, because it is already at execution time and no longer at macro expansion time. The import might import the let-values form, or might not, but it is already too late to expand the (let-values ...).

What is still a bit weird about it is, that in the original example with `let-values` I don't get an error about `let-values` not being defined, but only about `a` not being defined. And in the example with (define ...) and :var above, I get a message about `x` not being defined, instead of `a` not being defined.

Probably a good general workaround is to only have imports at the top level, by moving them into a source block, which does not have any :var header arguments.

OK, the question is though, whether org should wrap anything in a (let ...) at all. During discussion on the Guile IRC, some points against let-wrapping were brought up:

(1) The presence of a :var header argument currently determines, whether the code in the source block is wrapped with a (let ...). One argument for that was, that this way the variables do not leak. But this also decides, whether other things leak. For example (import ...) or (define ...). Should :var decide, whether bindings created with (define ...) are visible in other source blocks including the source block with the :var header arguments? It seems like a responsibility :var should not have and definitely is unexpected for the user.

(2) Wrapping in a (let ...) also moves things from the top level to the inside of expressions. This can make them move from one phase to another. Phases such as described here: https://www.gnu.org/software/guile/manual/html_node/Eval-When.html. This is probably not intended and also unexpected.

(3) Not wrapping with a (let ...) expression and instead going with simple (define ...) for :var variables seems a very general solution, that should be portable to all Schemes.

(4) To make things easily understandable, bindings defined in a source block that is included in another source block, should either always leak, or never. Not sometimes this way and sometimes that way. But conditionally wrapping with (let ...) if there is a :var header argument introduces a behavior that changes that.

(5) Imagine you already have a finished document and want to extend it, by letting one of the source blocks take a variable via :var header argument. Now you must change the source block as well, and not only place the variable's name somewhere, but also need to take care of imports and evaluation and expansion phases.

(6) Looking at other languages and how it behaves there. Say Python for example, there are no (let ...) expressions available, so no let-wrapping could happen. Unless org invents an additional `def` to wrap it in that, but only if there are :var header arguments. Lets see how things work there:

~~~~START~~~~
#+name: python-imports
#+begin_src python :python /usr/bin/python3 :results output replace drawer :var x=4
import math

y = math.sqrt(x)
# print(y)
#+end_src

#+name: python-usage
#+begin_src python :python /usr/bin/python3 :return :noweb strip-export :results value replace drawer
<<python-imports>>

print("y: {}".format(y))
#+end_src
~~~~~END~~~~~

Unfortunately, this example does not seem to work at all, but for a different reason:

It seems that using any Python source block with :var header args via :noweb does not work, as it then behaves in the way, that it merely pasted the included source block, without first putting in the :var values into the variables. I get errors about those :var variables being undefined, of course, since they are on the included source block, not on the including one:

~~~~START: *Org-Babel Error Output*~~~~
Traceback (most recent call last):
  File "<stdin>", line 10, in <module>
  File "<stdin>", line 5, in main
NameError: name 'x' is not defined
[ Babel evaluation exited with code 1 ]
~~~~~END~~~~~

So it would seem, that :var does not work with :noweb for Python source blocks yet.

(7) As an idea, one could still invent a new header argument that determines whether to let-wrap or not, if it is really needed for any other Scheme.

OK, to wrap up (ha!), I want to ask:

(q1) What is a rationale, if any, behind the let-wrapping?

(q2) Any chances of that changing to (define ...)?

(q3) How could I change my org-mode's code to not  let-wrap, and instead use (define ...)?

Best regards,
Zelphir

-- 
repositories: https://notabug.org/ZelphirKaltstahl