joyride

pez 2022-12-01T17:10:16.923719Z

What should I do to require node-fetch in Joyride?

; Evaluating file: util_aoc.cljs
; require() of ES Module /Users/pez/Projects/joyride-aoc/.joyride/node_modules/node-fetch/src/index.js from /Users/pez/.vscode/extensions/betterthantomorrow.joyride-0.0.24/out/joyride.js not supported.
; Instead change the require of index.js in /Users/pez/.vscode/extensions/betterthantomorrow.joyride-0.0.24/out/joyride.js to a dynamic import() which is available in all CommonJS modules.
My ns form looks like so:
(ns util-aoc
  (:require ["vscode" :as vscode]
            ["node-fetch" :as fetch]
            [clojure.edn :as edn]
            [clojure.string :as string]))

borkdude 2022-12-01T17:15:43.912379Z

in electron you can't require ES modules, so you either have to find out how to require the CJS module, or do this:

(promesa.core/let [mod (js/import "node-fetch") fetch (.-fetch mod) response (fetch ...)] ...)

borkdude 2022-12-01T17:16:29.817329Z

@pez https://www.npmjs.com/package/node-fetch-commonjs

๐Ÿ™ 1
pez 2022-12-01T17:16:55.852319Z

Oh, wow. We should update the docs. ๐Ÿ˜ƒ

borkdude 2022-12-01T17:18:30.149869Z

you can also use axios

borkdude 2022-12-01T17:19:21.925239Z

luckily import is idempotent; the second time you do dynamic import on an already loaded module, it's memoized

skylize 2022-12-01T17:23:48.701289Z

Or skip adding extra deps; and just use Node built-in util.promisify to transform the Node built-in http.get into a using a Promise API.

๐Ÿคฏ 1
pez 2022-12-01T17:32:26.064359Z

Does it have a way to provide a cookie?

pez 2022-12-01T17:33:41.744739Z

Yes, it has, but in a very roundabout way... I'll go with dependencies ๐Ÿ˜ƒ

skylize 2022-12-01T17:43:55.156059Z

What does node-fetch offer for cookies that Node's http and https libs don't do? It's just one of the headers.

skylize 2022-12-01T17:45:52.911039Z

In fact looking at the docs for node-fetch, looks like it makes dealing with cookies harder, by not saving cookies by default, and requiring you to use a separate "raw" response to read them.

skylize 2022-12-01T18:04:14.244279Z

Answering your question directly, "Does it have a way to provide a cookie?": I haven't done that recently, but if memory serves, you should be able to just include a headers object in the options map. So in JS, with get already promisified, that would look like:

get(url, {headers: {'Cookie': 'myCookie=myvalue'}})
  .then(handle-response)

๐Ÿ™ 1
pez 2022-12-01T21:15:01.944929Z

Having troubles with importing modules from npm here. I have this structure:

~/Projects/joyride-aoc/.joyride(:|โœ”) % tree -CI node_modules 
.
โ”œโ”€โ”€ package-lock.json
โ”œโ”€โ”€ package.json
โ””โ”€โ”€ scripts
    โ”œโ”€โ”€ aoc2022_1.cljs
    โ”œโ”€โ”€ clojuredocs.cljs
    โ”œโ”€โ”€ london_examples.cljs
    โ”œโ”€โ”€ next_slide.cljs
    โ”œโ”€โ”€ reset_prezo.cljs
    โ”œโ”€โ”€ showtime.cljs
    โ”œโ”€โ”€ terminal_demo.cljs
    โ”œโ”€โ”€ typist.cljs
    โ”œโ”€โ”€ util_aoc.cljs
    โ”œโ”€โ”€ webview
    โ”‚   โ”œโ”€โ”€ example.cljs
    โ”‚   โ””โ”€โ”€ page.html
    โ””โ”€โ”€ workspace_activate.cljs
๐Ÿงต

pez 2022-12-01T21:15:54.094409Z

in util_aoc.cljs, this ns form:

(ns util-aoc
  (:require ["vscode" :as vscode]
            ["requestify" :as fetch]
            [clojure.edn :as edn]
            [promesa.core :as p]
            [clojure.string :as string]))

