sci

https://github.com/babashka/SCI - also see #babashka and #nbb
borkdude 2020-07-04T07:07:51.177400Z

Nice!

borkdude 2020-07-04T15:48:09.178200Z

@lee Can I view your rewrite-cljc test somewhere? I might use it as inspiration for incorporating clojure.spec + test.check maybe at one point in bb

lread 2020-07-04T15:49:42.179300Z

yeah, will make public at some point… currently am using local/root hacks here and there… after I get rid of those.

lread 2020-07-04T15:52:03.181300Z

I am currently trying to track down why with-out-str is not working within a deftest when interpreted by sci. Will let you know more after I distill.

borkdude 2020-07-04T15:53:21.182300Z

does it work when you run the code outside a deftest?

lread 2020-07-04T15:53:56.183Z

yeah, that’s the confusing thing, am gonna look at what macro expansion is doing.

lread 2020-07-04T15:54:52.183500Z

BTW, my current native-image ram usage blows CircleCI 3.5gb but should fit GitHub actions.

borkdude 2020-07-04T15:56:49.184Z

@lee Can you repro similar behavior with bb?

$ bb "(use 'clojure.test) (deftest foo (is (= \"hello\n\" (with-out-str (println \"hello\"))))) (run-tests *ns*)"

Testing user

Ran 1 tests containing 1 assertions.
0 failures, 0 errors.
{:test 1, :pass 1, :fail 0, :error 0, :type :summary}

borkdude 2020-07-04T16:01:35.186600Z

I see a similar blow-up with clojure.spec. It would be really nice we could avoid that

borkdude 2020-07-04T16:01:51.187100Z

the image size also blows up. Maybe it's one or a few specific tweaks

lread 2020-07-04T16:02:10.187300Z

It gets weirder. It does pass for the foo case. I’ll share the particulars once I figure out what is distinct.

lread 2020-07-04T16:04:42.189700Z

Perhaps a booboo on my part somewhere. Dunno yet. One thing that is different about the namespace I am hitting in my test is that is does have a (:refer-clojure :exclude [print]) - will fill you in more as I learn more.

lread 2020-07-04T16:06:25.190700Z

Last executable I built is 56mb. I’ve made zero attempt at reducing anything.

lread 2020-07-04T18:53:29.198200Z

I expect my with-out-str confusion might be me not understanding how sci works here. I’ll work through an example with babashka. We know that the following works:

(def a (with-out-str (print "fiddle sticks")))
(println "captured:" a)
And will output:
captured: fiddle sticks
But let’s say I’ve added a namespace to babashka that prints to stdout:
(ns lilapi.hiya)

(defn i-do-print []
  (println "Oh hello"))
And defined how it should be exposed in babashka like so:
(ns babashka.impl.lilapi
  {:no-doc true}
  (:require [lilapi.hiya :as hiya]
            [sci.core :as sci]))

