What is the correct way of carrying an ast along in shen?

67 views
Skip to first unread message

Luca Di Sera

unread,
Nov 8, 2020, 8:05:42 AM11/8/20
to Shen
I'm currently working on a small program where I was in need of carrying what the programmer entered along until a later point of execution.

I'll first state the problem I wanted to solve, as I may be approaching this in the wrong way in shen.

I'm doing a small test framework for my future needs. Two requirements that I have are that the tests that the user enters are not run right away and that, in case of some failure, I will be able to show the user what was evaluated.

This means that I both need to remember the ast and need it to be evaluated at a later point in time, possibly more than one time at different time points t.

For example, from the current iteration, a failure may look like this:

```
(5-) (testing "Trivial Tests" (= 10 20) (= (* 10 (+ 20 30)) 10))
(6-) (run-tests)

Testing: Trivial Tests
    Failed: Expected 10 // 10 But Got 20 // 20 Instead
    Failed: Expected (* 10 (+ 20 30)) // 500 But Got 10 // 10 Instead
testing.done

```

As you see from the example I need to remember what the ast the user entered was.

I couldn't find a direct way to do this in shen as there isn't a direct quote/dequote mechanism.

The obvious first step is probably to have a macro, so that you have access to the ast at some point.

The problem is that you cannot directly return the ast itself, as, correctly, shen will evaluate it. For example:

```
(11-)(defmacro ast-macro [ast|Args] -> Args)
ast-macro

(12-) (ast 10)
10

(13-) (ast (= 10 20))
false

(14-) (ast (= 10 20) (= 20 30))
The function SHEN::|false| is undefined.
```

For example, in `(14-)`, the shen reader will see something like `[[= 10 20] [= 20 30]]` and evaluate it as `((= 10 20) (= 20 30)) -> (false (= 20 30) -> exception`.

To bring an ast along, it is thus needed to present it in a way in which the shen reader will not need to evaluate it right away.

This might be done, for example, with an abstraction:

```
(defmacro ast-macro [ast|Args] -> [/. _ (/. _ Args)])
ast-macro

(31-) (((ast (= 10 20) (= 20 30)) _) _)
[[= 10 20] [= 20 30]]
```

By calling the abstraction ( or a function too ) at reading time we will have access to the ast which we can then carry along.

From my understanding, the abstraction will contain the ast and return a closure whose insides are opaque to the reader and that will evaluate to itself, such that its contained value will be accessible later.

This is a bit unsightly and I'm not really sure this is the easiest way to write this, but the other things I tried haven't really worked.

For example, building an ast that builds the abstraction will not work, Args seems to have been reduced at least once by that time, such that the following is incorrect:

```
(32-)(defmacro ast-macro [ast|Args] -> [/. _ Args])
ast-macro

(33-) ((ast (= 10 20) (= 20 30)) _)
The function SHEN::|false| is undefined.
```

Similarly, trying to return the abstraction directly results in an error, albeit I'm not sure about the actual explanation for this and if it is correct that this is the case:

```
(34-) (defmacro ast-macro [ast|Args] -> (/. _ Args))
ast-macro

(35-) ((ast (= 10 20) (= 20 30)) _)
; in: #<CLOSURE (LAMBDA (_) :IN |ast-macro|) {100347F48B}> '_
;     (#<CLOSURE (LAMBDA (SHEN::_) :IN SHEN::|ast-macro|) {100347F48B}> 'SHEN::_)
; caught ERROR:
;   illegal function call
; compilation unit finished
;   caught 1 ERROR condition
Execution of a form compiled with errors.
Form:
  (#<CLOSURE (LAMBDA (_) :IN |ast-macro|) {100347F48B}> '_)
Compile-time error:
  illegal function call

(36-) (ast (= 10 20) (= 20 30))
#<CLOSURE (LAMBDA (_) :IN |ast-macro|) {10034A295B}>
```

As I said, I'm not sure about why this happens but I feel it might have something to do with the reader expecting a list of symbols or something like that.
If we try to enclose the lambda in a "list" it won't work but if we actually build a list with the abstraction inside, the computation will go trough:

