Fork me on GitHub
#clojurescript
<
2019-06-22
>
misha09:06:50

does anyone know how is https://github.com/CoNarrative/precept going? cc @alex-dixon (I hope you are the right Alex Dixon)

Ahmed Hassan12:06:23

Are you using precept?

misha12:06:34

No, but was wondering where it is at now: great success, dead end, so-so, etc

4
alex-dixon14:06:40

Great success I hope 😊

misha14:06:32

why no commits for 6 months? is it done? kappa

misha15:06:03

I reached sort of local dead end with state machines (state charts), which leads me towards datalog/rule-based-systems (clara?), which is kinda what precept is, so I am wondering how are things there :)

alex-dixon15:06:51

Pretty good I think but I’m just me. 🙂 There’s a couple of examples that should be fairly quick and painless to run. I’d recommend starting there and see what you think

Ahmed Hassan19:06:04

@U051HUZLD What are examples of state machines in Clojure(Script)? And what is your experience with them?

misha20:06:10

I wrote my own, which can handle widely branched and deeply nested state trees. but state trees turned out to be inherently unusable as a thing to build and manage as a user.

misha20:06:32

A bit simpler lib is https://github.com/samroberton/bureaucracy it supports machines nesting and composition, but has a fundamental "UX" limitation – you cannot go from state of one machine to state in another in a single go. E.g. on submit button click show another page. To represent this – you have to write an unobvious chain of state events and hops, which defeats the whole purpose, for me.

misha20:06:34

anything else – is either hello-world grade libs, which are 1 trivial function and not useful at all, or for very low level machines, like https://github.com/ztellman/automat, and require extra (mental) level of wiring to UI code. (but there is a non zero chance I just did not get it)

misha20:06:25

there is also https://github.com/candera/causatum but it is for a bit different purposes, which, again, does not match 1-to-1 to UI development (my main goal with state machines)

lilactown20:06:54

I’m curious if xstate solves any of these UX problems: https://github.com/davidkpiano/xstate

lilactown20:06:46

not sure how to judge that since I haven’t tried out state machines at all yet, but am v curious since some people seem to yell from the rooftops about them

misha21:06:59

I don't remember the details, but it does not kappa

misha21:06:45

the thing with state machines/charts for web dev/UI – you are up against the most convenient and inline way to do things. And comparing to just "hacking it" – benefits are so far away, it is not compelling at all, even if you are the one yelling from the rooftop. However, I believe it is a UX issue and lib API design, not a state machine approach issue.

lilactown21:06:25

yeah when I have watched people program directly with state machine APIs / data representations, it looked super verbose and indirect

misha21:06:06

yeah, it sort of does some times

lilactown21:06:22

seems like it would be better as a target for either compilation (DSL) or the domain model of some UI

lilactown21:06:48

since AFAICT the best parts of state machines / charts are visualization and consistency

misha21:06:10

the ultimate underlying issue is, I think: if you are trying to put graph in a tree – you are gonna have a bad time

lilactown21:06:02

and since we write code as a tree… the issue comes to bear quickly

misha21:06:51

and any UI larger than 1 input field, with fair UX – is a graph

misha21:06:34

we don't, actually, write it as a tree. and this is what you are up against as a FSM lib designer/user:

misha21:06:03

you define react component (or any function), and then use it in 5 places. this is graph, and it is essentially what you do, without any need to re-adapt to any new "style/approach/paradigm"

misha21:06:26

but since it is so close to everything else – state gets spread all over the place. because it is convenient to write it this way. you don't need to think to do that. and you have to think to not do it this way

misha21:06:40

inline is like "cheap fast food on your daily rout" vs. "expensive farmers market 50 miles away twice per month on tuesdays", convenience and willpower-cost wise

lilactown21:06:52

yeah, I think my (half-baked, inuition-based) thought matches what you’re saying. my waxing take on a Saturday afternoon is: when we write code, we write it as a tree. when we need to do graph-like things (like use a thing we created elsewhere, 5 times) we place references in the code, because code is represented as 2D text that can only get wider or deeper, not loop around or traverse to some other location directly. React elevates these references to first-class constructs (functions) and covers more than just the DOM but also state, effects, etc. to allow us to map this graph to our tree-like code like we’ve been taught. Hooks also follows this same strategy. Current state charts has to be embedded within the React graph and cannot be composed like the rest of the graph (it’s not first class), so you end up with a graph-within-a-graph, which it sounds like to tiny human brains, just isn’t worth the complexity cost of that.

misha21:06:48

