Fork me on GitHub
#clojure
<
2021-06-09
>
pinkfrog00:06:19

Need some help on this. Will you defprotocol Foobar with only a SINGLE defrecord FoobarImpl implementing the protocol? This way helps unit test so that we can write a defrecord FoobarTestImpl, but on the other side, I feel writing code in this way only for testing is unnecessarily complicated. Why not directly write functions working on the passed-in data? And we can use with-redefs to change the function logics under testing.

vemv10:06:47

I do it and know of other teams also doing it it's the sort of thing that may look ugly from the outside, but once you just try it one sees only a useful, clean pattern that's not even particularly verbose (I'm a fan of implementing protocols via metadata; this also kills some code-reloading issues)

vemv10:06:28

Interestingly, protocols can be considered to reify side-effects (or "side-causes"), yielding a functional (-ish) architecture

pinkfrog07:06:52

Hi. Didn’t quite get this part: protocols can be considered to reify side-effects (or “side-causes”), yielding a functional (-ish) architecture.

vemv09:06:42

under vanilla clojure, if you want to perform a side-effect, you normally just do it, inline: (write-to-db! ...) such calls make a given defn impure. There's no automated way to analyse impure functions, instrument them, etc by having protocols to perform all side-effects, you can get a comprehensive listing all sorts of side-effects/side-causes going on in your codebase. It's easier to determine if a given defn is impure: if it receives an object that implements a protocol as an argument, then it's almost certainly impure there are various other gains as you can imagine: swapping implementations (for making tests faster/deterministic) etc

👍 3
seancorfield01:06:31

I’m not a fan of creating a protocol purely for mocking during testing. That feels both too-OOP-ish and just plain unnecessary.

seancorfield01:06:47

with-redefs has its problems but using functions feel like the “right” approach to me — why not pass in the function that you might otherwise want to redef? Or refactor your code so you don’t have a (presumably) side-effecting call in the middle of it?

souenzzo02:06:30

When I call realized? on a "pending after start realize" delay it blocks my call until realized. Is it expected (repl example in the first comment)

souenzzo02:06:57

(let [x (delay
          (Thread/sleep 1000))
      start (System/currentTimeMillis)
      diff! #(- (System/currentTimeMillis)
               start)]
  (prn [:before (realized? x) (diff!)])
  (future
    @x
    (prn [:after-deref (realized? x) (diff!)]))
  (Thread/sleep 100)
  (prn [:after-start-deref (realized? x) (diff!)]))
[:before false 0]
[:after-start-deref true 1001]
[:after-deref true 1001]
=> nil

dpsutton02:06:11

I think there’s an IPending or something similar that gets involved with this

Alex Miller (Clojure team)03:06:45

in the queue for 1.11

✔️ 3
pieterbreed08:06:06

Has anybody here had to create POJOs before? I have a specific requirement to interface with jackson-databind, which runs on POJO's with specific Java annotations on the POJO classes, constructors and getters. Has anybody done this kind of thing before? I can get one POJO declared with gen-class, but the syntax is cumbersome for the many POJOs I need to create. I thought I'd use a macro to create the gen-classes for me, but with my own macro I can't pass in the meta-data that gen-class needs for annotations... Does anybody have some advice for me?

lassemaatta08:06:17

I had a similar case when implementing a web service API where I needed java classes with lots of annotations (and no args constructors, mutable fields with setters,…) to generate the WSDL. In the end I decided to just define the classes as plain Java code, instead of constructing them by gen-class etal.

👍 2
henrik4210:06:34

Have you tried using defrecord? It supports Annotations I believe. Some Java APIs need a default constructor though and in that case gen-class is all I've come up with when I needed it.

pieterbreed13:06:59

I also tried defrecord but could not make it work

souenzzo15:06:21

I'm not a JAVA guy and I just briefly know what a "POJO" is, but i thikn that these links can help https://github.com/clojure/java.data https://clojure.org/reference/datatypes#_java_annotation_support

nod12:06:28