```
(37-) (defmacro ast-macro [ast|Args] -> [(/. _ Args)])
ast-macro

(38-) (ast (= 10 20) (= 20 30))
#<CLOSURE (LAMBDA (_) :IN |ast-macro|) {1003514CBB}>

(39-) ((ast (= 10 20) (= 20 30)) _)
; in: #<CLOSURE (LAMBDA (_) :IN |ast-macro|) {100351FEEB}> '_
;     (#<CLOSURE (LAMBDA (SHEN::_) :IN SHEN::|ast-macro|) {100351FEEB}> 'SHEN::_)
; caught ERROR:
;   illegal function call
; compilation unit finished
;   caught 1 ERROR condition
Execution of a form compiled with errors.
Form:
  (#<CLOSURE (LAMBDA (_) :IN |ast-macro|) {100351FEEB}> '_)
Compile-time error:
  illegal function call

(40-) (defmacro ast-macro [ast|Args] -> [cons (/. _ Args) []])
ast-macro

(41-) ((ast (= 10 20) (= 20 30)) _)
The value
  (#<CLOSURE (LAMBDA (_) :IN |ast-macro|) {10035B413B}>)
is not of type
  (OR FUNCTION SYMBOL)
when binding FUNCTION

(42-) ((hd (ast (= 10 20) (= 20 30))) _)
[[= 10 20] [= 20 30]]
```

All of those forms, tough, aren't particularly appreciable, as the ast will have to be extracted by providing a unused argument to the abstraction. There might be a way to avoid this by instead compiling a 0-arity function in a null package that returns the ast for us and have the reader call it, but I haven't really tried that way as I feel the complexity is not worth it.

The last example actually shows us that there is another way to produce the ast.

The reader evaluates a list of symbol as code, thus when we simply return the ast it will not remain in list form.

A way to avoid this is to actually have the reader evaluate code that builds the list itself.
For example:

```
(51-) (define to-cons
[] -> []
[X|Xs] -> [cons (to-cons X) (to-cons Xs)]
X -> X)
to-cons

(52-) (defmacro ast-macro [ast|Args] -> (to-cons Args))
ast-macro

(53-) (ast (= 10 20) (= 20 30))
[[= 10 20] [= 20 30]]

(54-) (ast (= (* 20 30) 20) (= 20 30) (do (output "something") (* 20 (* 30 (+ 50 10)))))
[[= [* 20 30] 20] [= 20 30] [do [shen.prhush "something" stoutput] [* 20 [* 30 [+ 50 10]]]]]
```

Each ast is normalized to it's cons form, such that the reader only sees the concatenations of a series of symbol instead of evaluable forms.

This is the simplest and cleanest way I've found, as it avoids the needs for non-intuitive retrievals of the ast and provides for a simple macro.

My actual use case is quite more complex than this, as I have various requirements as compiling the tests only once and providing for user defined setups and teardowns and so on.

Nonetheless, the basic problem is still that of bringing the non evaluated ast along.

Thus my questions are:

What is the best way to accomplish this in shen?
Are there some edge cases that I cannot see in my currently used solution?
What is the reason for the, seemingly idiosyncratic, behavior of the various abstraction versions of ast?
Is there some apparent misunderstanding of mine on the internal workings of shen from how I approached this?

Best Regards,
Di Sera Luca

Bruno Deferrari

unread,
Nov 8, 2020, 8:53:59 AM11/8/20
to qil...@googlegroups.com
Hello Luca, what you did is indeed the way to approach it if you want to be able to print the expression later.

You want to convert an expression into the expression that will construct it:

(+ 1 2) => (cons + (cons 1 (cons 2 [])))

If you want to run that expression you can `eval` it. But you can also (in addition to building its "cons form") create a lambda from that expression so that you can call it later without having to re-evaluate (and compile) it every time.

To "bring the non-evaluated ast along" you will have to use global variables.

(set *tests* [])

(define register-test
  TestId Expr Lambda Expected -> (do (put TestId test-expression Expr)
                                     (put TestId test-lambda Lambda)
                                     (put TestId test-expected Expected)
                                     (set *tests* [TestId (value *tests*)])
                                     TestId))

(define to-cons
  [X | Y] -> [cons (to-cons X) (to-cons Y)]
  []      -> []
  X       -> X)

(defmacro ast-macro
  [ast Arg] -> (to-cons Arg))

You can then expand (deftest simple-test (+ 10 15) 30) into (register-test simple-test (ast (+ 10 15)) 30).