I'll give you an example of a problem, @U4YGF4NGM https://xstate.js.org/docs/#parallel-state-machines here there is a description of bold/italic/list machine. never mind italic/bold are more verbose than just booleans in the map. but imagine, if you want to keep all the state in the same place (uber-state-machine), how will you attach example machine to the actual text? is it a copy of a machine per character (almost what draft.js does) or word, or is it something more complicated and way harder to reason about?

lilactown21:06:17

yep, makes sense to me I think 😄

misha21:06:11

yeah, I thought about "extracting" state graph from, say, react, or just any clojure code. and this might be the way to go, actually. you write your daily code, but mark state bits with something (e.g. reader tags). and there is just a function, which renders you the mess you wrote in a repl

lilactown21:06:27

yep, I think the key is to follow the example React has set: make things locally reasonable / writeable but first-class and composeable

lilactown21:06:53

it’s not clear to me what the primary method of composition would be for a state chart

misha21:06:05

but, I think, easier approach would be rule engines + datalog. it is a shame clara has such shitty dsl out the box

misha21:06:06

well, state charts (and uml for that matter) – is just a visual vocabulary to representing things. it does not impose any rules of execution at all.

misha21:06:29

so the easiest (for dev) approach is to "add facts/constraints as you discover them, and let engine figure out output", like datomic queries

misha21:06:55

it is just datomic/datascript are too databasy, and need some appealing packaging for UI development: few shortcuts and convenient oneliners to get adoption going.

misha21:06:36

on the other hand, if you try to build and balance state graph/tree by hand: each new fact/state/transition will make you rebalance it by hand again. to know how that feels - just try to use any diagraming tool iteratively d

lilactown21:06:37

Yeah I've been playing with some Hooks for Datascript. Been thinking about ways to declare schema, pull data into local state then transact

lilactown22:06:04

Unfortunately Datascript is pretty light on constraints but maybe that's a good thing

Ahmed Hassan05:06:42

Where do FRP systems like Javelin/Hoplon stand in all this?

misha10:06:17

did not think about it. may be as a subscriptions management tool.

misha10:06:47

on the surface, 1000 atoms is a bad idea (to me)

Ahmed Hassan11:06:38

You mean 1000 javelin cells?

Ahmed Hassan11:06:51

Subscriptions can also be managed by other tools like Keechma or re-frame.

misha11:06:11

well, in javelin each cell is and atom, so ¯\(ツ)

misha11:06:18

+ it basically prevents you to be able to get entire state in a single "place", e.g. to persist. also, if the only api you get is full of macros - it better be worth it.

misha11:06:24

it seems to me, rum solves this in a way leaner way. I'm not even mentioning "raw" add-watch (partly because javelin might actually offer something useful, and I just don't have any experience with it)

misha11:06:57

well, hoplon has this on its home page:

(button :click #(dosync
                       (swap! todo-items conj @new-item)
                       (reset! new-item ""))
kappa

misha11:06:24

so yeah, thanks, no thanks

Ahmed Hassan11:06:12

So, basically not having state in single place increases complexity. Right?

Ahmed Hassan11:06:58

I like Rum's idea of mixins.

misha13:06:17

increased amount of state increases complexity. having it in different places limit your option and make it harder to deal with/reason about

Ahmed Hassan13:06:06

Fulcro solves the issue of state and data fetching(client/server communications) in elegant way.

lilactown14:06:13

I think that having state all in one place isn’t without it’s tradeoffs, tradeoffs which we’re not very good at measuring yet

lilactown14:06:25

it can literally increase complexity (in the Hickeyian way) by increasing the number of components that can subscribe ad-hoc to any value, making it difficult to change without additional machinery

lilactown15:06:11

there are also considerations like performance that end up increasing the total complexity of the system

misha15:06:27

yeah, but even then you are looking at <10 state places vs. 100s

lilactown15:06:42

my rule of thumb in a Concurrent Mode world is: keep state as local as possible

alex-dixon15:06:36

@U051HUZLD Any thoughts on Precept or haven’t had time to try yet?

misha20:06:09

@alex-dixon did not run it yet, no. I am interested in idea though (an API), and, frankly, amount of clojure.core/last and butlast in source worries me kappa

alex-dixon13:06:22

@U051HUZLD PRs accepted 🙂 Worried about correctness or perf or…?

misha09:06:59

@alex-dixon perf (a bunch of functions I saw had several lazy seqs instead of single desctructuring, where it is obvious, that input is short), but it is waaay to early for me to either complain, claim anything, or send PRs, so disregard opieop

misha09:06:59