Hey! Quick question: How can I efficiently update every nth element in a vector? As in, I want something faster than iterating over every single element. Thanks! (I just posted the same question on #beginners, not sure if I should have posted here instead)

p-himik12:06:03

Vectors are associative collections, so just (assoc v 17 x).

p-himik12:06:22

Oh, sorry, I missed the "every" word. Then just mapv.

nod12:06:45

Thanks! I tried to understand it but I don't see how that would be done (I'm still a beginner, sorry...) And wouldn't that still require iterating over the entire vector?

solf12:06:06

A possible way using transients. This multiplies each 3rd element by 10

(let [data [1 2 3 4 5 6 7 8 9 10]
        step 3]
    (persistent!
     (reduce
      (fn [data i]
        (assoc! data i
                (* (get data i) 10)))
      (transient data)
      (range 0 (count data) step))))
  ;; [10 2 3 40 5 6 70 8 9 100]

solf12:06:25

Ah but if you’re beginner there’s also the more straight-forward way without using transients:

(let [data [1 2 3 4 5 6 7 8 9 10]
        step 3]
    (reduce
     (fn [data i]
       (assoc data i
              (* (get data i) 10)))
     data
     (range 0 (count data) step)))
  ;; [10 2 3 40 5 6 70 8 9 100]

nod12:06:43

Thank you! Do you know if this offers better performance than the solution Alex gave? I'll paste it here:

(loop [coll v, i 0]
  (if (< i (count v))
    (recur (assoc coll i (inc (nth coll i))) (+ i 10))
    coll))

solf12:06:00

it should be faster, yes I didn’t see the (+ i 10), it’s pretty much the same written differently

solf12:06:07

but only marginally so?

solf12:06:32

The transient version however should be noticeable faster. You could also add transients to Alex solution though

solf12:06:05

Here’s the official guide if you’re interested in performance: https://clojure.org/reference/transients

p-himik12:06:19

mapv uses transients by itself. Don't try to optimize without measuring and at places that aren't bottlenecks.

👍 2
solf12:06:46

Fair point, but mapv would still have to loop over every item, which can be very bad depending on how big is the nth

p-himik12:06:24

Ahh, right, I see - you have created a transient from the already existing data. Makes sense, and I'm especially blind today for some reason.

erwinrooijakkers13:06:28

Since vector is associative you can use update as well with an index as second argument:

(update [0 0 0 0 0] 2 inc)
;; => [0 0 1 0 0]

erwinrooijakkers13:06:33

Ah I see there’s no transient version update!, that’s why above used assoc

solf13:06:48

Indeed, for the non-transient version update is better

john-shaffer15:06:03

Any profiler recommendations? It's all Clojure code, and so far I just want to know which functions we're spending the most time in

jumar18:06:47

Bear in mind that by default it shows on-CPU time - if you are interested in wall clock time there’s another option for it

Alex Miller (Clojure team)15:06:45

probably the easiest clojure-oriented profiler. you can use any JVM profiler too (YourKit, JProfiler, VisualVM etc)

denik16:06:18

Just published an article about Jive, an architecture where UI code is server-rendered but still web-based. This enables native backends with the reach of web. Give it a read and share your thoughts! https://kalavox.medium.com/jive-for-artisanal-software-2df0d675c104

🖤 2
gratitude 1
emccue18:06:24

> It doesn't exist yet

tatut05:06:46

you mentioned Ripley, I wrote about that a year ago https://dev.solita.fi/2020/06/01/rethinking-the-frontend.html I took the approach of server side apps that are enhanced with live components… you can probably get even better patch granularity and smaller websocket traffic with full “vdom on the backend”

💯 2
gratitude 1
tatut05:06:38

but I find that sending html fragments is good enough, tho I don’t have any real world benchmarks to give

tatut05:06:01

and of course, I didn’t even try to solve the “no server” scenario

zeitstein08:06:30

