sci

2024-10-09T10:10:13.376019Z

Hello, I'm a bit stuck trying to improve some stacktrace information when calling Hiccup from Sci, specifically with lazy sequences ๐Ÿงต

2024-10-11T08:59:58.032769Z

I've created an issue https://github.com/babashka/sci/issues/942 Hopefully it is clear enough

borkdude 2024-10-11T09:15:24.191929Z

thanks!

2024-10-18T09:25:31.430829Z

Works well! Even some quick tries with transducers. I could find one more case specific to map i think:

["(str (map (fn [a] a) 1))"  [1 6]] ;=> [1 1]

borkdude 2024-10-18T09:27:19.783529Z

I don't think that case is solvable due to laziness, clojure itself also doesn't know the location when you execute this: test.clj

(str (
      map
      (fn [a] a)
      1))
user=> (load-file "/tmp/test.clj")
Execution error (IllegalArgumentException) at java.lang.Object/toString (Object.java:270).
Don't know how to create ISeq from: java.lang.Long

borkdude 2024-10-18T09:28:28.882739Z

if I would radically re-engineer SCI in how the stack trace is produced, I could maybe make it work...

2024-10-18T09:30:32.728129Z

Yeah can imagine this one is tricky. But to be fair I think Sci's error reporting is close to be better than Clojure's error reporting ๐Ÿ˜Ž

borkdude 2024-10-18T09:31:21.303289Z

just out of curiosity, what are you building with SCI on the JVM?

2024-10-18T09:32:40.397189Z

Something like pcp , like a web framework, but it is very early days

๐Ÿ‘ 1
2024-10-18T09:33:24.417009Z

But I try to use the error information to display inline errors. A bit like babashka

2024-10-18T09:35:30.054279Z

I use it with hiccup, so if the error information is not precise the displayed error is not very helpful

2024-10-18T09:36:22.143409Z

In general with the column information you normally get very nice errors with Sci

๐Ÿ‘ 1
2024-10-09T10:10:22.554709Z

I'm using the stacktrace information to highlight the exact location in the code

2024-10-09T10:10:48.004239Z

The problem is that lazy sequences are only triggered during the html building, so the Sci stacktraces don't point to the line of a for call

2024-10-09T10:11:08.798849Z

To illustrate this problem:

;; line 1, column 1

(try (sci/eval-string "(str 
                         (for [[a] 1] a))") 
     (catch Exception e 
       (select-keys (ex-data e) [:line :column])))

;; vs doall, line 2, column 26

(try (sci/eval-string "(str 
                         (doall (for [[a] 1] a)))") 
     (catch Exception e 
       (select-keys (ex-data e) [:line :column])))

2024-10-09T10:11:26.240639Z

A quick hack is to change the 'for' binding to a for wrapped with doall

2024-10-09T10:11:50.667429Z

But I would prefer to do this only in the context of hiccup and not for the whole Sci context. Not sure if this can be done from a macro?

2024-10-09T10:12:21.604309Z

The other thing I was thinking about is to port the Hiccup interpreter to Sci and do something with the stacktraces, but I'm not sure if I can manipulate the stacktrace in a sensible manner this way

2024-10-09T11:32:03.480559Z

It seems to be only an issue when something goes wrong in the destructuring or in the arguments of for or map et al. If there is an issue in the body of the macro or function the stacktrace is correct

2024-10-09T11:33:28.794899Z

e.g.

(try (sci/eval-string "(str
                         (for [a 1] (/ a 0) a))")
     (catch Exception e
       (select-keys (ex-data e) [:line :column])))
=> {:line 2, :column 26}

2024-10-09T11:37:15.498099Z

vs

(try (sci/eval-string "(str 
                         (map (fn [a] (/ a 0)) 1))") 
     (catch Exception e 
       (select-keys (ex-data e) [:line :column])))
=> {:line 1, :column 1}

borkdude 2024-10-09T12:03:09.159409Z

hey @jeroenvandijk let's see...

borkdude 2024-10-09T12:04:27.182309Z

yeah I think this is just how laziness works unfortunately, not much SCI can do here I'm afraid

borkdude 2024-10-09T12:05:14.018159Z

you're using SCI directly for hiccup, so this isn't related to bb or scittle or so right?

2024-10-09T12:05:43.882619Z

