Fork me on GitHub
#clojure
<
2021-06-06
>
lspector02:06:50

Can anyone recommend a graphics library other than Quil? Reasons for "other than Quil" are that the Processing/Quil's applet framework and update loop don't fit well with the projects I want this for, and that Quil appears to be stuck in old Java versions. Ideally the library would work with both Clojure and Clojurescript, and require no interop.

phronmophobic02:06:36

I'm a bit biased, but I think https://github.com/phronmophobic/membrane might be a good fit. The focus is primarily on Desktop, but webgl is supported. Happy to answer any questions.

3
❤️ 3
phronmophobic02:06:28

What issues are you having with Quil's update loop? I think that's one of the areas where membrane is more flexible and might make your program simpler.

lspector14:06:53

Thanks @U7RJTCH6J! I will definitely check membrane out. Are there any examples focused just on drawing, not on GUI interaction?

lspector14:06:19

Maybe something draws a handful of colored shapes?

lspector14:06:18

On Quil's update loop, my applications have their own long-running computation loops, and integrating those with Quil's concept of all computation being done in an update function, with drawing happening once per update, doesn't really fit naturally. I'd like to just be able to call drawing functions whenever I want to, and have the results appear then. Or if there's double buffering (which would be good), have them go to an offscreen buffer and then appear on screen when I call show, or something like that. I know that one can work around the update loop in Quil, and I believe there is documentation on this somewhere on the Quil site that may have been added after I interacted with the Quil devs years ago... but if I recall correctly, it is still awkward if you're not making something that fits the update/draw loop concept behind Processing.

phronmophobic16:06:24

@U06BV1HCH, for drawing examples, check out the https://github.com/phronmophobic/membrane/blob/master/docs/tutorial.md#graphics of the tutorial. The built ins are mostly just paths, rectangles, text, and rounded rectangles. I haven't really needed other shapes, but if you want some other shapes, let me know! Regarding the update loop, the basic idea is that you produce a data structure that represents what to draw rather than imperatively drawing. Membrane tries to stay out of your way, so you have some flexibility for how you structure your application. Here's an example setup if you wanted to wanted to draw shapes from the repl:

(def shapes (atom []))

;; show a window that will draw the latest values of `shapes`
(backend/run (fn [] @shapes))

;; add a rectangle
;; will automatically redraw
(swap! shapes
       conj
       (ui/rectangle 10 10))

;; add a label
;; will automatically redraw
(swap! shapes
       conj
       (ui/translate 25 25
                     (ui/label "hello")))

;; add column of red squares
(swap! shapes
       conj
       (ui/translate 0 (ui/height
                        (ui/translate 25 25
                                      (ui/label "hello")))
                     (apply
                      ui/vertical-layout
                      (for [i (range 0 1 0.1)]
                        (ui/with-color [i 0 0]
                          (ui/rectangle 25 25))))))

;; etc

❤️ 3
lspector17:06:09

Beautiful -- thank you @U7RJTCH6J! The update scheme here also looks great to me, specifically in making fewer assumptions about application structure than Quil. I will definitely be looking into this more!

lspector23:06:49

@U7RJTCH6J is there a way to control the granularity of the screen updates? I made a tight loop that moves a square, and the graphics update much more frequently when I am moving the mouse over the window, but only intermittently when I'm not. Aside from not wanting to have to keep the mouse moving to get rapid updates, there will be situations when I want to make sure that every update is displayed. Are there ways to control this sort of thing? Does it depend on the backend? I've been using skia, but I don't really know much about the options.

phronmophobic23:06:14

I’m away from keyboard, but I can send you some options later today or tomorrow

❤️ 3
phronmophobic23:06:46

for skia, there’s https://github.com/phronmophobic/membrane/blob/e7e60e6b6537c7bc797d3d047adb969ca5074ed3/src/membrane/skia.clj#L871, which is private, but you can call it with requiring-resolve as a work around. Anyway, will explain better options when I get back to my computer

❤️ 3
phronmophobic20:06:34

@U06BV1HCH. Finally had a chance to think about this more. One of the mantras for membrane is "Make the common case easy and the complex case possible". To provide the best option, I think I'd have to know a little bit more about your use case. Here's a few options that sound reasonable based on your description so far: 1. I've been meaning to expose a "repaint" function for each backend that would force a repaint. That would let you just call repaint when necessary. 2. The default event loop for each backend is written with mostly static user interfaces in mind. I could help write an event loop specifically for your use case. 3. I could implement some options that allow the event loop to be configured. I'm a little hesitant about this route though since it leads to programming by configuration. Instead of just directly specifying the event loop, you indirectly pulls some levers and knobs to hopefully get the system to work the way you want. It can provide a good middle ground, but I'd like to design it based on some more real world use cases. 4. You could try the skija backend. It already has a more game-like event loop that will check every frame if the view has updated and draw if necessary. This event loop is less CPU efficient. The Skija backend also has an annoying quirk that you can only open one window at a time and once you close it, it will crash if you try to open another window. This is a limitation of the glfw library that the skija backend uses. Fixing the multiple windows issues is possible, but no one has complained about it, so I haven't spent time on it yet.