This doesn't follow the exact form you showed in your example, but it illustrates the idea. For a given expression, you save it's literal form, and also a lambda that will execute it.

(value *tests*)  \\ Get a list of test-ids that have been defined (in reverse order)
(get TestId test-expression) \\ Get an expression you can print
(get TestId test-lambda)     \\ Get a lambda you can call to run the test code
(get TestId test-expected)   \\ Get the expected result

Does this make sense?

--
You received this message because you are subscribed to the Google Groups "Shen" group.
To unsubscribe from this group and stop receiving emails from it, send an email to qilang+un...@googlegroups.com.
To view this discussion on the web, visit https://groups.google.com/d/msgid/qilang/d6e6d161-7d94-4e0c-8ff4-98960bf52cbfn%40googlegroups.com.


--
BD

Bruno Deferrari

unread,
Nov 8, 2020, 8:57:01 AM11/8/20
to qil...@googlegroups.com
On Sun, Nov 8, 2020 at 10:53 AM Bruno Deferrari <uti...@gmail.com> wrote:
Hello Luca, what you did is indeed the way to approach it if you want to be able to print the expression later.

You want to convert an expression into the expression that will construct it:

(+ 1 2) => (cons + (cons 1 (cons 2 [])))

If you want to run that expression you can `eval` it. But you can also (in addition to building its "cons form") create a lambda from that expression so that you can call it later without having to re-evaluate (and compile) it every time.

To "bring the non-evaluated ast along" you will have to use global variables.

(set *tests* [])

(define register-test
  TestId Expr Lambda Expected -> (do (put TestId test-expression Expr)
                                     (put TestId test-lambda Lambda)
                                     (put TestId test-expected Expected)
                                     (set *tests* [TestId (value *tests*)])
                                     TestId))

(define to-cons
  [X | Y] -> [cons (to-cons X) (to-cons Y)]
  []      -> []
  X       -> X)

(defmacro ast-macro
  [ast Arg] -> (to-cons Arg))

You can then expand (deftest simple-test (+ 10 15) 30) into (register-test simple-test (ast (+ 10 15)) 30).

Sorry, the expansion here should be:

(register-test simple-test (ast (+ 10 15)) (/. _ (+ 10 15)) 30)

but even better, you can use `freeze` instead of a lambda:

(register-test simple-test (ast (+ 10 15)) (freeze (+ 10 15)) 30)

And then you can (thaw FrozenExpr) to execute the code.


--
BD

Luca Di Sera

unread,
Nov 8, 2020, 9:29:33 AM11/8/20
to Shen
Hello, Mr. Deferrari,

Thanks for the time you took to respond to me.

This indeed makes complete sense to me and is actually how I've built this ( and with code that is quite similar both in naming and form ) which gives me hope that I'm not over-complicating things.

The suggestion about using freeze here is great and I will surely look into using it.

Do you happen to have an explanation about the error in a form such as `(defmacro ast-macro [ast|Args] -> (/. _ Args))`, from my original post, as this is something that I'm finding difficult to provide a rationale, with my current knowledge, for.

Best Regards,
Di Sera Luca

Bruno Deferrari

unread,
Nov 8, 2020, 9:48:05 AM11/8/20
to qil...@googlegroups.com
On Sun, Nov 8, 2020 at 11:29 AM Luca Di Sera <diser...@gmail.com> wrote:
Hello, Mr. Deferrari,

Thanks for the time you took to respond to me.

This indeed makes complete sense to me and is actually how I've built this ( and with code that is quite similar both in naming and form ) which gives me hope that I'm not over-complicating things.

The suggestion about using freeze here is great and I will surely look into using it.

Do you happen to have an explanation about the error in a form such as `(defmacro ast-macro [ast|Args] -> (/. _ Args))`, from my original post, as this is something that I'm finding difficult to provide a rationale, with my current knowledge, for.

That one will not work because you are returning a lambda value, and the evaluator will not know what to do with it (it expects an s-expression).

Macros process the elements from a stream of s-expressions and must produce as a result another s-expressions, which a lambda is not (but an expression that constructs a lambda is).

Does this answer your question?

Btw, another possible expansion for tests is a function like this:

(define my-test
  description -> "Test description"
  expression -> (ast (actual code))
  expected -> ExpectedValue
  exec -> (actual code))

This avoids the need for global variables.

 

Luca Di Sera

