Fork me on GitHub
#clojurescript
<
2019-06-09
>
john16:06:19

Is it possible to extend a whole type to a new IPrintWriter, but only in the scope of particular print action?

john16:06:31

Like, say I want to print a whole collection, but I want to do something funny with the strings or functions or objects, but only during this printing, not globally at all times.

john16:06:27

I think I can do scoped tagged readers... looking for the other direction

john16:06:16

I want to tell the writer, "for each node in this collection, if you get to this type, write it this way instead"

john16:06:16

Without having to pre-walk the collection and tag things

john16:06:53

And if I had to walk it, is it even possible to get js type info during a macro time walk? Does the analyzer have that info?

john16:06:21

I could walk it at runtime, it just seems excessive

lilactown17:06:09

I think you want to extend a particular JS object to IPrintWriter?

lilactown17:06:38

have you looked at specify! @john?

john17:06:03

Yeah, I'd just have to walk the whole thing before writing it

john17:06:34

It would be more efficient to instrument the write's walk

lilactown17:06:52

so basically ou want to extend-type, print it, and then un-extend-type 😛

john17:06:18

right 🙂 scoped-extend-type. or with-extensions or something

john17:06:06

Could be done with the generic isa? hierarchies

john17:06:25

Doubt it with the protocols

john17:06:15

hmmm. Can you use isa? hierarchies to override type dispatches?

john17:06:47

Probably not for a writer...

lilactown17:06:03

I don’t think this is a use case anticipated / supported by Clojure, typically. I guess it’s all about how much we can get at the protocol impl

lilactown17:06:40

I can think of a pretty bad way to do this

john17:06:20

Well I'm doing some real bad things 😉

lilactown17:06:11

in CLJS we happen to know that protocol implementations end up like

Bar.prototype.cljs$core$IPrintWithWriter$_pr_writer$arity$3 = function (this$ writer$ opts$) { ... }

lilactown17:06:50

(i wrote that from memory so might be wrong)

lilactown17:06:48

so you could save the current value of the -pr-writer impl, extend the type, then when you’re done swap it back 😛

lilactown17:06:13

I’m expecting dnolen or mfikes to come in at any second to swat my hand away

4
lilactown17:06:34

(or to tell us it’s trivially easy using something I don’t know)

lilactown17:06:37

I’ve been circling around a similar problem with printing hiccup. I’d like to take a React element and print it to something like #hiccup/react [:div "foo"], but React elements are just bare objects 😕 hard to handle

lilactown17:06:44

ironically the use of plain data in JS bites us when it’s in fact special

john17:06:55

I'm essentially trying to experiment with strategies to pickle certain kind of objects and functions over the .postMessage wire in a webworker environment

lilactown17:06:55

it’s been awhile since I used IPrintWithWriter. doesn’t it require you to call it recursively?

lilactown17:06:52

so couldn’t you make that a recursive call to your own printer that handles these objects specially?

john17:06:33

So, for any given cljs collection, printing it will pickle certain types into a tag with an id, add the object as another parameter to the message, send the message, read-string the edn, then the tag-reader will grab the object for that id and the collection is restored on the other side.

john17:06:52

I don't think so

john17:06:32

I'm not sure

john17:06:56

Well, it may be recursive for some types

john17:06:01

My other option, instead of printing collections to edn, would be to clj->js the coll and extend IEncodeJS for the types I care about

john17:06:49

But the edn path already has more fidelity for clojure datastructures... I'd have to add a lot of IEncodeJS writers and readers

lilactown17:06:53

do you even need to use IPrintWithWriter?

john17:06:03

Maybe not?

lilactown17:06:39

maybe it’s better to implement your own protocol, IPickle, and create your own printing function

lilactown17:06:57

you could fall back on IPrintWithWriter if the object doesn’t implement IPickle

john17:06:11

At the moment, I'm experimenting with pickling anything that answers to fn?

lilactown17:06:36

I think the only thing that answers to fn? is function

john17:06:48

So, extend-type js/Function is pretty aggressive

john17:06:38

Wouldn't IPickle have to implement IPrintWithWriter?

didibus17:06:01

Does polymorphism in JS even have any performance benefits?

lilactown17:06:57

well, it has performance implications. sometimes it’s faster