e.g. here https://github.com/CoNarrative/precept/blob/1b2b23951e66817ccf24ca7711db938256781cc2/src/cljc/precept/serialize/facts.cljc#L8-L10 you scan all xs to bind last one, and then scan all of them again to drop it. Sure, it is namespace string, which is like ~1-7 elements long, but makes me want to read the rest of the codebase, just in case. alternatively, since clojure.string/split returns persistent vector, you could just pop and peek for O(1). however, sending pop/peek PR would be somewhat useless, since this function seems to try to achieve something very similar to cljs.core/demunge:

cljs.user=> (cljs.core/demunge "foo.bar$baz")
"foo.bar/baz"
but, since I am not sure w/o inspecting all the call sites – I would not send demunge PR either kappa

👍 4
alex-dixon13:06:31

@U051HUZLD Thanks for taking the time…that’s really helpful. I think fn->map is only used for the dev tools. Recently I’ve been spending way too much time working on EDN serialization/deserialization for Rust so I should apply some of what I’ve learned from that here

alex-dixon13:06:02

I’m not disregarding. You’re right 🙂

misha14:06:28

yeah, that's what I'm saying: without much project-context loaded – any such PR is likely to be just nitpicking and waste of both ours time opieop

chris_12:06:55

I’m hosting a Clojure-backend, Clojurescript-frontend app on Heroku and everytime I refresh the page, the Clojurescript code is recompiling 😮

chris_12:06:35

The heroku deploy appears to compile the cljs:

remote:        Use clj -h for help.
remote: -----> Using cached Leiningen 2.8.3
remote:        Writing: lein script
remote: -----> Building with Leiningen
remote:        Running: lein uberjar
remote:        Downloading Leiningen to /app/.lein/self-installs/leiningen-2.8.3-standalone.jar now...
...
...
remote:        Compiling ClojureScript...
remote:        Compiling ["target/cljsbuild/public/js/app.js"] from ["src/cljc" "src/cljs" "env/prod/cljs"]...
	remote:        Successfully compiled ["target/cljsbuild/public/js/app.js"] in 120.311 seconds.
remote:        Created /tmp/build_a95d6139840adffffa725a79128a47dd/target/uberjar/landschaften-0.1.0-SNAPSHOT.jar
remote:        Created /tmp/build_a95d6139840adffffa725a79128a47dd/target/uberjar/landschaften.jar
remote: -----> Discovering process types
remote:        Procfile declares types -> web
remote:
remote: -----> Compressing...
remote:        Done: 142.1M
remote: -----> Launching...
remote:        Released v12
remote:         deployed to Heroku
remote:
remote: Verifying deploy... done.

lilactown20:06:26

are you starting your application?

lilactown20:06:24

I see that you have an init! defn in your core.cljs, but it’s not exported and not called in your home.html (which I assume is rendering the page)

lilactown20:06:05

I think the fix is to: 1. add :export metadata to your init! defn so that the name doesn’t get munged when advanced optimizations occurs:

(defn ^:export init! ...)
2. In your home.html (and anywhere else where, after a full page load, you want to start your app), add a call after loading the app.js file:
<script>
landschaften.core.init_BANG_();
</script>
which will start your app on page load after loading the JS file

chris_21:06:24

ah cool — I will try that — thank you so much!

lilactown21:06:30

np. assuming you’re in germany - have a good night!

chris_21:06:40

And redeployed to Heroku.

chris_21:06:09

Unfortunately the ‘recompile on refresh’ behavior persists, visible here: https://landschaften.herokuapp.com/

lilactown21:06:50

it works for me after a few seconds

lilactown21:06:06

you might consider changing the page in home.html to display something other than that text. It’s display whatever is in home.html until the app.js file is done loading, then executes the init! function

lilactown21:06:33

In this case, the app.js is a bit hefty (2.3mb) so it takes a few seconds to load and start up

chris_21:06:16

ah, okay; I was hoping to eliminate that text itself; I had assumed it was an issue with the js not yet being compiled hehe XD

Schmoho15:06:53

Hi everyone! I'm having a newby problem, hope someone has mercy. I'm running figwheel through CIDER and also a separate ring-server. I'm trying to send an API-request to my ring but firefox informs me this is not in compliance with CORS-policy, which as far as I understand is because figwheel uses its own ring to serve up the page and connect to it. Now I use ring-cors to set :access-control-allow-origins to #".*" and :access-control-allow-methods to :get, but this does nothing so far. Also I tried disabling strict_origin_policy in firefox which felt quite wrong but also did nothing to help. So I must be misunderstanding something rather basic here.

Schmoho16:06:18

nevermind, it was just two little typos