unread,
Nov 8, 2020, 11:24:31 AM11/8/20
to Shen
Not completely and please forgive me for my ignorance here.

What you say makes sense and is how seem to understand this too. But I'm unable to build a complete understanding of the behavior of some of those calls.

I'll try be more thorough in explaining where my confusion comes from so that it may be easier to provide me with the key understanding that I'm currently missing.

As you say, this call fails as macros should produce an s-expr which lambdas might not inhabit.

My first misunderstanding comes from why this fails at call time, instead of the end of the expansion. That is, given:

```
(defmacro ast-macro [ast|Args] -> (/. _ Args))
```
The macro will succeed, returning a closure:

```
(ast (= 10 20))
#<CLOSURE (LAMBDA (_) :IN |ast-macro|) {10025B990B}>
```

But trying to call that closure will result in an error:

```
((ast (= 10 20)) _)
; in: #<CLOSURE (LAMBDA (_) :IN |ast-macro|) {10025C0B5B}> '_
;     (#<CLOSURE (LAMBDA (SHEN::_) :IN SHEN::|ast-macro|) {10025C0B5B}> 'SHEN::_)
; caught ERROR:
;   illegal function call
; compilation unit finished
;   caught 1 ERROR condition
Execution of a form compiled with errors.
Form:
  (#<CLOSURE (LAMBDA (_) :IN |ast-macro|) {10025C0B5B}> '_)
Compile-time error:
  illegal function call
```

This may be a simple derivation of the implementation, but I would thus expect that actually providing a valid expression with a similar behavior would thus fail right away:

```
(defmacro ast-macro [ast|Args] -> [(/. _ Args)])
ast-macro

(ast (= 10 20))
; in: #<CLOSURE (LAMBDA (_) :IN |ast-macro|) {10027098CB}>
;     (#<CLOSURE (LAMBDA (SHEN::_) :IN SHEN::|ast-macro|) {10027098CB}>)
; caught ERROR:
;   illegal function call
; compilation unit finished
;   caught 1 ERROR condition
Execution of a form compiled with errors.
Form:
  (#<CLOSURE (LAMBDA (_) :IN |ast-macro|) {10027098CB}>)
Compile-time error:
  illegal function call
```

This is indeed the case and from how I understand it this should be seen as the following reduction "(ast (= 10 20)) -> (some-closure)" which indeed makes a wrong call as it tries to call a closure without an abstraction without any argument. For example:

```
((/. _ 10))
invalid number of arguments: 0
``` 

This would actually be the error message that I would expect here but I the difference might derive from a more complex implementation that makes the two calls not directly equal.

But then I would consider that if we were to provide the correct s-expr the call would go trough:

```

(defmacro ast-macro [ast|Args] -> [(/. _ Args) 10])
ast-macro

(ast (= 10 20))
; in: #<CLOSURE (LAMBDA (_) :IN |ast-macro|) {10028398CB}> '_
;     (#<CLOSURE (LAMBDA (SHEN::_) :IN SHEN::|ast-macro|) {10028398CB}> 'SHEN::_)
; caught ERROR:
;   illegal function call
; compilation unit finished
;   caught 1 ERROR condition
Execution of a form compiled with errors.
Form:
  (#<CLOSURE (LAMBDA (_) :IN |ast-macro|) {10028398CB}> '_)
Compile-time error:
  illegal function call
```

From my understanding this should be seen as "(some-closure 10)" which should be a valid call.
We might guess that we simply cannot have a closure of that type "naked" in the list, but we have seen examples where this is not the case, a simple one might be:

```
(defmacro ast-macro [ast|Args] -> [print (/. _ Args)])
ast-macro

 (ast (= 10 20))
#<CLOSURE (LAMBDA (_) :IN |ast-macro|) {1002D8637B}>#<CLOSURE (LAMBDA (_) :IN |ast-macro|) {1002D8637B}>
``` 

Interestingly, if we where to try and call that closure we should expect to be similarly in trouble:

```
((ast (= 10 20)) 10)
#<CLOSURE (LAMBDA (_) :IN |ast-macro|) {1002D96ADB}>[[= 10 20]]
```

But we are not. Thus, is the problem that of having the closure in the call-position? But why would this be the case, since the following is valid in shen:

```
((/. _ 10) 10)
```

This to me is confusing. I don't see how this directly derives from the need of a macro to produce a correctly built s-expr.

