Hey folks, I'm working my way through the ray tracer challenge and on chapter 2. This chapter starts with the implementation of Color, guided by this pseudocode test:
Scenario: Colors are (red, green, blue) tuples
Given c ← color(-0.5, 0.4, 1.7)
Then c.red = -0.5
And c.green = 0.4
And c.blue = 1.7
I'm hoping folks can weigh in on ideas for how to implement this, given that in chapter 1 I implemented the concept of a tuple, which similarly takes in three values (with an optional 4th to distinguish between points and vectors), as well as tuple operations tied to those tuples:
(defn tuple
([x y z] (tuple x y z 0.0))
([x y z w] {:x x :y y :z z :w w}))
(defn point
([x y z] (tuple x y z 1.0)))
(defn vector
([x y z] (tuple x y z 0.0)))
(defn add [t1 t2] ...)
(defn subtract [t1 t2] ...)
(defn dot [t1 t2] ...)
...
So the idea is that colors are a type of tuple, and we can reuse that underlying structure and functions for colors. In Go, I would likely implement this as a combination of structs and interfaces, and in Python using inheritance. I think that the notion of polymorphism exists in Clojure, but I don't know enough to say whether that's the right direction here (or even what that looks like exactly), or if there are more clojure-esque ways to go about with the above implementation.
Any advice or guidance here would be greatly appreciated! I don't need a full implementation - I'm more interested in learning what my options are, and what best practices I should be aware of for tackling this kind of problem both functionally and specifically in Clojure. Thank you!Probably the most common answers are "just use maps" and defrecord . Maps require basically no setup, but they're not as amenable to protocol-based polymorphism. A record provides a bit more structure and a little bit faster access to the declare fields and a type that can be used to extend a protocol. Depending on what operations you've got in mind, multimethods might make it possible to do some things more elegantly, but multimethods are more value-based, so whether they're being passed maps or records is less important in that context (or at least, more interchangeable).
You can always start with maps and move to records if you need them, because keyword access is functionally the same on both.
it's also possible to put a protocol implementation on a clojure value (like a map): <https://www.clojure.org/reference/protocols#_extend_via_metadata> has more on that (and that page also has more general information on protocols)
Thanks for the input! I'll check out the link. When you say "just use maps", is it as simple as relying on the fact that the tuple and color functions will each return a map with the same set of keys?
Relying on that for what specifically?
The add operation, for example, which returns a new tuple with the sum of each pair of keys (x: tuple1.x + tuple2x, y: tuple2.y + tuple2.y, etc). Since a color is more of a semantic representation that under the hood functions just like a tuple, I'm asking if the "Clojure way" of "just using maps" translates to giving the color map the same keys as the tuple map, so that either get can passed as an argument to add without issues
I'd say no... if the desire is for different behavior, then that add function should probably be part of a protocol, or functions could be in different namespaces. Alternatively, if the desire is for a generic "add the numbers in these maps" functions, then merge-with + could work (if the edge case behavior suits the need).
What do you mean by a tuple? That it has to be a contiguous array ?
The book defines a tuple as a data structure containing the attributes x, y, z, and w. It's meant to model points and vectors as the foundational pieces of a ray tracer
In Clojure a "point" can be as simple: A vector of length 3:
[334 253 199]
A variant (aka a vector where the first element is a keyword):
[:point3d 334 253 199]
A simple map:
{:x 334
:y 253
:z 199}
A map with a notion of "type":
{:x 334
:y 253
:z 199
:type :point3d}
The notion of "type" can take different forms such as:
#:point3d{:x 334
:y 253
:z 199}
Or as metadata:
^{:type :point3d}
{:x 334
:y 253
:z 199}
Or it can be a record:
(defrecord Point3d [x y z])
And there's more ways you could do it still.I appreciate the input! My question is not about how to model a tuple though
Well, the thing is, normally in Clojure, you wouldn't hide the data behind an interface or object or some similar thing. So how you would model a point is really just as simple as any of the things I've showed. There's nothing else to do.
Like say your point is just this map:
{:x 234
:y 354
:z 343}
Then your "structure" is just:
(defn make-point
[x y z]
{:x x
:y y
:z z})
And if you want to get x you do:
(:x (make-point 1 2 3))
And if you want to update x you do:
(assoc (make-point 1 2 3) :x 5)Or you could just use merge-with directly where you want too.
Are you saying that the same x, y, z, w is added differently if it's a Point or a Color, or a Vector ? So you want polymorphism on that?
It's not that they're added differently - it's that the color type has labels of red, green and blue, but I think that's tripping me up unnecessarily. I was still thinking of how to model it as a dependency/subclass of color on the underlying tuple, but your comments are making me realize that they're just maps with the same attributes - if it becomes important to really have a concept of "green" for a label, I can deal with that then, but I think for now I can continue to reuse the same map structure I'm using for tuples. Thanks!
Hum, what would you have done in Python/Go? Something like:
getRed() {
return this.x;
}There are a lot of options depending on what you want to do. You could implement the various protocols for indexed lookup and key lookup so that (nth my-color 0) and (:red my-color) do the right thing. You could also implement protocols so that regular vector operations apply to both colors, points, etc. I've seen variations of "just use maps", "use vectors", implement custom types, etc. I think any of these approaches are viable.
Here's some similar examples if you're interested:
• https://github.com/thi-ng/geom
• https://github.com/mikera/vectorz-clj
• https://github.com/sicmutils/sicmutils
• https://clojure2d.github.io/clojure2d/docs/codox/clojure2d.color.html
• https://github.com/Clojure2D/clojure2d
• https://cloogle.phronemophobic.com/doc-search.html?q=return+a+point+in+3d+space
• https://cloogle.phronemophobic.com/doc-search.html?q=return+an+RGB+color
Common approaches will often define a data model based on protocols like associative protocols, indexed protocols, and some domain specific protocols. Then, implementations will be provided for those protocols. There's usually a straightforward, pure clojure implementation with few dependencies that's good for examples and then a less approachable, high performance implementation which may rely on java or native code.
@didibus yea in go it would likely be an interface approach in Go. Python maybe something like
class Color(Tuple):
def __init__(self, x, y, z, w):
super().__init__(x, y, z, w)
@property
def red(self):
return self.x
...@smith.adriane thanks! I'll take a look through some of those examples to see if anything sticks out. I think I'm over-engineering at the moment and everything so far is simple enough that maps should cover it, but all of the input has helped with my understanding of what's possible in terms of domain modeling and actual operations. I appreciate it!
You "can" do accessors (getters/setters), but it's really verbose, and kind of annoying to use those instead of like normal map functions. And the biggest downside is if you printed them, you'd see :x, :y, :z and not :red, :green, :blue. I think what I would do is re-implement add, sub, and all for colors as well. You can delegate to the tuple impl inside those for re-use like so:
;; require clojure.set :as set
(defn add
[color]
(tuple/add (set/rename-keys color {:red :x, :green :y, :blue :z})))That way you color structure is:
(defn make-color
[red green blue]
{:red red, :green green, :blue blue})
And when you print it will print as such. But you still delegate to the tuple impl for most things.But, I also feel, since they say "tuple", and normally a tuple means a list of known size. Maybe using variants would work better here?
(defn make-tuple
[kind x y z w]
[kind x y z w])
(defn make-color
[red green blue transparency]
(make-tuple :color red green blue transparency))
(defn make-point
[x y z w]
(make-tuple :point x y z w))
Then it's easy to define "generic" functions:
(defn add
[[kind1 x1 y1 z1 w1] [kind2 x2 y2 z2 w2]]
(assert (= kind1 kind2))
[kind1 (+ x1 x2) (+ y1 y2) (+ z1 z2) (+ w1 w2)])
Or specialized ones:
(defn darken
[[color red green blue transparency] amount]
(case color
:color [color (- red amount) (- green amount) (- blue amount) transparency]))
It avoids the issue that different "tuples" conceptually might give different names to each position
something that might be missing here is that idiomatic clojure usage proliferates functions not data types. recall that classic line about "better to have 100 functions with 1 data type than 10 data types with 10 functions each".
nothing you describe here needs anything more than a hash map with some keys IMHO. write the functions that implement the logic you need, use hash maps that look like {:x x :y y :z z :w w}, it doesn't need constructors or accessors and that is an advantage
Since it's a ray tracer I propose to stick to type annotated records and functions only. Sooner rather than later you'll hit performance issues when you pick Clojure general structures like maps or vectors. You can use a protocol for common object operations like add, sub and other. For simplicity I think one 4 elements tuple will cover vector, point and color types. Something like:
(defrecord Tuple4 [^double x ^double y ^double z ^double w])
(defn color [^double red ^double green ^double blue]
(Tuple4. red green blue 0.0))
(defn vec3 [^double x ^double y ^double z]
(Tuple4. x y z 0.0))
(defn add [^Tuple4 v1 ^Tuple4 v2]
(Tuple4. (+ (.x v1) (.y v2))
(+ (.y v1) (.y v2))
(+ (.z v1) (.z v2))
(+ (.w v1) (.w v2))))
(defn red ^double [^Tuple4 v] (.x v))
(defn green ^double [^Tuple4 v] (.y v))
(defn blue ^double [^Tuple4 v] (.z v))
(add (vec3 1 2 3) (vec3 4 3 2))
;; => #playground.Tuple4{:x 4.0, :y 5.0, :z 5.0, :w 0.0}
(red (add (color 100 200 300) (color 200 100 50)))
;; => 200.0You can save your time and use fastmath where you have Vec4 and Mat4x4 types already defined with all necessary operations.
@tsulej on reflection I agree about the advantage of type hinting and record access. does using the interop style constructor actually make a significant difference compared to the hash-map style constructor though?
As far as I remember the difference is minor. But! In rendering it will be called billions of times, so everything matters.
similarly, re (defn red ...) etc. why not name the slot :red and use :red as your function instead of making a new function? is the function's hinting doing something useful there?
Function hinting is important to keep the call chain primitive as much as possible.
maybe that's more useful for java's hotspot optimizer
yeah, that makes sense, thanks
honestly clojure is a weird choice for implementing a ray tracer, since it's a part of the system that is easy to modularize out and it goes against clojure's grain so much. probably an interesting exercise though
graphics programmer here - (also I am a complete clojure noob). my advice is to have your vector representation live below ideas like points, colors or directions. I would also advise that you try to let go of the naming of the elements within vectors, as it gets limiting, especially if you eventually want code that works over 2D as well as 3D or higher... I think earlier, there was a suggestion of implementing those names as accessor functions - so (x some_vector) would return some_vector[0] etc. if you want to mark some value as being a color, rather than a point, there are several nice-looking suggestions above.
Slot is not named :red since I proposed to reuse tuple in several contexts. I would just call (.x tuple)
what is the advantage of (.x tuple) over (.get tuple 0) ?
(obviously that's informed by what @nshepar says immediately above)
(.x tuple) will directly access generated class field, while .get will call a function.
I misremembered the name - I meant, if it was a raw java float-array, simply asking the float array for element 0 - this skips a lot of the needed hinting as well
public static Object invokeStatic() {
final Object tuple = new Tuple4(1.0, 2.0, 3.0, 4.0);
return ((Tuple4)tuple).x;
}
vs
public static Object invokeStatic() {
final Object tuple = new Tuple4(1.0, 2.0, 3.0, 4.0);
return ((Tuple4)tuple).get(const__4);
}that's still higher overhead than an array I would think
user=> (aget (float-array [1.0 2.0 3.0]) 0)
1.0
or maybe there's something more direct than that?suggesting this tentatively because of Noah's suggestion of vector representation living below other stuff, and that's the lowest I think we can get
Yes, array is lowest representation, but field access is comparable I believe. Also aget is not translated directly to the my-array[0] but it calls some helpers functions in RT.
yeah that's the part I am iffy on too, the most intuitive things to look for are unchanged so as not to break older code, but not necessarily that best thing to use today
I mean - maybe my advice isnt super helpful 🙂... For me, I do a lot of vector stuff, so its nice to have a very concise vector library that is also flexible - I dont like to have to do a + 3 times because my vector happens to have 3 components, right? and then the same for * and min and max etc... I think also you'll see some power and expressiveness by setting aside the "differences" between points and colors - doing so for me helped me simplify a lot of the code I end up writing. All this being said, if you're just doing an exercise, maybe you dont need the most general, most expressive, 5-dimensional vector library out there 🙂
Ya, I feel arrays would be fastest. But records with primitive types I guess are nice because they know how to "print" themselves. They'd take up a lot more memory though I'd assume.
There's also deftype which would let you encapsulate the internal, so it could be an array inside and so on.
I fear I've permanently lost my "hammurabi" code (so named because if you make a mistake it will figuratively cut off your hand). hammurabi was an experiment in doing insane lowest-possible-overhead operations in the jvm, where instead of using classes and data types, it used raw byte representations in https://docs.oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html blocks, with conversion code to dump arrays in and get arrays back out. I never pushed it to github because it was would shamefully segfault the jvm if I had bugs in my code, but it did things very very fast when it worked.
I don't think it's a problem (memory) in this case. defrecord is actually a deftype implementing some set of additional interfaces/protocols to behave like a map.
@noisesmith Neanderthal uses byte buffers for vectors and matrices, and it delegates operations to BLAS and LAPACK (fortran libs) or to the GPU. https://neanderthal.uncomplicate.org/articles/tutorial_native.html Can't go much faster 🤷♀️
yeah - hammurabi was much more about me learning low level programming than it was about making something useful (I found higher order functions in clojure more useful for debugging than the sorts of things I could do with gdb)
neanderthal looks perfect for clojure ray tracing
@tsulej Fast math has a core.matrix implementation? Is it better than vectorz ?
I don't know tbh. I've never compared that I've made it once and never came back. I'm not sure if it's useful.
I'm using reitit and trying to create a static resource handler. I'm following the docs, but something is causing the route to hang when it grabs the file. I had this issue a long time ago, and I can't remember how I solved it. If I get everything loaded into the repl, I can see that the response body is a java.io.File which seems not quite right, but I'm not sure. I can slurp the body and get the right contents. Anybody know what might be causing it to hang?
This is how I've been poking around at the results to know what's going on
["/assets/*" {:get (fn [req]
(let [resp ((ring/create-resource-handler) req)]
(def req req)
(def resp resp)
resp))}]I should also mention, I'm using Aleph as my http server.
This works for me :
(ns test
(:require [aleph.http :as http]
[reitit.ring :as ring]))
(def resource-handler (ring/create-resource-handler))
(def router
(ring/ring-handler
(ring/router
[["/assets/*" {:get {:handler resource-handler}}]])))
(http/start-server #'router {:port 1235})
assuming you have a public folder on the classpathHi, I'm practicing clojure backend with #replicant frontend, and want to avoid the usual routing, is there a common pattern in clojure for handling backend requests via a single route and dispatching based on :action in the request body like a data-driven RPC style?
Also, is there any strong reason to use multimethods over a simple map of functions approach? (I like the clarity of the map version since it’s pure data and easy to reason about, especially when actions come from a client like a Replicant frontend.)
Any pointers, examples or articles?
Multimethods shine when you want to enable "other people" to extend the functionality. If you're the only one implementing the functions then map-of-fns is fine IMO.
I've seen the "single route, dispatch on a key in the request" approach used but I wouldn't say it's widespread. In the end it's just a different place to do dispatch.
I prefer hashmaps because of the declarative approach and ability to describe behavior in a configuration file. In my experience, they are also easier to debug. As for the third-party extensions, providing the ability to inject the configuration works fine.
I use https://github.com/practicalli/gameboard-donut/blob/main/src/practicalli/gameboard/router.clj and its very simple to reason about. I also use hash-maps with donut to define system components to great effect
as long as you use vars in your hashmap, it's a perfectly fine way to write a dispatch table
Thanks 🙂 everyone, I'll stick with hash maps for now, but I’ll also explore multimethods to understand them better.
Any recommendations for tools to help refactor ns bar.baz to foo.bar.baz?
tools.namespace has this https://github.com/clojure/tools.namespace/blob/master/src/main/clojure/clojure/tools/namespace/move.clj#L86 which I have used in the past, but it has been a while. A more modern/more complete approach would likely be something based on clj-kondo's analysis, but I don't know of something that uses that off hand
maybe I'm overly tolerant of tedium, but I've found that I can iterate on moving defintions and calling (require 'some.ns :reload) until I stop getting errors for missing definitions, with occasional repl restarts. this hasn't felt like a big enough chore to require a special tool
oh, and remove-ns so I don't have straggling definitions, almost forgot that part
best done in user so you are never sitting in a removed ns
you can have one line like (doseq [n '[foo.bar foo.baz foo.quux]] (doto n remove-ns require)) and just up-arrow and re-execute
Thanks, @hiredman. That is indeed the kind of solution I was hoping for. Unfortunate that it doesn't exist (yet 😉). I was hoping clojure-lsp rename --from bar.baz --to foo.bar.baz would do the trick, but that doesn't appear to work on namespaces.
@noisesmith I find this kind of tedium meditative myself, but when it's on the order of 50 namespaces across almost 1k files, it's becomes infeasible to do manually.
You might get pretty decent results (or first pass) if you vibe code it with some agentic AI
That is my current MO, yes, but it's rather slow, expensive and still quite tedious. I'll adapt my prompt approach...
What has worked for me is simpler. So it feels I might be missing something:
• Search and replace bar.baz to foo.bar.baz everywhere
• Leaving the definition of foo.bar.baz in the wrong directory
• Then move bar.baz.clj to where it need to be.
The latter is an autofix in Cursive. That's probably what I'm missing?
In Cursive, at least in typical cases, you don't have to do anything beyond hitting "Rename" on the ns symbol.
What is problematic is that fully qualified keywords are not renamed, and keywords with aliases as namespaces are also not touched. And it's not something that can be blindly automated because whether to replace :bar.baz/x with :foo.bar.baz/x depends on the actual usage. It could even be that half of them need replacing and half of them don't. So you end up searching the whole code base anyway and checking each keyword.