Late to the party, but I've found the two articles linked above very engaging reading, thanks! I'm wondering whether your thoughts, @U11SJ6Q0K and @U050CJFRU, have evolved further? Have you found more downsides? Being unable to work offline (a hard problem either way) notwithstanding, it is the latency that is most worrying me (as somebody looking to build, say, a Notion-like UI): > Some applications require very rich interactions and those will benefit most from executing logic directly on the browser. But given that the average https://humanbenchmark.com/tests/reactiontime is over 200 milliseconds there’s plenty of time for a server round trip in most cases. Perhaps a rich text editor component will need browser side components still but common CRUD applications with tabular listings and forms are no problem. Though: > The average [localhost] latency out of over 10,000 requests was ~0.23ms. That's 4347 updates/second (`1000/0.23 ~ 4347.82`). This 0.23ms is especially negligible when processing and UI are not on the same thread (hi SPAs!). > This will greatly depend on your internet connection. On my phone, a roundtrip took 19ms, on desktop over wifi 12ms. I'm having a hard time reconciling the app being UI-heavy but also having a complex data model (meaning that a lot of the decisions about the state of the UI have to be deferred to the JVM db – so the SPA optimistic update approach can only work sometimes). (@U899JBRPF might be interested in the discussion.)

🙂 1
tatut08:06:39

for the kinds of apps I do, it hasn’t been a problem at all

tatut08:06:20

for example in a query view, an SPA will need to send a query, wait for results, read JSON, involve client side logic to update app db and then render… live components are a good replacement

tatut08:06:29

the photon demos look particularly impressive

zeitstein09:06:01

Completely agree (Ripley, too!). I can't escape the feeling, though, that for a UI-heavy app one might end up needing a bunch of client-side state and logic, and then the question becomes how does that blend with the 'server-first' model. You are saying, though, that my feeling is wrong for some kinds of apps.

tatut09:06:27

I’ll concede that ripley doesn’t have a good story for how to blend large scale JS parts with the server side rendering, though I have some ideas on it

👍 1
tatut09:06:35

Something like inertiajs has a sort of middle ground approach, the components are made in SPA style, but server is still mostly in control

denik14:06:45

@U02E9K53C9L glad you enjoyed the article! The article addresses your concern re: offline use: > A Jive app combines all of the above into an architecture that treats the browser as a dumb view layer while moving all app logic and computation to a preferably local but optionally remote backend. It shouldn’t be hard to convince power users to install a local backend. This could be minimally represented in the OS menu bar (MacOS) with an icon and do little more that imply that it’s running and communicate whether it’s up to date with the remote server. The frontend would default to this backend and otherwise fallback to a remote backend. At this point I’m most excited about #hyperfiddle’s photon because it’s compatible w/ the ideas of JIVE but far more expressive.

denik14:06:24

I’ve since taken a less ambitious approach in building https://yam.wiki/. There’s no sync’d virtual dom. Components simply issue http requests and store some results in global state. The frontend is overall dumb. UI code is < 1000 LoC. The backend runs queries over datasets that would blow up any frontend in-memory DB and it’s fast.

👀 1
zeitstein06:06:08

Thanks for continuing the discussion! I didn't specify what I mean by "working offline", so this might be causing confusion 🙂 I guess I'm thinking of "offline-first". Some thoughts on that: • If you're running just the local backend, how simple exposing it to the Internet (or P2P) is? And still have to deal with unreliable network. (What I meant by "hard problem".) • If you're assuming that there is always local + remote, the users need to handle the remote server? Doable, but might be asking too much from users? (That's assuming the user controls the remote, perhaps you're thinking the company would provide that as a service?) • If local server runs on JVM, it is my understanding this is not really feasible on mobile devices? So, you have to fall back to remote and you're back to dealing with unreliable network. Being able to work offline on desktops/laptops is certainly a very good start for me, though 🙂

denik13:07:24

> If you’re running just the local backend, how simple exposing it to the Internet (or P2P) is? And still have to deal with unreliable network. (What I meant by “hard problem”.) Sure, every app, native or not has this problem if if needs to talk to the internet. The benefit of the local backend is multiple cores, persistent disk storage, polyglot languages etc. > If you’re assuming that there is always local + remote, the users need to handle the remote server? Doable, but might be asking too much from users? (That’s assuming the user controls the remote, perhaps you’re thinking the company would provide that as a service?) The latter, that a company would provide a sync service. AFAIK logseq is going to ship something like it soon. Obsidian.md also charges for sync last I checked. > If local server runs on JVM, it is my understanding this is not really feasible on mobile devices? So, you have to fall back to remote and you’re back to dealing with unreliable network. Don’t forget about graalVM. there are already a few durable databases (e.g. datalevin) and http servers that can be compiled with graal (and looking at babashka, a good chunk of the mainstream clojure ecosystem). I did some research a while ago and it seems some people packaged graal native executables in mobile apps. a few unknowns there for sure