john17:06:09

I think it can be done by walking the whole collection and specify!ing a special printer that hands off the object and drops in a tagged id for fn? args

john17:06:17

What's interesting is that certain functions print out in a self contained way, such that if all of your workers (or js environments) have the same compiled artifacts, the strings act as byte code that can be shuffled between them.

lilactown17:06:22

@yogthos presented a video where they’re serializing functions to a DB and then reading them into a CLJS app

yogthos18:06:55

Actually, the functions get read in clj in my case because all the business logic runs server-side to ensure updates being transactional across clients.

lilactown18:06:19

ah! my mistake

yogthos19:06:11

I had a chance to chat with Mike Fikes, and he suggested that one option would be to add a namespace with exports for all the core functions you're using. Then you'd be able to call those if you compile cljs to js on the fly and hot load it on the client.

👍 4
john17:06:18

ah, yes, there are some libs that serialize to local-storage

lilactown17:06:22

the whole function impl was in the serialization though, so it sounds like you’re getting like a reference to a function and then passing that over the wire?

john17:06:50

I don't think they're storing compiled snippets of cljs as bytecode though

lilactown17:06:21

not really sure what you mean “as bytecode”. I think they were just passing EDN back and forth from the DB to the app

lilactown17:06:34

and evaling it

john17:06:03

I'm doing this (((read-string (pr-str (fn->special-fn (fn [] inc))))) 1) where special fn reifies a special print writer and a tagged reader calls js/eval on it.

john17:06:26

where the output is 2

john17:06:57

Or (((read-string (pr-str (fn->ray-fn inc)))) 1), rather

lilactown17:06:30

wild 🙂 all to avoid including self-hosting?

john17:06:56

But (fn [] inc) evals to:

#object[ret__6694__auto__ "function (){
return cljs.core.inc;
}"]

john17:06:09

Yup 🙂 Works in advanced mode too 🙂

john17:06:50

And if you ((js/eval *1)) you'll get back cljs.core.inc, the function

john17:06:08

It's definitely a little wild. Not a supported use case... But the utility of it is pretty surprising. Especially when you use it mostly as an rpc mechanism for already statically compiled code, even though it feels kinda like your talking between threads like back in clojure-land

lilactown17:06:24

yeah. starts to tickle at some things I’ve run into while developing a REBL clone for CLJS

john17:06:56

Yeah, you're not getting the same object on both sides of the wire here, in an equality sense, necessarily

john17:06:18

But you can get blocking back if you are willing to keep it inside a worker

john17:06:11