pez 2022-12-01T21:16:36.330289Z

In aoc2022_1.cljs this content:

(ns aoc2022-1
  (:require [util-aoc :as aoc]))

(def input (aoc/values+ 1))

pez 2022-12-01T21:18:25.062059Z

Now starting from a fresh REPL, I load aoc2022_1.cljs, and get:

; Evaluating file: aoc2022_1.cljs
; Cannot find module 'requestify'
; Require stack:
; - /Users/pez/Recordings/util_aoc.cljs
; Evaluation of file aoc2022_1.cljs failed: #error {:message "Cannot find module 'requestify'\nRequire stack:\n- /Users/pez/Recordings/util_aoc.cljs", :data {:type :sci/error, :line 2, :column 3, :message "Cannot find module 'requestify'\nRequire stack:\n- /Users/pez/Recordings/util_aoc.cljs", :sci.impl/callstack #object[cljs.core.Volatile {:val ({:line 2, :column 3, :file "/Users/pez/Projects/joyride-aoc/.joyride/scripts/aoc2022_1.cljs", :ns #object[T1 aoc2022-1]} {:line 2, :column 3, :file "util_aoc.cljs", :ns #object[T1 util-aoc]})}], :file "util_aoc.cljs"}, :cause #object[Error Error: Cannot find module 'requestify'

pez 2022-12-01T21:19:03.845889Z

I can load util_aoc.cljs and use requestify just fine.

borkdude 2022-12-01T22:00:03.339339Z

dunno, if you can make a issue repro, I can take a look sometime soon, but you could also try debugging the :load-fn yourself where it loads the npm libs. could be an issue with resolving in the wrong directory or so

๐Ÿ™ 1
pez 2022-12-01T21:53:49.899289Z

Also, did we ever find a way to unwrap a promise? I fail to find a way to look at the values of my aoc-input in the REPL

borkdude 2022-12-01T21:59:07.240379Z