lspector20:06:35

Ah -- I had been using skija and experiencing that crashing issue you mentioned! Thanks for noting that!

lspector20:06:25

Of the options you list, the first is the one that seems best for my use cases.

phronmophobic20:06:57

Alright, just created an issue for the skija window issue and updated the skija example project to make note of it.

phronmophobic20:06:09

Ok, I'll work on the repaint issue. Currently, the return value of backend/run is unspecified. The current plan is to have every backend/run return a map with extra info. There will be some standard keys (like a repaint function) and some namespaced keys that are specific for each backend.

👍 2
lspector20:06:47

My use cases fall mostly into two categories, simulations and visualizations of dynamic systems. I work in AI/ML/ALife (and I teach) and one relevant thing I did with Quil is https://github.com/lspector/pucks. Some fancier "simulations" of this general kind that I've done in other environments (and in 3d in these cases) are http://faculty.hampshire.edu/lspector/gecco2003-collective.html and http://faculty.hampshire.edu/lspector/db-gecco-2007/. By visualizations of dynamic systems, I mean that I might have a long-running evolutionary computation process (maybe running with https://github.com/lspector/Clojush or https://github.com/lspector/propeller), and I may want to be dynamically visualizing the status of the system. In all of these kinds of applications there is already a long running loop, and it is awkward to reformulate everything so it integrates with a separate event loop, such as that assumed by Quil/Processing. That said, there will be cases when we want to see every change to the world, so it's not acceptable for the graphics to be running asynchronously and only updating at some coarse level.

phronmophobic20:06:27

Taking a look at some of the links 👀. Looks cool!

👍 2
phronmophobic20:06:21

How much user interaction is there?

lspector20:06:50

For my standard uses, relatively little. The world runs, and we watch.

phronmophobic20:06:13

fyi, you may also be interested find skia/draw-to-image! to useful

lspector20:06:27

Usually our simulations/runs are configured in advance, through a parameter map etc., so we can run repeated experiments. That said, interaction in the GUI offers new opportunities. Especially if we can put these things online, for example to provide an online genetic programming or alife system that you can configure and start from a browser.

phronmophobic20:06:48

Yea, definitely

phronmophobic21:06:21

Someone recently published https://github.com/cnuernber/avclj. At some point, I think it would be cool to have deeper integrations with that library. In general, it shouldn't be very hard to reuse the same views to produce a video/gif, but it could be even easier.

phronmophobic21:06:03

Membrane has a pretty large surface area, so there may be a few rough edges. Don't hesitate to message me if you see an issue or have a suggestion 😄

lspector21:06:14

On the online applications, although I've done little web or GUI stuff myself a student recently produced https://www.sungkwak.me/InterPush/, which allows interaction just with the interpreter at the core of our genetic programming systems. At this point that's just good for demos or tutorials, but I'd like to extend it to do complete genetic programming runs, with a GUI to configure things and to visualize progress and results. I've vaguely thought of doing this with Quil, but haven't put in the effort because of some of the issues at the top of this thread.

metal 2
Jacob Rosenzweig04:06:18

Why does Clojure have multimethod when maps can achieve the same thing? What I mean is that the same problem exists in JS and is solved without additional language constructs. For example:

//OH NO if only there was something more elegant than an if else chain.
function fizzbuzz(val) { 
  if(val === 3) { return "Fizz"};
  else if(val === 5) { return "Buzz"};
  else if(val === 15) { return "FizzBuzz };
  else return val.toString()
}

//Much better :)
function fizzbuz(val) {
  const fizzbuzzMap = {
    "3" : "Fizz",
    "5": "Buzz",
    "15": "FizzBuzz"
  };
  return fizzbuzzMap[val.toString()] ?? val.toString();
}
Here, you're effectively dispatching to different values based on the val. But you could dispatch to functions as well. So what's the benefit we get with multimethods?

lassemaatta04:06:15

What can I do if that map is declared in a library and I want to extend it with a new key+value?

Ian Fernandez04:06:33

you can require this map and assoc some keyword+value for it 🙂

lassemaatta05:06:00

You can require and modify a variable, which is defined within a function (in javascript)? I admit I did not know that.

😆 3
Jacob Rosenzweig05:06:48

I would like to know how this is done

phronmophobic05:06:40

Using a map is more or less how multi methods are implemented in Clojure (https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/MultiFn.java#L24). I don't speak with any authority here, but I think part of the reason for including them in the core language was to attract OO developers. Basically, to include builtin, functional alternatives for typical OO constructs. https://clojure.org/about/runtime_polymorphism

phronmophobic05:06:28

Briefly searching https://download.clojure.org/papers/clojure-hopl-iv-final.pdf, I found: > 3.7 Polymorphism > > It was my intent that Clojure have a predicative dispatch system [Ernst et al.1998]. None of my experiments produced a system with good enough performance to live at the ‘bottom’ of other constructs. Instead I developed multimethods, inspired by Common Lisp’s generic functions.A multimethod defines a single named function, with which one can associate an open set of implementation ‘methods’. Each multimethod is defined with a dispatch function, a function of all of the arguments, and methods are associated with particular return values of the dispatch function.

eskos08:06:58

Multimethod's dispatch function can be a compound/complex logic resolver as well, kinda hard to do that with just map lookups 🙂 Think stuff like multimethod dispatcher which can change its exact dispatch target based on time of day or week/system load/user ACL/partial data...

thheller08:06:48

am I the first one to point out that this fizzbuzz implementation is invalid? you can't use a map if any nat-int is a valid input? what about 6,9,10?

Alex Miller (Clojure team)13:06:14

Also multimethods support Java inheritance and/or custom hierarchies and default values

Jacob Rosenzweig16:06:37

@U05224H0W Then the value from the map is undefined and I null coalesce to val.toString()

thheller16:06:27

sure, I see that. just saying that your impl isn't a correct fizzbuzz implementation. you probably just meant to show an example of how maps can replace a couple ifs, but the chosen fizzbuzz name seemed to imply something else.

thheller16:06:52

6 is supposed to be Fizz not 6 😛

Jacob Rosenzweig16:06:24

Oh yeah I forgot the whole divisible part of the problem. Yeah I completely butchered the exercise I didn't really care about it haha.

emccue17:06:25

I tend to think of multimethods as a library that got put into clojure.core, not so much as fundamental parts of clojure.

emccue17:06:07

You can certainly use maps to dispatch, but that has different benefits and tradeoffs than multimethods

emccue17:06:02

and doesn't really require a special mechanism all its own

Alex Miller (Clojure team)19:06:54

I think having a built-in mechanism for open dispatch is a fundamental part of Clojure

zendevil.eth06:06:00

What’s the best way to call a Julia language function, and get its output in Clojure? Say I have a Julia file with a function increment, that takes one number and returns its increment. What’s the best way to call this function from Clojure and return its result?

p-himik07:06:14

Unless Julia also works on JVM, I'd say you're better off just making it possible to run that function as an executable, and calling it from Clojure via clojure.java.shell/sh.

👎 3
borkdude07:06:36

Perhaps you could call Julia functions using JNI?

javahippie07:06:03

If you are comfortable with using code from the incubator packages in Java 16, there is also the Foreign Linker API instead of JNI: https://openjdk.java.net/jeps/389, you should be able to call libjulia from it, as it is possible from C? The Foreign Linker API might change again in the future, though

👀 3
zendevil.eth19:06:57

Executables are nightmares for portability

zendevil.eth19:06:35

@U04V15CAJ I think JNI is how libjulia-clj works

zendevil.eth19:06:29

@U0N9SJHCH that’s really cool. This method seems to reduce the amount of setup necessary to create the link as compared to JNI.

javahippie19:06:13

It‘s part of Java’s project Panama and planned to be a more robust and effective JNI. Foreign Memory Access goes hand in hand with it. Still both in preview, but I already saw some OSS projects using it.

zendevil.eth03:06:28

Foreign memory access means that I can read and write the contents of the heap through the host program? How does the datatype inference and conversion work of the memory reads, writes, params and return values? If it’s planned to be a superset of JNI, then why does it say on the page that it’s not intended to replace it? What does OSS stand for?

javahippie06:06:54

For technical details on foreign memory access, I can only point you to this design document, I have not looked into that in detail, yet: https://github.com/openjdk/panama-foreign/blob/foreign-jextract/doc/panama_memaccess.md I said “more robust JNI” to categorize the API, but it is not meant as a literal replacement, sorry if that was not clear. JNI will stay for all times to guarantee backwards compatibility. OSS stands for Open Source Software.

chrisn13:06:03

@U0N9SJHCH - https://github.com/cnuernber/dtype-next/, the library that is used to build the libjulia-clj bindings supports that jep that you mention. It generates FFI bindings at compile time that target JNA, JDK-16 (that JEP) and/or Graal Native. I have a https://techascent.com/blog/next-gen-native.html talking about how to use it and I have an https://github.com/cnuernber/avclj/ that you can use as a reference and that is capable of generating bindings for any of those platforms. As another example, I have ran libpython-clj on JDK-16 using the foreign module which contains the various classes you use to load libraries and find functions.

didibus21:06:23

I'm curious if it's possible to implement let using functions? Something like:

(let [a 10
      b (+ a a)]
 b)
Rewritten as:
((fn[x] (+ x x))
  ((fn[x] 10)))
But I'm failing to generalize this.

didibus21:06:06

Say the goal was to write a let like macro that internally just rewrote the form using only anonymous function composition

borkdude21:06:23

could you explain why you would like to do this?

didibus21:06:31

Purely as an intellectual exercise haha. I'm actually not sure if it's possible, but I have it in my head that it should be.

quoll21:06:25

Yes, we did this in gherkin, which was an interpreted lisp

quoll21:06:38

For every [a b] we created:

((fn [a] …body…) b)

👍 3
didibus21:06:45

Wow, my bash is not good enough to follow its source code though haha. But I'm glad yo know my instinct is correct in thinking you can.

borkdude21:06:54

well yeah, it does seem possible since locals introduced by a function aren't really different from locals introduced by let

quoll21:06:03

I think it was Alan who did it? Maybe? I did bits, but most of it was him. It was a fun project. I learned a LOT about Bash

borkdude21:06:24

I never heard of it, but it seems like a pretty cool project

didibus21:06:31

Ya ok, that was the trick I think. So:

(let [a 10
      b a
      c (+ a b)]
 c)
Becomes:
((fn[a]
  ((fn[b]
    ((fn[c] c)
     (+ a b)))
   a))
 10)

👍 6
quoll22:06:55

I’m on my phone, but that should be a link to the lightning talk Alan did on it

quoll22:06:45

You don’t hear it in the video, but the whole room was laughing 😊

borkdude22:06:04

In the context of babashka, I should have been aware of this piece of history. I just watched the lightning talk. Also read an article from him written in 2020 as a retrospective on this project. Very interesting, thank you for sharing.

👍 3
quoll23:06:41

We can’t be aware of everything. This is why participating in the community is so important, and you do that extremely well!

Ben Sless05:06:53

Really surprised no one mentioned SICP https://sarabander.github.io/sicp/html/1_002e3.xhtml#g_t1_002e3_002e2 > Using `let` to create local variables

quoll10:06:22

Because I forgot 😳😆

😄 3
Ben Sless10:06:41

Turns out Haskell's core compiler does these transformations in both directions, too

borkdude10:06:36

what do you mean in both directions?

Ben Sless10:06:36

Sometimes it "applies" functions by turning them into let bindings and sometimes it abstracts let bindings into functions

didibus22:06:09

I don't think the SICP transform behaves like Clojure's let though, but maybe it does for Haskell?

didibus22:06:42

Because in Clojure the previous bindings are made available to the later exprn forms of the following bindings and not just to the body

didibus22:06:37

(let [a 10
      b (inc a)]
 (+ a b))
Would become:
((fn[a b] (+ a b))
 10
 (inc a))
Which doesn't work, does SICP have a trick later for it? Otherwise with @U051N6TTC way of doing it, it works and you do:
((fn[a]
  ((fn[b]
    (+ a b))
   (inc a)))
 10)

quoll22:06:02

Scheme has let* which behaves like Clojure’s let

quoll22:06:40

(somewhat amusingly, Clojure also has a let*… no relation)

oliver21:06:25

I'm trying to translate the https://github.com/microsoft/playwright-java#page-screenshot to Clojure. I have imported playwright as follows::

(:import com.microsoft.playwright.Playwright
         com.microsoft.playwright.Page)
I'm running into trouble with the creation of the ScreenshotOptions. Page seems to be imported properly, since evaluating it as-is yields the corresponding class com.microsoft.playwright.Page. However, I'm getting a ClassNotFoundExeption for for (new Page.ScreenshotOptions) . Guessing that ScreenshotOptions is a nested class I've also tried (new Page$ScreenshotOptions) which yielded: “Unable to resolve classname: Page$ScreenshotOptions”. For Reference, here's the corresponding part of the Playwright API: https://www.javadoc.io/doc/com.microsoft.playwright/playwright/latest/com/microsoft/playwright/Page.ScreenshotOptions.html My understanding of Java is quite brittle and I'm about at my end of it. Most likely I'm missing something obvious here – any ideas as to what that might be?

ghadi21:06:40

Page$ScreenshotOptions is a distinct class from Page, and must be imported in the same way

ghadi21:06:15

(:import [com.microsoft.playwright Playwright Page Page$ScreenshotOptions])

3
oliver21:06:58

That did the trick, thanks a lot. I've taken embarrassingly long before asking about this… Actually, I was sort of close trying to import com.microsoft.playwright.Page.ScreenshotOptions but probably wouldn't have found the solution for quite some more time… so thanks again for the swift solution!