gratitude 1
👍 1
zeitstein07:07:16

> I’ve since taken a less ambitious approach in building https://yam.wiki/. There’s no sync’d virtual dom. Components simply issue http requests and store some results in global state. The frontend is overall dumb. UI code is < 1000 LoC @U050CJFRU can you comment on why you've taken this approach?

denik14:07:07

Sure. After writing the article @U09K620SG (hyperfiddle/photon) reached out. JIVE is a puddle jump compared to the leap of photon. So I decided to make my UI as dumb as possible with the expectation that I can throw all of it away and rewrite using photon

gratitude 1
borkdude20:06:48

Has a reflection cache been considered for Clojure? I.e. cache the lookup of the method based on the type of the receiver and types of args?

ghadi20:06:30

What’s the unsaid problem that is trying to solve?

ghadi20:06:41

(drink beverage of choice)

borkdude20:06:16

@ghadi I'm just trying to gain information if something like this has been discussed before

borkdude20:06:09

The problem could be that repetitive lookups are a waste, but I'm not sure if that is true. I'm just thinking out loud, hoping that someone will say something smart or will provide a link to an existing JIRA :)

borkdude20:06:26

Context: I am working on an issue in the context of a graal-compiled binary where reflection works a bit different, since not all classes might have all information available, but sometimes methods can be found by visiting super classes and doing lookups on those, which seems quite wasteful to do repetitively. So I wondered if this topic had come up before.

ghadi21:06:11

😂 at the ninja edit -- I saw it. I have only considered it in the context of making a cache at reflective callsites. That would reflect only at the first invocation, then remember the dispatch for subsequent invocations.

ghadi21:06:47

for that use-case (slow "unresolved/reflective call" ), it's probably better to not reflect

ghadi21:06:54

aka tell the developer to be explicit

ghadi21:06:25

the topic of asking putting a method reflection cache in the compiler has come up some years ago

ghadi21:06:40

but again, that's an impl thing, not a problem thing

ghadi21:06:58

can you describe the graal thing some more?

borkdude21:06:37

yeah ok. two things: 1) the SCI interpreter is using reflection at runtime to perform interop calls (this can be optimized more, but that's how it works currently) 2) Graal native binaries support reflection configs. This makes it possible to reflect at runtime like you would in the JVM. But if you don't provide a config for class X let's say, and you wanted to call Object.notify on an instance of X, you would reflect on the class X, but find nothing. However, when you visit the superclass, e.g. Object, and you had a reflection config for Object, then you would still find notify and would be able to call it. This is more work than you would normally have to do on the JVM in the clojure.lang.Reflector. Depending on which class you hit, you would hit that path or not, so I would have to do some benchmarking etc to see how bad the overhead is there, but that's where I'm coming from.

borkdude10:06:20

Solved part 2

Alex Miller (Clojure team)21:06:33

I don't know of anything like this in jira. The general rule is if it matters, stop using reflection. So, not much interest historically in trying to optimize the not optimized case.

✔️ 4
awb9922:06:24

I have a weird bug when working with RELEASE dependencies: "clojure -X:notebook" brings this error Execution error (FileNotFoundException) at clojure.run.exec/requiring-resolve' (exec.clj:31). but "clojure -P -X:notebook" works fie. Thishappens only when there is no artefact of the dependency in .m2 cache. Is there a shortcut that works in all circumstances?

hiredman23:06:20

you have a stale classpath cache

hiredman23:06:06

clj caches the calculated classpath between executions to speed up startup

hiredman23:06:38

(goes in ./.cpcache)

hiredman23:06:22

you are running the clojure script in an evinronment where the cache exists, and tells it the dep is in ~/.m2, but that isn't the case