Yeah Sci directly for hiccup

2024-10-09T12:06:37.366919Z

This is another hack I found

(sci/eval-string* (sci/init {:deny '[for]
                             :bindings {'for2 (macrofy (fn [&form & args]
                                                         (list (with-meta 'doall (meta &form)) (apply for-macro/expand-for &form args))))
                                        'html (macrofy
                                               (fn [_ _ body]
                                                 (clojure.walk/postwalk (fn [x]
                                                                          (if (= x 'for)
                                                                            (with-meta 'for2 (meta x))
                                                                            x))
                                                                        body)))}

                             })
                  "(html (for [[i] (range 100)] (/ i 0)))")
So the hiccup macro would change the bindings, although it can mess thing up with other things named for

borkdude 2024-10-09T12:07:14.410429Z

you could just do this:

{:namespaces {'clojure.core {'for ...}}}
to replace for

2024-10-09T12:07:55.759169Z

yeah true, but then i would do it everywhere, even if I am not rendering html. I was hoping to isolate more to hiccup

2024-10-09T12:08:28.855179Z

I was also wondering if there is a way to access the ctx from a macro, but i think this is not possible, right?

borkdude 2024-10-09T12:08:34.699579Z

you could use with-redefs in your macro, but also this would not play well with laziness

borkdude 2024-10-09T12:10:46.445299Z

the way to access the ctx from a macro would be something like this:

(require '[sci.ctx-store :as store])
(def ctx (sci/init ..))
(store/reset-ctx! ctx)
And in the macro call (store/get-ctx)

2024-10-09T12:10:52.445339Z

hmmm i will give that I try. I think in sci you don't have to worry about thread safety for with-redefs

borkdude 2024-10-09T12:11:41.478869Z

> I think in sci you don't have to worry about thread safety for with-redefs hmm, it depends if you're running SCI in multiple threads

2024-10-09T12:12:23.332929Z

I do, but maybe I'm still safe because of sci/fork. Although not sure

borkdude 2024-10-09T12:12:44.656129Z

the built-in vars are all shared

2024-10-09T12:12:51.416859Z

ah i see

borkdude 2024-10-09T12:13:28.895959Z

so maybe it would be the safest to make a combination of what we had. copy the SCI for macro into a new var, add this to clojure.core and then use with-redefs

borkdude 2024-10-09T12:14:01.657049Z

or what you could also do

borkdude 2024-10-09T12:14:12.241089Z

make the for macro in SCI dynamic and then use binding

borkdude 2024-10-09T12:14:18.532299Z

then it would be thread-safe

2024-10-09T12:14:38.947889Z

ah cool, yeah that's smart

2024-10-09T12:14:58.250119Z

I will give it a try, thanks!

2024-10-09T12:37:10.103389Z

I can't seem to bind the macro to the dynamic var. Here is some of my experiment. I'm probably missing something

(require '[sci.impl.for-macro :as for-macro])

(def for-macro (sci/new-var 'for nil #_for-macro/expand-for {;:sci/macro true 
                                                             :dynamic true}))


(sci/eval-string* (sci/init {:namespaces {'hiccup {'eager-for (macrofy (fn [&form & args]
                                                                           '(throw (ex-info "for eager" {}))
                                                                           #_(list (with-meta 'doall (meta &form))
                                                                                   (apply for-macro/expand-for &form args))))
                                                   'html (macrofy (fn [_ _ & body]

                                                                    (concat (list 'binding '[clojure.core/for hiccup/eager-for])
                                                                            body)))}
                                          'clojure.core {'for for-macro
                                                         '*for* for-macro}}})
"(binding [*for* hiccup/eager-for] (*for* 2))"

                 #_ "(str
(binding [*for* hiccup/eager-for] 
  (clojure.core/*for* 2 ;#_[[i] (range 100)] (/ i 0)
)))"
  #_                "(str
(hiccup/html 
(for [[i] (range 100)] (/ i 0))))")

2024-10-09T12:44:17.545069Z

I think you meant to make for a dynamic var, right? In clojure this would not be possible AFAIK

borkdude 2024-10-09T12:45:37.773849Z

There's probably something hard-coded in SCI concerning for But something like this isn't go work anyway:

(sci/eval-string* ctx "(defmacro my-for [& args] 42) (binding [for @#'my-for] (for [i [1 2 3]] i))")
since the binding doesn't take effect during macro-expansion, the macro is expanded at that time

borkdude 2024-10-09T12:47:00.131829Z

so never mind what I said, too optimistic when not trying it for real in a REPL ;)

2024-10-09T12:47:27.447169Z

ah too bad, was worth a try though

borkdude 2024-10-09T12:57:09.518499Z

so yeah I think your postwalk thing might be ok, but at the risk that you would also replace for local variables and function arguments etc ;)

2024-10-09T13:08:01.850669Z

Thanks for your help. I'll play with it and see how it goes

2024-10-09T18:40:47.991099Z

One more question, since it is possible to point to the exact location in case of an error in the body of a lazy sequence e.g. in the case of (for [i (range 10)] (/ i 0)) or (map (fn [i] (/ i 0)) (range 10)), sci can point out the exact location of /. Shouldn't this be possible somehow for the destructuring bit for instance? I tried to look at the code, but I don't see directly where all this is happening

borkdude 2024-10-09T20:04:55.339019Z

hmm, you may be right, can you maybe post a SCI issue about a better error location reporting for your case? perhaps it's just a matter of fixing some metadata somewhere

๐Ÿ‘ 1
borkdude 2024-10-17T10:38:28.300889Z

Fixed:

$ clj -M:babashka/dev -e '(str (let [[a] 1] a))'
----- Error --------------------------------------------------------------------
Type:     java.lang.UnsupportedOperationException
Message:  nth not supported on this type: Long
Location: <expr>:1:6

----- Context ------------------------------------------------------------------
1: (str (let [[a] 1] a))
        ^--- nth not supported on this type: Long

โค๏ธ 1
borkdude 2024-10-17T10:39:51.938259Z

Hm, for should also still be improved

borkdude 2024-10-17T10:45:54.425839Z

for is a lot harder due to laziness probably

2024-10-17T10:49:50.774469Z

Can imagine. I will have a look at your fix. I didn't know where to look actually to fix it

borkdude 2024-10-17T10:50:04.185219Z

o I wait, I do see something improving here:

1: (str (for [[a] [0]] (/ 1 a)))
        ^--- nth not supported on this type: Long

โค๏ธ 1
2024-10-17T10:50:04.193299Z

Thanks!

borkdude 2024-10-17T10:50:10.119269Z

anyway, I'll also dig a little further

2024-10-17T10:50:49.702729Z

> o I wait, I do see something improving here: I think that's a major improvement

2024-10-17T10:51:12.961319Z

Looks small here, but in a bigger context it is significant

borkdude 2024-10-17T12:32:00.916399Z

are you using this from JVM or CLJS?

2024-10-17T12:44:54.090479Z

From the JVM

borkdude 2024-10-17T16:23:00.859009Z

pushed another improvement for for. feel free to post more cases

2024-10-17T17:03:36.635789Z

Cool! I found one more. Do you want me to post it here?

2024-10-17T17:09:08.404799Z

Failing:

["(str (map (fn [[a]] a) [0]))" [1 6]] ;=> [1 1]
           ["(str (filter (fn [[a]] a) [0]))" [1 6]] ;=> [1 1]
I'm guessing this is a different kind of case

2024-10-17T17:12:45.557359Z

Failing:

["(str (if-let [[a] 0] a))" [1 6]] ;=> [1 1]
["(str (when-let [[a] 0] a))" [1 6]] ;=> [1 1]
["(str (if-some [[a] 1] a))" [1 6]] ;=> [1 1]
["(str (when-some [[a] 1] a))" [1 6]] ;=> [1 1]
I'm guessing this is more of what you did before

2024-10-17T17:15:41.998809Z

Ok:

["(str (loop [[a] 0] a))" [1 6]]
["(str (letfn [(f [[a]] a)] (f 1)))" [1 27]]

borkdude 2024-10-17T17:27:00.618859Z

thanks!

2024-10-17T17:31:46.105079Z

np!

borkdude 2024-10-17T18:41:49.933069Z

all of the above work now

borkdude 2024-10-17T18:54:20.220839Z

also handled most doseq cases

2024-10-17T21:53:19.997849Z

Wow, that's amazing. Thank you!