One guess I have is that I'm misunderstanding how the reader interprets the final ast. While I'm using only abstractions here the same thing happens more generally with curried functions too for example which might corroborate the idea that I'm just misunderstanding how this is expanded.

Another guess might be that there is some problem in how the values are closed over during a macro, which later result in an invalid call form as this seems to happen only when a curried function is in call-position.

I hope this might help find where my thought process is going wrong such as that is easier to explain in a way that I may be able to understand.

> Btw, another possible expansion for tests is a function like this:
>
> (define my-test
> description -> "Test description"
> expression -> (ast (actual code))
>  expected -> ExpectedValue
>  exec -> (actual code))

>This avoids the need for global variables.

This is how initially started implementing this ( not exactly this but the idea of compiling them to a function ).

The problem I had with this was that I couldn't find a sensible way to then collect the tests.

I have the need to allow for a series of tests to be run more than once. This is mostly because one workflow that I want to support ( along with the usual command line tool which runs the tests given some files or tests directory which could simply have the tests being run as a script  similar to how I remember Elixir testing framework doing this), as this is something that I do a lot of, is that of allowing the tests to be run multiple times in a repl.

On another note the first prototype tried to avoid global state with functions.
My initial guess was compiling them into an internally named package and then retrieve the function from  that package.

For example, it looked something like this:

```
(define register-test Name Test -> [package (value *testing-package*) [] (define Name ....   

(define run-tests -> (.... (internal (value *testing-package*)))
```

