Fork me on GitHub
#clojure
<
2022-12-06
>
escherize01:12:22

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)

phronmophobic01:12:20

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

escherize01:12:25

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.

escherize01:12:00

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

escherize01:12:29

I don’t deny it’s pretty close to just using vanilla clojure s-expressions… but there is a difference

phronmophobic01:12:22

> 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.

1
David Ackerman04:12:53

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.

1
escherize04:12:54

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

escherize04:12:09

Yeah, you can meta-dot into it.

escherize04:12:43

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

seancorfield04:12:53

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...

escherize04:12:48

re-frame isn’t really a “Clojure interpeter” but it has some similar characteristics

seancorfield04:12:17

You can use re-frame on the server 🙂

seancorfield04:12:27

(I tried it... it works but it's kind of painful)

escherize04:12:54

I was in the room when we were making that happen, fwiw. but let me try another example

escherize04:12:40

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

seancorfield04:12:37

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).

David Ackerman04:12:44

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

seancorfield04:12:25

Sure, but reitit explicitly has an interpreter -- or you could argue a "compiler".

escherize04:12:01

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

David Ackerman04:12:14

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

David Ackerman04:12:05

(not trying to shoot down your idea btw, mostly trying to understand)

escherize04:12:00

Thanks for saying that, that’s how I’m taking it. just trying to clarify for myself too

seancorfield04:12:47

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 😞

escherize04:12:09

(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)])

seancorfield04:12:20

(It also tended to produce monadic code ... which I never really liked)

escherize04:12:10

is it open source?

escherize04:12:43

is it the only open source attempt?

Ben Sless05:12:31

You might want to read up on the free monad,too

Ben Sless05:12:49

Looks similar to what you want to accomplish

👌 1
seancorfield05:12:45

(see my comment about monadic code above 🙂 )

Ben Sless05:12:01

It's early 🙃

seancorfield05:12:41

I feel validated by your confirmation 🙂 And "good morning!" 🙂

👋 1
phronmophobic05:12:12

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.

escherize18:12:05

The vocab from the free monad pages is helpful

Drew Verlee03:12:07

(deleted my message bc i think i figured it out, sorry for the noise)

ghadi22:12:48

I’m curious about this if it was iteration related

Drew Verlee22:12:10

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.

Drew Verlee22:12:45

When i fixed it, i was confused how i ever got it wrong.

Ian Fernandez16:12:53

how can I use clj-goes-fast async-profiler and get the milliseconds times for the operations?

Joshua Suskalo16:12:56

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

Joshua Suskalo16:12:19

Or if you're looking to benchmark something rather than to profile it, you could also look at https://github.com/hugoduncan/criterium

☝️ 1
Takis_18:12:11

I am reading about reactive programming, and seems that Java programmers use it also, for example spring 5 uses Reactor Core webflux etc

Takis_18:12:42

does reactive programming feels like functional programming ?

Sam Ritchie19:12:15

Sure! Both styles have you thinking in terms of signals (transformations of sequences, transformations of reactive signals) a little vague but I guess yes?

Sam Ritchie19:12:23

Both are good feelings so go with it :)

Takis_21:12:39

thank you : ) i am going for it

Takis_18:12:39

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

Apple22:12:19

Is symbol considered constant? Does case not support symbol matching?

dpsutton22:12:28

it matches on symbols

dpsutton22:12:29

(case 'foo
  bar :bar
  baz :baz
  foo :found-foo)
:found-foo

Apple22:12:05

thx. sth wrong with my code then.

dpsutton22:12:21

always check simple examples in the repl

phill23:12:41

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.

Sam Ritchie04:12:52

Also be careful not to quote the symbols in the match

valerauko05:12:07

might be an issue with namespaced vs not namespaced