@pez you can do (.then #(def res %)) and then inspect res

pez 2022-12-01T22:03:06.224619Z

I'm doing something that maybe is similar:

(ns aoc2022-1
  (:require [promesa.core :as p]
            [util-aoc :as aoc]))

(defn foo []
  (p/let [input (aoc/values+ 1)]
    (def input input)))

(foo)

borkdude 2022-12-01T22:03:23.107189Z

yeah, that's similar

pez 2022-12-01T22:06:47.282379Z

But (.then (aoc/values+ 1) #(def input %)) is neater. Thanks!

pez 2022-12-01T22:07:23.644519Z

Maybe we could have that as a repl utility in Joyride somehow?

borkdude 2022-12-01T22:13:47.021479Z

I've recommended doing this as a macro in nbb before we had the async thing.

(defmacro defp [name & body]
  `(.then (do ~@body) #(def ~name %)))

pez 2022-12-01T23:14:30.262279Z

This would be in Joyride or can a Joyride script do it?

borkdude 2022-12-01T23:19:27.013529Z

you can script this

pez 2022-12-01T23:20:04.258159Z

I get errors:

; Evaluating file: util_aoc.cljs
; Could not resolve symbol: clojure.core/unquote-splicing
; Evaluation of file util_aoc.cljs failed: #error {:message "Could not resolve symbol: clojure.core/unquote-splicing", :data {:type :sci/error, :line 10, :column 14, :message "Could not resolve symbol: clojure.core/unquote-splicing", :sci.impl/callstack #object[cljs.core.Volatile {:val ({:line 9, :column 1, :ns #object[T1 util-aoc], :file "/Users/pez/Projects/joyride-aoc/.joyride/scripts/util_aoc.cljs"} {:line 10, :column 3, :ns #object[T1 util-aoc], :file "/Users/pez/Projects/joyride-aoc/.joyride/scripts/util_aoc.cljs"} {:line 10, :column 10, :ns #object[T1 util-aoc], :file "/Users/pez/Projects/joyride-aoc/.joyride/scripts/util_aoc.cljs"} {:line 10, :column 14, :ns #object[T1 util-aoc], :file "/Users/pez/Projects/joyride-aoc/.joyride/scripts/util_aoc.cljs"})}], :file "/Users/pez/Projects/joyride-aoc/.joyride/scripts/util_aoc.cljs", :phase "analysis"}, :cause #error {:message "Could not resolve symbol: clojure.core/unquote-splicing", :data {:type :sci/error, :line nil, :column nil, :file "/Users/pez/Projects/joyride-aoc/.joyride/scripts/util_aoc.cljs", :phase "analysis"}}}

borkdude 2022-12-01T23:20:25.289219Z

you need a syntax quote before (.then

borkdude 2022-12-01T23:22:07.035109Z

(edited my original post)

pez 2022-12-01T23:22:41.968449Z

๐Ÿš€

borkdude 2022-12-01T23:23:18.229209Z

tested it in nbb:

user=> (defp x (p/delay 1000 :hello))
#<Promise[~]>
user=> x
:hello 

borkdude 2022-12-01T23:24:06.067499Z

๐Ÿ˜ด

pez 2022-12-01T23:25:31.389269Z

Sleep tight!

borkdude 2022-12-01T22:13:59.050269Z

and then:

(defp x (some-promise-thing))

borkdude 2022-12-01T22:14:41.261199Z

but having some REPL utility is probably better for this: (joyride.repl/await ...)

borkdude 2022-12-01T22:15:32.489589Z

since the REPL is already async, you can unwrap promises. in nbb the whole evaluation is async (because ES modules forced that upon nbb, which is not the case with electron + cjs modules), this is why you can use it in normal code on the top level as well

pez 2022-12-01T22:16:34.869019Z

Can you complete the example (joyride.repl/await ...)? ๐Ÿ˜ƒ

pez 2022-12-01T22:17:01.689759Z

Maybe await-def?

borkdude 2022-12-01T22:17:26.087119Z

not sure. in any case we could document the defp macro

skylize 2022-12-01T22:36:42.609529Z

I seem to be the only person in the world who thinks await was always just a bad idea from the start. Sticking with the monadic wrapper it is built on top of (a Promise) is so much more honest about what your code is actually doing, while still giving you everything you need to get the work done. But even Haskellers seem to love using do notation everywhere; using imperative code to describe their functional transformations. So what ground do I have to stand on?

pez 2022-12-01T22:39:54.291259Z

Here's my use-case for await-def in Joyride. (Spoiler alert, AOC 2002 p1-1)๐Ÿงต

borkdude 2022-12-02T08:04:38.841549Z

> My best interpretation of the SCI docs and existing code is that I should be able to add it to the map after`'clojure.core` at https://github.com/BetterThanTomorrow/joyride/blob/8de67c5eb6eec93767fcd9df05a710ce5d274da6/src/joyride/sci.cljs#L78. Correct

skylize 2022-12-02T13:06:10.190049Z

> This stuff is not hot-reloaded. Oh yes. I forgot to build before testing. ๐Ÿคฆ So all the relevant symbols exist now. But I am not really sure how to try setting a print target with bound-fn* not available.

=> (add-tap prn)
nil
=> (tap> :foo)
true
; no print output

๐Ÿค” 1
skylize 2022-12-02T18:58:11.828599Z

Side note: once tap> is working, my suggestion of an explicitly defined tap function could be optionally replaced by an inline #(doto % tap>).

borkdude 2022-12-02T19:02:23.891899Z

My rule of thumb is to not add things to clojure.core in SCI that are not in clojure core.

skylize 2022-12-02T19:31:21.072279Z

Is that in reference to`bound-fn*`? That is in Clojure core (https://github.com/clojure/clojure/blob/5ffe3833508495ca7c635d47ad7a1c8b820eab76/src/clj/clojure/core.clj#L2030*), though seems to be missing in ClojureScript. There is a mk-bound-fn added to Cljs (https://github.com/clojure/clojurescript/blob/e7cdc70d0371a26e07e394ea9cd72d5c43e5e363/src/main/cljs/cljs/core.cljs#L9742-L9746), which I guess is likely intended as a replacement. It takes 3 arguments though. I'm not sure how that translates. Honestly I have zero understanding why it is even needed. All I know is I can get a basic tap-to-print working in clj project by wrapping whatever print-fn in bound-fn*. If I don't, then it doesn't work there either. Rather than requesting to add more stuff to SCI, I think I was more hoping for guidance from an expert on how this should work without bound-fn*.

borkdude 2022-12-02T19:32:41.107799Z

I have no clue what you are even doing to be honest. Can you state a problem statement as if I'm a complete noob?

skylize 2022-12-02T19:41:40.201289Z

The primary goal at the moment is just to verify that tap> is working correctly in Joyride, so I can submit a PR for it. Because the easiest way to test this seemed to be printing a tap, that led to a separate goal, which is: I want to be able to use tap> in my code, and have that output to a built-in print function, such as prn, even if I am not running on JVM Clojure.

borkdude 2022-12-02T19:43:03.624449Z

The reference for joyride is CLJS, not the JVM. So if you have a working example in CLJS (or nbb), we can make that the same in joyride

borkdude 2022-12-02T19:44:03.208529Z

in nbb:

user=> (add-tap prn)
nil
user=> (tap> 1)
true
user=> 1

skylize 2022-12-02T19:50:43.631269Z

๐Ÿค” That is exactly what I have added to Joyride, but does not seem to work, neither in Joyride REPL ...

user=> (add-tap prn)
nil
user=> (tap> 1)
true
user=> 
... nor with Joyride: Run Clojure Code.
=> nil

=> true

borkdude 2022-12-02T19:52:17.011219Z

can you try a different tap function? it might be something weird with the print-fn

borkdude 2022-12-02T19:54:39.718089Z

e.g. the vscode information message thing

skylize 2022-12-02T20:02:22.350689Z

(v/window.showInformationMessage "foo")
;; foo

(add-tap v/window.showInformationMessage)
(tap> "foo")
;; nothing

(add-tap #(v/window.showInformationMessage %))
(tap> "foo")
;; nothing
Also tried with all print variants and js/console and wrapping in another function (defn prnt [x] (prn x)). ๐Ÿ˜ž

pez 2022-12-02T20:12:28.179299Z

I think it's best to file a PR, and get feedback on that one. Draft it, if you think that's appropriate.

skylize 2022-12-02T20:21:29.464369Z

The issue is not in the printing.

(def n (atom 0))
(add-tap #(swap! n + %))
(tap> 5)
@n       ; => 0

pez 2022-12-01T22:40:22.058149Z

(ns aoc2022-1
  (:require [promesa.core :as p]
            [util-aoc :as aoc]))

;; Unwrapping the a promise is a bit involved...
;; Read this as something like:
#_(await-def real-input (aoc/values+ 1))
(.then (aoc/values+ 1) #(def real-input %))

(def test-input [1000
                 2000
                 3000
                 nil
                 4000
                 nil
                 5000
                 6000
                 nil
                 7000
                 8000
                 9000
                 nil
                 10000])

(defn part-1 [input]
  (->> input
       (partition-by nil?)
       (map (partial apply +))
       (apply max)))


(comment
  (part-1 test-input)
  :rcf)

(part-1 real-input)

pez 2022-12-01T22:42:03.339369Z

That is, my solution code can be promise free.

borkdude 2022-12-01T22:42:35.004759Z

same over there

borkdude 2022-12-01T22:43:07.069609Z

but in node you can also use readFileSync

pez 2022-12-01T22:43:25.370879Z

Haha, so similar to mine!

(def fetch-input+
  (memoize fetch-input'+))

(defn values+ [day]
  (p/let [values (fetch-input+ day)]
    (->> values
         (string/split-lines)
         (map edn/read-string))))

pez 2022-12-01T22:44:42.407689Z

I should make my slurp take a Url too of course...

(defn slurp+ [ws-path]
  (->
   (p/let [ws-root (-> vscode/workspace.workspaceFolders first .-uri)
           uri (vscode/Uri.joinPath ws-root ws-path)
           data (vscode/workspace.fs.readFile uri)
           text (.decode (js/TextDecoder. "utf-8") data)]
     text)
   (p/catch (fn [e]
              (println "Ho, ho, ho! Path not found:" ws-path e)
              (throw (js/Error. e))))))

(defn- fetch-input'+ [day]
  (->
    (p/let [cookie (slurp+ ".aoc-session")
            response (.get requestify
                           (str "" day "/input")
                           #js {:cookies #js {:session cookie}})]
      (.getBody response))
    (p/catch (fn [e]
               (println "Ho, ho, ho! Did you forget to populate `.aoc-session` with your AOC session cookie?" e)
               (throw (js/Error. e))))))

(def fetch-input+
  (memoize fetch-input'+))

(defn values+ [day]
  (p/let [values (fetch-input+ day)]
    (->> values
         (string/split-lines)
         (map edn/read-string))))

borkdude 2022-12-01T22:45:48.841469Z

yeah, you can download those inputs separately from running your solution

borkdude 2022-12-01T22:45:55.261249Z

and then your solution becomes sync

pez 2022-12-01T22:46:54.715589Z

I sort of do it separately, with that memoize.

pez 2022-12-01T22:47:40.145169Z

And the await-def you handed me works just fine for making it sync in my solution code.

borkdude 2022-12-01T22:48:25.515609Z

it's not sync, the def is still being defined at a point in the future, but for REPL purposes it works

pez 2022-12-01T22:49:00.909769Z

It works when I run the script as well. But that's in the REPL too, right?

pez 2022-12-01T22:50:05.580679Z

What I mean is that my solution code will look exactly the same whether the input is defined now or at a point in the future.

borkdude 2022-12-01T22:50:21.185439Z

well, as long as the input is there before the puzzle runs...

borkdude 2022-12-01T22:50:30.258889Z

which might not be the case the first time

pez 2022-12-01T22:50:35.421339Z

I see....

pez 2022-12-01T22:51:11.788259Z

I'll solve that with some instructions.

pez 2022-12-01T22:52:17.260159Z

Or, maybe I should allow my solutions to be async instead... Hmmm...

pez 2022-12-01T22:53:56.212519Z

Then await-def is a bad name for this, right?

skylize 2022-12-01T22:55:15.116349Z

Regarding the original concern of how to "unwrap" a promise, I would favor simply peeking inside.

(defn tap [x] (tap> x) x)

(-> my-promise
  (p/then tap)
  (p/then whatever-i-was-doing-with-result))

๐Ÿ‘ 1
borkdude 2022-12-01T22:56:41.822019Z

yeah, I think that's better

pez 2022-12-01T23:05:46.197219Z

We don't have tap> in Joyride, have we?

skylize 2022-12-01T23:18:20.564019Z

hmm. That's unfortunate.

skylize 2022-12-01T23:18:58.151879Z

prn to the rescue, I guess...

borkdude 2022-12-01T23:19:15.546259Z

we can add tap> of course

borkdude 2022-12-01T23:20:00.416989Z

just add it to the config in joyride.sci

borkdude 2022-12-01T23:20:03.320339Z

๐Ÿ’ค

skylize 2022-12-01T23:20:31.386319Z

goodnight blob-wave

pez 2022-12-01T23:20:46.727719Z

Issue please, @eveningsky. And PR too, if you like. ๐Ÿ˜ƒ

skylize 2022-12-02T01:23:08.595229Z

https://github.com/BetterThanTomorrow/joyride/issues/112 My best interpretation of the SCI docs and existing code is that I should be able to add it to the map after`'clojure.core` at https://github.com/BetterThanTomorrow/joyride/blob/8de67c5eb6eec93767fcd9df05a710ce5d274da6/src/joyride/sci.cljs#L78.

; formatted to far-left for readability in Slack

:namespaces
{'clojure.core
 {'IFn (sci/copy-var IFn core-namespace)
  'tap> (sci/copy tap> core-namespace)
  'add-tap (sci/copy-var add-tap core-namespace)
  'remove-tap (sci/copy-var remove-tap core-namespace)}
But my trial shows symbol as still unresolvable. No other ideas seem obvious to me on how to patch this.

pez 2022-12-02T07:00:02.460539Z

This stuff is not hot-reloaded. Just saying that, I don't think you actually assumed it was, or that this is the issue.