(Forgive me if there may be some syntax or logical errors here as I'm going by memory).

The problem with this is that this is just unneededly complex. For example the symbols that the user entered into their test would be modified to have the package name prefix and you would have to care about what gets externalized and what not.

Furthermore, while different in some ways, at the essence this is not different from having a global state in a variable, we are just pushing the global state in a package but it's the same kind of global state nonetheless.

At least this was my rationale for having a global variable to collect the tests.

What do you think about this? Do you feel I'm over-complicating things? Is there a way to avoid the global state while still allowing for the repl-based workflow?

Bruno Deferrari

unread,
Nov 8, 2020, 12:23:07 PM11/8/20
to qil...@googlegroups.com
On Sun, Nov 8, 2020 at 1:24 PM Luca Di Sera <diser...@gmail.com> wrote:
Not completely and please forgive me for my ignorance here.

What you say makes sense and is how seem to understand this too. But I'm unable to build a complete understanding of the behavior of some of those calls.

I'll try be more thorough in explaining where my confusion comes from so that it may be easier to provide me with the key understanding that I'm currently missing.

As you say, this call fails as macros should produce an s-expr which lambdas might not inhabit.

My first misunderstanding comes from why this fails at call time, instead of the end of the expansion. That is, given:

```
(defmacro ast-macro [ast|Args] -> (/. _ Args))
```
The macro will succeed, returning a closure:

```
(ast (= 10 20))
#<CLOSURE (LAMBDA (_) :IN |ast-macro|) {10025B990B}>
```

This works in Shen/SBCL, because SBCL will allow it, but that's only accidental. If you try this in Shen/Scheme you will get this:

(0-) (defmacro ast-macro [ast|Args] -> (/. _ Args))
ast-macro

(1-) (ast (+ 1 2))
Exception in *unknown-location*: invalid syntax

This is what happens in SBCL when you eval a lambda:

* (eval (lambda (x) (+ x x)))
#<FUNCTION (LAMBDA (X)) {2240647B}>
 
And this is what happens in Chez (which Shen/Scheme runs on top of):

> (eval (lambda (x) (+ x x)))
Exception: invalid syntax #<procedure>
Type (debug) to enter the debugger.


Now, in SBCL this will not work (will result in the error you are getting now):

((eval (lambda (x) (+ x x))) 1)

But this version will:

(FUNCALL (eval (lambda (x) (+ x x))) 1)

All the confusing behaviour you are experiencing has to do with how the underlying platform works.

This is something you have to take into account when writing Shen code, especially when the code is not statically typed (in typechecked code a lot of what is "undefined" behaviour in dynamically typed code just doesn't typecheck).

But macros are dynamically typed, so will not get an error at macro-definition time. If your macro produces something that is not an s-expression, Shen will not complain, but in the end the underlying platform's evaluator will get an invalid s-expression to work with.

Revise all the situations you described with this in mind, and with the understanding that in the end SBCL will evaluate the code and things should become clearer.
Remember what I mentioned in the other email, macros have to produce an s-expression as a result, AND they can also have side-effects. Every time you produce a (define some-test ...) you can save that name on a list so that you can reference it later:

(defmacro deftest
   [deftest Name ....] -> (do (set *tests* [Name | (value *tests*)])
                              [define Name ....]))

\\ expands into code that constructs the list of tests
\\ TODO: may be a good moment to reset *tests* to `[]` too
(defmacro tests-list
   [tests-list] -> (to-cons (reverse (value *tests*))))

Or instead of (tests-list) a (deftest-runner run-tests-name)`macro instead that expands into (define run-tests-name ...).

Anyway, these are just ideas, there are many ways to go around this. You could define the tests and then require that the names are repeated again at the point where you run them, this is what I have to do when testing OCaml code with Alcotest for example: https://github.com/mirage/alcotest#examples
 
What do you think about this? Do you feel I'm over-complicating things? Is there a way to avoid the global state while still allowing for the repl-based workflow?

I can't tell, because although I have a vague idea, I don't know in full what your constraints are, or how exactly you expect the final result to look like. But other than some confusion caused by the SBCL-specific behaviour you seem to have a good intuition of how things work, so I think that with some more experimentation you should end with a solution that works well for what you are looking for.

One last thing, there is nothing wrong with using global variables, I mentioned the `define` approach just to show a different way to go around it.
 

Luca Di Sera

unread,
Nov 8, 2020, 12:47:15 PM11/8/20
to Shen
Thanks for taking the time to explain this to me. This makes a lot more sense now and I doubt I would have been able to find the correct answer by myself, at least in a reasonable amount of time.


Your suggestions are all quite interesting and are giving me a lot of ideas that I at least need to explore.

Your help was invaluable and you have my utmost regards for it, albeit I'm incapable of repaying such kindness.
You thus have my sincere thanks, however meaningless they may be.

Best Regards,
Di Sera Luca

Mark Tarver

unread,
Nov 8, 2020, 2:21:25 PM11/8/20
to Shen
But macros are dynamically typed, so will not get an error at macro-definition time. If your macro produces something that is not an s-expression, Shen will not complain, but in the end the underlying platform's evaluator will get an invalid s-expression to work with. 

That is true but of course if type checking is enabled the code produced by the macro will be checked. 

Mark

Mark Tarver

unread,
Nov 9, 2020, 8:25:09 AM11/9/20
to Shen
I arrived at this party a bit late, due to enjoying the weekend.

I'll first state the problem I wanted to solve, as I may be approaching this in the wrong way in shen.

I'm doing a small test framework for my future needs. Two requirements that I have are that the tests that the user enters are not run right away and that, in case of some failure, I will be able to show the user what was evaluated. This means that I both need to remember the ast and need it to be evaluated at a later point in time, possibly more than one time at different time points t.
For example, from the current iteration, a failure may look like this:
```
(5-) (testing "Trivial Tests" (= 10 20) (= (* 10 (+ 20 30)) 10))
(6-) (run-tests)
Testing: Trivial Tests
    Failed: Expected 10 // 10 But Got 20 // 20 Instead
    Failed: Expected (* 10 (+ 20 30)) // 500 But Got 10 // 10 Instead
testing.done

This might help.

(defmacro astmacro
  [testing Label | Tests] -> [do [output "Testing ~A ~%" Label]
                                 (assemble-tests Tests)])
  
(define assemble-tests
   [] -> "testing done"
   [[= Expected Actual] | Z] -> (let String (make-string "~R" Expected)
                                     [let (protect EvalExpected) Expected
                                          (protect EvalActual) Actual                                     
                                     [if [= (protect EvalExpected) (protect EvalActual)] 
                                         "skip" 
                                         [do [output "Failed: expected ~A // ~A, got ~A instead~%" 
                                              String (protect EvalExpected) (protect EvalActual)]
                                     (assemble-tests Z)]]]))

If you track this macro you'll find it outputs type secure code.   Assuming this is ok (typed in a hurry here) you might find some elucidation.

(42+) (testing "Trivial Tests" (= 10 20) (= (* 10 (+ 20 30)) 10))
Testing Trivial Tests
Failed: expected 10 // 10, got 20 instead
Failed: expected (* 10 (+ 20 30)) // 500, got 10 instead
"testing done" : string

Mark


Mark Tarver

unread,
Nov 9, 2020, 8:36:58 AM11/9/20
to Shen
Sorry; forgot to test on positive cases - used only the OP example.

(define assemble-tests
   [] -> "testing done"
   [[= Expected Actual] | Z] -> (let String (make-string "~R" Expected)
                                    [let (protect EvalExpected) Expected
                                         (protect EvalActual) Actual                                     
                                     [do [if [= (protect EvalExpected) (protect EvalActual)] 
                                             "skip" 
                                             [output "Failed: expected ~A // ~A, got ~A instead~%" 
                                                              String 
                                                              (protect EvalExpected) 
                                                              (protect EvalActual)]]
                                          (assemble-tests Z)]]))
                                          
(testing "Trivial Tests" (= 10 (+ 5 5)) (= 10 20) (= (* 10 (+ 20 30)) 10))
Testing Trivial Tests
Failed: expected 10 // 10, got 20 instead
Failed: expected (* 10 (+ 20 30)) // 500, got 10 instead
"testing done" : string

Off out and about.

Mark          

Mark Tarver

unread,
Nov 9, 2020, 11:30:51 AM11/9/20
to Shen
I couldn't find a direct way to do this in shen as there isn't a direct quote/dequote mechanism.
 
(defmacro quote
  [quote [X | Y]] -> [cons [quote X] [quote Y]]
  [quote X] -> X)

(8+) (quote (string? a))
[string? a] : (list symbol)

(9+) (quote (+ 9 8))
type error - \\ because [+ 9 8] is not well typed

Mark

Luca Di Sera

unread,
Nov 9, 2020, 5:16:10 PM11/9/20
to Shen
Hello Dr. Tarver!

Thanks for taking the time to respond!

Thanks for the code examples, they allow me to have an idea of how this should look like from an experienced shen coder and provides me with further inspiration for exploration.

I must say that for now, while I'm in a prototyping phase, I have the typecheker turned off, albeit I have an idea of how I want to type-check and hope that I will be able to do that when I have a more solid idea of where I'm going, such that knowing how type safe code will have to look like helps me immensely.

I actually have a question about your code, as it answers a problem I was dealing with yesterday when rewriting the program from scratch again.

My first version of the equal tests was similar to yours, that is, it was the following:

```
(define compile-test
  [= Left Right] -> [let  LeftResult   Left
                                     RightResult Right
                      [if [= LeftResult RightResult]
                          success
                          [cons failure [cons (to-cons Left) [cons LeftResult [cons (to-cons Right) [cons RightResult []]]]]]]]
........................
)
```

Now, as you obviously will know, this call will fail, as `LeftResult` and `RightResult` will be found to be unbounded variables.

I temporarily had this changed to a simple, bugged version, such that `Left` and `Right` were evaluated more than once, without the `let`, to work on the other parts of the program as I wasn't finding a way to make this work.

I've now tried your version and, as expected, it works correctly.

I've seen `protect` for the first time in the book of shen, where I remember it appearing near some of the `gensym` calls, specifically in the first or second chapter about shen's type system ( albeit I think it appeared in other parts of the book too ).
If my memory is not completely failing me, there wasn't a specific explanation about what `protect` did and `Appendix A` doesn't have an entry for it.

My question is: What is the semantic of `protect` and why does it make this code correct?

Mark Tarver

unread,
Nov 9, 2020, 5:26:22 PM11/9/20
to Shen
I've seen `protect` for the first time in the book of shen, where I remember it appearing near some of the `gensym` calls, specifically in the first or second chapter about shen's type system ( albeit I think it appeared in other parts of the book too ). 
 
  You'll find it explained at the end of 2.7.

Mark

--
You received this message because you are subscribed to the Google Groups "Shen" group.
To unsubscribe from this group and stop receiving emails from it, send an email to qilang+un...@googlegroups.com.

Luca Di Sera

unread,
Nov 9, 2020, 5:36:02 PM11/9/20
to Shen
You're indeed right. I've just checked and the explanation is there and makes complete sense.

Thanks for taking the time to point me in the right direction and excuse for forgetting that it was explained in the book!

Best Regards,
Di Sera Luca

Reply all
Reply to author
Forward
0 new messages