(def hiya-ns (sci/create-ns 'lilapi.hiya))

(def lilapi-hiya-namespace
  {'i-do-print (sci/copy-var hiya/i-do-print hiya-ns)})
And in babashka.main, added require for [babashka.impl.lilapi :refer [lilapi-hiya-namespace]] and referenced it namespaces map via 'lilapi.hiya lilapi-hiya-namespace. When I now interpret the following:
(require '[lilapi.hiya :as hiya])

(def c (with-out-str (hiya/i-do-print)))
(println "captured:" c)
I get the following output:
Oh hello
captured: 
And you’ll probably tell me this is normal.

borkdude 2020-07-04T19:05:52.199Z

@lee sci's with-out-str uses sci/out to capture output

borkdude 2020-07-04T19:10:36.200900Z

the reason for this difference is that sci programs don't get access to Clojure vars, in order not to have detrimental and lasting effects on those.

borkdude 2020-07-04T19:14:29.201300Z

let me create an example, I can understand this is confusing

borkdude 2020-07-04T19:18:18.201700Z

@lee :

user=> (require '[sci.core :as sci])
nil
user=> (defn foo [] (println "yello!"))
#'user/foo
user=> (sci/eval-string "(with-out-str (foo))" {:bindings {'foo foo}})
yello!
""
user=> (sci/eval-string "(with-out-str (foo))" {:bindings {'foo (fn [] (binding [*out* @sci/out] (foo)))}})
"yello!\n"

borkdude 2020-07-04T19:19:14.202200Z

so you need to write a little wrapper for functions that write to *out*, rebinding *out* to @sci/out

lread 2020-07-04T20:08:40.203600Z

aha, thanks, will try when I get back to kb!

borkdude 2020-07-04T20:13:50.204Z

@lee I'm using the same pattern here for clojure.pprint: https://github.com/borkdude/babashka/blob/b05c36da35f32df2dad391d5b67be64cb99126c0/src/babashka/impl/clojure/pprint.clj#L26

lread 2020-07-04T21:46:05.206300Z

Thanks, that worked! I could add some guidance to the README under https://github.com/borkdude/sci/tree/master#stdout-and-stdin but I think I only currently understand the how and am not entirely clear on the why.

borkdude 2020-07-04T21:49:08.209100Z

The why is that sci implements its own var type, hence it has its own in and out using that var type. The reason sci has its own var type is that sci is designed to be safe, scripts/program fragments can only mutate what you give them. If sci programs were to directly have access to vars, users could do all kinds of stuff to mess things up in the Clojure runtime. That's why the separation exists.

lread 2020-07-04T21:50:58.210200Z

Right, that makes sense.

lread 2020-07-04T21:57:08.213200Z

Let me see if I kind of understand the workings, in your README example

(sci/binding [sci/out *out*
              sci/in *in*]
  (sci/eval-string "(print \"Type your name!\n> \")")
  (sci/eval-string "(flush)")
  (let [name (sci/eval-string "(read-line)")]
    (sci/eval-string "(printf \"Hello %s!\" name)
                      (flush)"
                     {:bindings {'name name}})))
Type your name!
> Michiel
Hello Michiel!
You make *out* (and *in*) available to the sci interpreter, and then in your example from above:
(sci/eval-string "(with-out-str (foo))" {:bindings {'foo (fn [] (binding [*out* @sci/out] (foo)))}})
"yello!\n"
You map *out* to sci’s out for the interpreted code.

lread 2020-07-04T21:59:14.214400Z

I think I can do a little PR for the README on this - if it is of interest to you.

borkdude 2020-07-04T22:01:03.216100Z

- When you sci-bind sci/out to *out* then clojure.core/println from the SCI runtime has the effect of writing to whatever *out* is bound to - When you clojure-bind *out* to sci/out then clojure.core/println from the Clojure runtime has the effect of writing whatever sci/out is bound to

borkdude 2020-07-04T22:02:18.216500Z

so there is a nice symmetry

lread 2020-07-04T22:05:36.217400Z

It only makes sense to do the latter in interpreted code, correct?

borkdude 2020-07-04T22:07:55.217700Z

The latter happens for example in the pprint link I sent earlier

lread 2020-07-04T22:08:33.218500Z

Oh right that’s not interpreted. Kind of a bit mind bendy.

borkdude 2020-07-04T22:11:40.219300Z

In interpreted code there is no sci/binding or sci/out, only binding and *out*

borkdude 2020-07-04T22:11:51.219600Z

ostensibly, but these are backed by sci's implementations of these

borkdude 2020-07-04T22:13:02.220600Z

one other reason the distinct var type exists is that users can create new (dynamic) vars in their programs and this should not be visible to the outside clojure runtime, so "no side effects", it only affects the sci ctx

lread 2020-07-04T22:13:51.220800Z

sciandboxed

borkdude 2020-07-04T22:14:04.221Z

nice

lread 2020-07-04T22:19:09.221500Z

You do talk about this under Vars https://github.com/borkdude/sci#vars , maybe it just takes a while to digest.

borkdude 2020-07-04T22:20:11.222100Z

Maybe the "why" can be clarified more with some words I posted here. PR welcome 🙂

lread 2020-07-04T22:21:31.223300Z

I think I’d better sleep on it :simple_smile: then I’ll make an attempt. I feel like a diagram might even be useful?

borkdude 2020-07-04T22:26:21.223600Z

sleep sounds good. ttyl

lread 2020-07-04T22:26:39.224Z

thanks for the interesting chat! g’night!