I do have a cool SAB backed atom thing though that can be shared between workers, and that is the same object on both sides of the wire. And a cool on macro that knows how to transfer them, so you can do like (let [sa (shared-atom 1)] (-> wkr1 (on #(swap! %1 + %2) sa 1)) (println (wait sa))) or just ... (-> wkr1 (on swap! sa + 1)) ... as all arguments to the right of the function passed to on will be applied to it.

john17:06:16

Still thinking through the api

john18:06:41

on also sends a sab to the worker for the return value of the function you pass, so that you can dereference the return value of the on to get the result, or wait on the result, like (-> wkr1 (on swap! sa + 1) wait (->> (println :result)))

john18:06:46

or (-> wkr1 (on fib large-number) wait (->> (println :result))) would probably be more likely, if you're just using the return value sab to get the result.

john18:06:18

I've already got the on macro doing the function wrapping jazz and it seems to work for common functions you might pass to a swap. Just building out the ability to transfer clojure collections now, but not sure whether to go through the trouble of trying to build out all this machinery to convert all functions in an arbitrary clojure datastructure

lilactown18:06:26

if anyone’s interested, I whipped up an example of using reader tags for hiccup parsing: https://github.com/Lokeh/hiccup-tag/blob/master/src/hiccup_tag/examples.cljs#L17

john18:06:31

That's pretty awesome

john18:06:22

It'd be interesting to make a static site/blog edn format where all behaviors are in an approved set of tagged readers, where all the logic is guaranteed to run deterministically and terminate.

john18:06:58

You could use some subset of react for building out the view. Some bounded for-looping.

john18:06:05

And then possibly page snippets could be shared between untrusted users. Essentially, sandbox the logic but allow for new complex components that are built in clojure but are guaranteed to be safe at runtime

Mno18:06:45

meanwhile I’m happy if my reagent counter works when I click the button. kappa

Mno18:06:12

joke aside, cool idea.

lilactown19:06:14

I’m thinking of ripping out the hiccup interpreter in hx and using tagged literals instead

lilactown19:06:05

I haven’t really run into a use-case yet where I wanted the dynamicity of runtime interpretation. it’s been more a concession because the ergonomics of macros are bad

lilactown19:06:33

I need to assess if the ergonomics of tagged literals are also bad 😛

john19:06:39

Yeah, what's the tradeoff?

lilactown19:06:24

tagged literals are actually even more static

lilactown19:06:38

but IMO they’re easier to write ad-hoc

lilactown19:06:58

#h/r [:div (for [n [1 2 3]] 
             #h/r [:div {:key n} n])]

;; vs

(h/r [:div (for [n [1 2 3]]
             (h/r [:div {:key n} n])])

john19:06:57

Yeah, that's a happy-to-glad for me

john19:06:42

One interesting thing about tagged literals - because they run before macro expansion, you can write things in macros that transform prior to macro expansion

lilactown19:06:08

an upside to the reader literal is being able to use it with cljs.reader to receive stuff over the wire, though that’s not something I’m actively using it for

john19:06:17

Not sure where I'd use that yet, outside of the ns macro

john19:06:27

Though you can dip into that using register-tag-reader! without having to have a reader file, if you're just converting stuff from io

lilactown19:06:22

actually the change that I think makes either macros or reader tags tenable is the thing I’m doing with props

lilactown19:06:06

I haven’t implemented it completely yet, but I’m testing a new syntax for dynamic props:

(let [stuff {:style {:color "green"}}]
  #h/r [:div {& stuff} "foo"])

mhuebert18:06:49

@lilactown if the intent is to know that stuff is a map, another possibility would be to tag it directly, eg [:div #hx/map stuff "foo"]

mhuebert18:06:36

or #hx/props

mhuebert18:06:44

i personally find writing lots of nested tagged literals not-so-pleasant. even the simple case of #js gets tedious with nested structures

lilactown18:06:48

The reader tag for hiccup is only loosely related to the props disambiguation

lilactown18:06:19

IMO the props disambiguation makes doing the parsing at read time or macro time much more manageable

lilactown18:06:16

Because you don't have to fall back to runtime checks for whether it's props or not. And you don't have to pay the cost of allocating a map and then turning it into a js object in many cases

lilactown18:06:09

I've though about metadata for disambiguating the props as well, but feels mor verbose than I like

lilactown18:06:28

just to be clear, the syntax for dynamic props is the {& stuff}

john19:06:58

what happens to stuff?

lilactown19:06:04

which will merge the stuff map onto the actual props object given to React

lilactown19:06:36

it’s the equivalent of the JSX:

<div {...stuff}>foo</div>

john19:06:51

mmm, can you get access to cljs locals at tag expansion time?

john19:06:25

I think the analyzer might be different at tag reader vs macro compile time

lilactown19:06:51

atm I just expand it to:

(merge-obj+map (js-obj ...) stuff)

lilactown19:06:12

merge-obj+map awaits implementation 😛

john19:06:30

oh gotcha

lilactown19:06:32

but it solves the ambiguity of parsing a form like this:

[:div stuff "foo"]

john19:06:35

Yeah writing tagged readers can be a little mind-bending too though. If you pull it off, I'm sure it'll be very interesting.

lilactown19:06:21

yeah 😅 I’m still not sure I did it right, but it seems to be working in my example

JaimeV22:06:44

Hello, is there a way to require clojure libaries into clojurescript? I am getting this error:

Unexpected error (ExceptionInfo) compiling at (<cljs repl>:1:1).
No such namespace: clojure-csv.core, could not locate clojure_csv/core.cljs, clojure_csv/core.cljc, or JavaScript source providing "clojure-csv.core" (Please check that namespaces with dashes use underscores in the ClojureScript file name) at line 1 <cljs repl>

lilactown22:06:56

no, you cannot use clojure libraries in clojurescript

lilactown22:06:03

some libraries provide code for both

JaimeV22:06:35

@lilactown Understood. Thanks!