This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-08-21
Channels
- # announcements (1)
- # beginners (30)
- # calva (3)
- # cider (23)
- # clerk (5)
- # clj-kondo (16)
- # clojure (39)
- # clojure-brasil (3)
- # clojure-europe (19)
- # clojure-nl (1)
- # clojure-norway (54)
- # clojure-seattle (1)
- # clojure-uk (2)
- # clojurescript (9)
- # cursive (3)
- # datahike (13)
- # datomic (4)
- # emacs (7)
- # events (1)
- # fulcro (32)
- # hyperfiddle (17)
- # jobs-discuss (3)
- # meander (5)
- # missionary (132)
- # music (2)
- # nyc (1)
- # off-topic (33)
- # polylith (22)
- # proletarian (3)
- # scittle (106)
- # shadow-cljs (23)
Since cloroutine (no channel for it and it's what missionary is built on, so :man-shrugging: ) makes it trivial to implement shift and reset, have you given some thought to implementing algebraic effects with it?
Speaking for myself not Leo, Geoffrey and I have considered algebraic effects several times for the use case of electric-UI but each time, so far, ended up finding a better way
Do you have a use case in mind?
Backend programming in particular. Lots of effects going around, mount is terrible, component puts people off, integrant is all over the place Maybe just not have any DI framework for writing code, just write code?
Have you considered the already existing correspondence between Electric and DI frameworks? I.e. both have a reactive component lifecycle
That might be more than I need. I wouldn't know exactly where to start with electric building an api server, but I would with algebraic effects
Comparison of Component and Electric for DI
Just a sketch I haven't thought very hard about this, the "real" DI systems surely have polish for that use case that we don't have
i.e. hot code reloading situations or something idk
I haven't really used them
Likely the system components (i.e. database) would be Electric bindings i.e. reactive dynamic scope
Having to pass the arguments explicitly sort if defies the point if the exercise Yeah, there's an option to make the components dynamic, but that also raises the question how that will differ from regular old clojure
With algebraic effects they'll still be dynamically bound, but you go to function handlers (transfer of control) instead of access stateful objects bound dynamically
Is it better to bind the handlers? Is there any value to using algebraic effects over just dynamically binding functions?
Electric dynamic scope differs from Clojure dynamic scope in that the bindings are stateful and have component lifecycle, i.e. Electric instances are objects. Clojure bindings are just values
yeah idk what the added value of algebraic effects is for this use case
here is the relevant tutorial about Electric lifecycle which is relevant to this discussion - not sure if you have seen it
the latest missionary changes (to m/signal) i think are related to this discussion too, you don't need Electric for this, i think Electric is just the syntax sugar that makes it palatable for this use case
Have, but haven't dug too deeply into it. I think I should try to sketch out example code and see how an implementation would look like
Sure, if you gave us a sketch and list of operational aspects/requirements to consider that would be interesting for us to think about and valuable
not sure I'm fully in sync w/ you but whatever
What in your opinion is the current best DI framework for Clojure?
lol, what's that
"in anger" ?
There are preservation laws in the universe. Momentum, angular momentum, energy, turns out that things sucking is another thing which is preserved. So if DI sucks, all DI frameworks will suck in the same amount, just maybe in a different way
So instead, I wrote a helper for Component to help it look slightly less annoying to me https://github.com/bsless/companion/blob/master/src/bsless/companion.clj
The suck was transferred to other developers who have to think about another layer of abstraction and in how it slightly restricts access patterns to components
our theory/worldview with Electric is that "having actual lambda" is the answer to that – if you are making something that is supposed to compose, you either have lambda or you don't, where "don't" is the root cause of "preservation of suck"
conjecture: all programming infrastructure will eventually take the form of programming language infrastructure
evidence: Temporal (durable execution), JVM garbage collection, React (reactive execution embedded in JS and the ways in which it sucks are the places where it fails to have fully reactive control flow), Electric network transparency, async/await, generators
In Erlang ALL infrastructure is programming language infrastructure, your code runs just as well on a cluster of 1000 nodes and on your local machine
functional composition model / functional concurrency primitives like missionary?
No, generic machines specified by callbacks for reacting for events and potentially advancing internal state
The idea is that any system can be built by assembling such machines, and that the set of behaviors you actually want for machines in a system is pretty limited
would be curious to know what you think about https://github.com/nivekuil/nexus, which is my (imo successful) attempt at replacing integrant with a generic logic engine. I think backward chaining inference is a better fit than (push?) FRP that electric implements, for DI. I'd also be curious to read a comparison between FRP and inference engines in general
my guess is they come from different schools of thought but there's significant practical overlap
electric is push/pull, i.e. "lazy sampling"
I have to say I personally like Nexus, although I consider it using a nuke to kill a mouse.
I wonder if algebraic effects (e.g. handlers) are neither push nor pull, they're just immanent
algebraic effects are orthogonal
main benefit is the ergonomics in use of the auto caching, which as Dustin mentioned is one of those specific touches orthogonal to the paradigm
> Components specify the data they need and are used like a function with their arguments automatically passed to them, Fulcro-style. this smells like the "almost a function but not quite" problem, I propose that all differences between this and actual functions are bugs
But I think it's a nuke worth adopting more broadly. I'm using a similar approach to do distributed configuration like https://web.archive.org/web/20030218034509/http://www.research.microsoft.com/research/dtg/davidhov/pap.htm
I'm increasingly coming to believe that configuration encompasses most of why computers suck
"DI as configuration" worldview excludes control flow, now your XML config language has <if> and <for> directives in it
Not sure what you mean by control flow, the point is to have the computer figure it out. Dustin have you seen this critique of pure function composition https://youtu.be/XONRaJJAhpA?t=15m27s
have not seen
he seems to be criticizing imperative programming (which Clojure is)
i'll accept (or conjecture) that "declarative programming" and "configuration" are the same thing
declarative programming means programming without control flow
that means you need to abstract over cardinality, for starters, because declarative programming has no userland loops
control flow = if, for, recur, lambda, throw/try/catch, etc
example: HTML does not have control flow
example: XML does not have control flow, until it does
example: GraphQL does not have control flow, until it does - see @if
Ironic coming from Facebook given the whole point of React is to embed view authoring DSL in javascript control flow, to stop writing {{if x: ...}}
in our "declarative" view templates
Configuration is the problem. I want X to work. To get X to work X needs to know Y. I don't want to think about Y, I only care about X, so I tell X everything I know and hope for the best. Declarative programming helps actualize that hope
hope is only to the extent that you don't violate Greenspun's 10th, which every system eventually does
hence Ben's principle of suck preservation
I don't reject your argument entirely; HFQL is declarative (though it is embedded in Electric's composition model, which is the critical point)
Electric itself is declarative with respect to network and declarative with respect to reactive evaluation, however this requires a nuanced worldview based on homoiconicity where the lisp program reifies all the control flow statements statically into the AST, which the Electric compiler then uses to "have the computer figure it out"
which is kinda the end of the configuration-driven worldview, Lisp is that, the program is data i.e. configured
hence the emergence of Greenspun's 10th Rule – applied here, it means that any configuration language will eventually need control flow, which means your configuration language must be exactly Lisp
It has to be imperative at some level but turing incompleteness is the goal. I want to be just able to give the CenturyLink guy my account number and have my internet fixed so I can stop typing on my phone. But of course someone has to know how to fix it
And of course the more proximal the condition is to the problem the more you can charge for that condition as a solution. Framers get paid a lot worse than finish carpenters
"imperative" I didn't say that
Configuration is just a private case of dependency, which is a private case of ordering, which is why logic programming and rule engines are fun
functional programming gives you declarative wrt evaluation order
basically DAGs
let x = y in body is not declarative, you have to have x bound before you can use it in body
not so in Haskell
recursive let
https://en.wikibooks.org/wiki/Haskell/Fix_and_recursion - the let binding is recursive
here's Clojure psuedocode
(defn Fix1 [f]
(let [x (f x)]
x))
also see https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/recursive_do.html
The need for recursive let comes up a lot in UI programming
This doesn't work yet in Electric but we'd like it to weight depends on bmi, bmi depends on weight
(e/defn bmi-component []
(let [height (Slider. 170 100 220)]
; Looped interdependent bindings
(let [weight (Slider. (* (or bmi 50) height height) 30 150)
bmi (Slider. (/ (or weight 70) (* height height)) 10 50)]
(dom/p (dom/text "BMI: ")
(dom/text bmi)))))
The workaround (that Reagent tutorial uses) is to introduce an atom to "tie the knot"
ok, i guess i dont understand your objection to "FP gives declarative wrt evaluation order"
oh you're saying datalog lets you compile declarative query into a query plan, user did not specify the plan
Exactly. Same for prolog and generally logic languages. Imagine execution plan in general being incidental
I accept that, with the caveat that we have application glue code (control flow) between our queries
better to have a trap door down to control flow layer from inside the queries without leaving the DSL's composition model entirely
so you end up with a Rama layer
conjecture: programming languages and databases are the same thing
pretty sure you accept that
right
so we're right back to Greenspun's 10th and Lisp
because applications are queries; i.e. application views are projections over the event log
the control flow in a user interface is part of the query
classical SQL/datalog query langs are too declarative and its the source of a massive seam through the entire application
Maybe they're too declarative because we're still missing something. Until then we need escape hatches
i mean we know what they're msising, analyse it from the perspective of composition as it exists in lisp
we want to weave in lambdas (with control flow) at arbitrary points in the query, e.g. a stored procedure on the inside
we want the query when it finishes to return control to lisp, i.e. a stored procedure on the outside
we want to factor apart our queries into reusable units and compose them together, including higher order combinators – that's lambda
we want the end-to-end composition model to be seamless and have the same semantics everywhere
(reactive, network-transparent, durable, etc)
in the middle of a query, we want to call out to another system, and then return control back to the query, and we want uniform semantics all the way through this
I don't understand how something can be too declarative. If your app isn't just a view then it's not sufficient, but to the extent that you need a view the fully declarative layer, read-optimized layer is really nice to have.
an example is React is the wrong amount declarative (it does not understand your js control flow) which causes the evaluation model to suffer performance problems, over-render etc
the theory was that views should be declarative and don't need control flow, react has conclusively demonstrated that to be wrong
the strongest statement that can be made is that you can interweave declarative abstractions into your app at various points, at the tradeoff of expressive power
isn't the escape valve for that react behavior like zustand, re-frame subscriptions etc.
I understand that the problem with fine grained reactivity everywhere is it sucks to debug, so fulcro puts everything into a big map (and then just renders from root, leading to the performance issues you describe.) Fulcro also tried keeping runtime indexes for more fine grained rendering but that turned out to be even slower. So I wonder how hard it would be to get electric's compile-time dependency tracking as a hook
> I think I should try to sketch out example code and see how an implementation would look like @UK0810AQ2 have you explored this more since? I am also curious on how missionary could help make a better solution mount/component