This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-12-06
Channels
- # adventofcode (71)
- # aleph (1)
- # announcements (6)
- # aws (1)
- # babashka (27)
- # beginners (60)
- # biff (7)
- # calva (3)
- # clj-kondo (3)
- # clj-yaml (1)
- # clojure (60)
- # clojure-europe (43)
- # clojure-nl (3)
- # clojure-norway (75)
- # clojurescript (16)
- # code-reviews (7)
- # css (4)
- # cursive (47)
- # datascript (4)
- # events (5)
- # fulcro (37)
- # gratitude (5)
- # hyperfiddle (4)
- # introduce-yourself (4)
- # joyride (23)
- # juxt (4)
- # malli (4)
- # membrane (64)
- # nbb (8)
- # off-topic (12)
- # other-languages (6)
- # pathom (6)
- # polylith (9)
- # random (3)
- # rdf (66)
- # reitit (3)
- # releases (2)
- # shadow-cljs (18)
- # tree-sitter (10)
Can I get some feedback on an idea i’ve been trying to flesh out about a more purely data-driven style? Maybe you can help me clarify them. The problem is: how do you keep something purely functional, and defer execution when you need to change behavior based on the outcome of an intermediate impure and stateful function? Say we want to pass around a pure function or a description of a behavior that defers the get-user call until we execute it:
(let [user (db/get-user user-id)]
(if user
(str user " is authorized")
"unauthorized"))
How do you handle and manipulate that snippet as a value? I guess you can pass around a quoted list? It’s just not ergonomic to attach another step onto. Have people looked into this? Is there a library that does this?
I was thinking of a crude approach, but which is pure and data driven:
(tests
(run-opts! {:inc inc} [[:inc] [:inc]] 1)
:= 3
(run-opts! {:inc inc :+ +} [[:inc] [:inc] [:+ 2]] 1)
:= 5
(run-opts! {:if (fn [a b c] (if a b c))
:== (fn [a b] (= a b))}
[[:== 1] [:if 2 3]]
1)
:= 2)
I'm not sure I totally understand the problem statement.
> Say we want to pass around a pure function or a description of a behavior that defers the get-user call until we execute it:
The short answer is a delay
, but I don't think that's what you're getting at. It's ok for a program to have side-effects in it (or else, what's the point?). The generic answer is that for the side-effecting part of your program, you want to have some sort of flow.
It's hard to give more specific advice since I'm not totally sure what the question is, but here are some references that might help:
• https://www.youtube.com/watch?v=ROor6_NGIWU, talks about systems in the large, but also has lots of good ideas for in the small
• https://www.infoq.com/presentations/clojure-core-async/
For example libraries:
• check out the middleware from ring which is a pattern for composing a side-effecting pipeline.
• also checkout the lifecycle libs like https://github.com/stuartsierra/component, https://github.com/tolitius/mount, and https://github.com/donut-party/system
Yeah, the problem statement itself is a little hazy. I want to generate full descriptions of what e.g. a complicated function is going to do as data. So I know “code is data” but I am thinking of the difference between a react state tree (functions all the way down right?), and a literal hiccup html page. Middleware from ring is another function based example, I want to move these sorts of “behavioral descriptions” into data.
In my toy example this is the program:
[[:== 1] [:if 2 3]]
and this handles the defered execution:
#(let [env {:if (fn [a b c] (if a b c))
:== (fn [a b] (= a b))}]
(run-opts! env % 1)) ;; edit added missing 1
I don’t deny it’s pretty close to just using vanilla clojure s-expressions… but there is a difference
> I want to generate full descriptions of what e.g. a complicated function is going to do as data. To do this in a general way might require solving the halting problem. I think it's possible and useful to do it in a less general way. My opinion is that you want something that ends up looking like a workflow diagram (ie. flow). I've used programs that end up with something like what you describe and I usually don't end up enjoying it very much. The end result is that you usually end up with a quirky programming language that is harder to work with and doesn't have the tools/support that a normal programming language has. That's more or less where I feel http://pedestal.io/reference/interceptors and https://day8.github.io/re-frame/Interceptors/ interceptors ended up.
For your get-user example, you can pass in the effectful function as a parameter to make it pure:
(fn [get-user]
(let [user (get-user user-id)]
(if user
(str user " is authorized")
"unauthorized"))
Now you can pass db/get-user
to it later to actually run the function. Or if you really want the lookup to be implicit, maybe you can use something like with-redefs
.but, whatever function that gets called won’t be pretty printable. these things don’t compose like data. That’s where I am going with this
But you can’t have a view of what happens, without abstraction. you are building a behavior not a data structure that represents a behavior. that’s what I am thinking about
You can't represent general functions as data unless you're prepared to eval
everything or otherwise write your own "Clojure interpreter" on top of everything...
re-frame isn’t really a “Clojure interpeter” but it has some similar characteristics
You can use re-frame on the server 🙂
(I tried it... it works but it's kind of painful)
I was in the room when we were making that happen, fwiw. but let me try another example
compojure, we use macros and functions to define routes. but reitit is a good example of moving the logic of a system into a configuration
You still have to map that data to functions somehow -- which is an interpreter (since even if you have a 1:1 mapping from some subset of data to functions, you need "interpretation" to know how to do the mapping and make the calls).
reitit defines more of a data-backed DSL right? but route handlers are still regular functions. and the DSL part isn't general purpose afaik
Sure, but reitit explicitly has an interpreter -- or you could argue a "compiler".
yeah, I’m not sure if you can get (f request) -> [:such-and-such-handler {:user user}]
in reitit. But I am arguing that there are benefits to that. And that I’d like to explore a system like that
agreed, i just mean they aren't trying to interpret regular clojure functions, they are interpreting a routing-specific DSL with a limited number of tokens/structure
(not trying to shoot down your idea btw, mostly trying to understand)
Thanks for saying that, that’s how I’m taking it. just trying to clarify for myself too
I've had a couple of goes at producing a data representation of changes that need to be made to a system... They've all worked but I don't think it makes for a readable system and I don't think the benefits of "datafication" outweigh the complexity of dealing with causality and sequencing of side-effects 😞
(defn login-flow []
[:if ::authorized
[:if [:admin [:get! :user]]
[:response/_200 "hello admin"]
[:response/_200 "hello person"]]
[:response/_403]])
(defn logged-in? (fn [req] (rand-nth [false true])))
(f {:logged-in? logged-in}
[[:user-logged-in?]
(login-flow)])
(It also tended to produce monadic code ... which I never really liked)
This was one attempt https://github.com/seancorfield/engine
(see my comment about monadic code above 🙂 )
My main suggestion is to avoid "accidentally" creating your own virtual machine. If that's what you want to do, then check out papers and virtual machines that are intended for formal verification. It's easy to slowly add branching, loops, continuations, bindings etc in an adhoc fashion and end up with a mess.
(deleted my message bc i think i figured it out, sorry for the noise)
It was, i wasn't handling state between steps correctly. I think i had two issues at the same time. One with just keeping my repl up to date.
When i fixed it, i was confused how i ever got it wrong.
how can I use clj-goes-fast async-profiler and get the milliseconds times for the operations?
I'm not an expert, but I've used it many times. I don't think that kind of information is available, as the purpose of the flamegraph style of profiler is to show relative size of aggregated calls. I would recommend https://visualvm.github.io/ to check on individual function runtime
Or if you're looking to benchmark something rather than to profile it, you could also look at https://github.com/hugoduncan/criterium
clj-goes-fast now has a nice knowledge base http://clojure-goes-fast.com/kb/profiling/clj-async-profiler/
I am reading about reactive programming, and seems that Java programmers use it also, for example spring 5 uses Reactor Core webflux etc
Sure! Both styles have you thinking in terms of signals (transformations of sequences, transformations of reactive signals) a little vague but I guess yes?
Both are good feelings so go with it :)
i am thinking about learning more Java(for job reasons) if now days you can write Java in functional ways also, and where possible to use Clojure
It may be subtle, that in Clojure case does not evaluate the option keys! You cannot use foo inside case to refer to a "constant" that you may have bound to the symbol foo. In ClojureScript the rule is different (and more complicated). Therefore, in any code that might be cljc or ported among clj/cljs, I consider it advisable to avoid symbols inside case.
Also be careful not to quote the symbols in the match