Fork me on GitHub

Let’s say I have two actors (loosely defined: just individual pieces of code, running async with regards to each other) whose job it is to take a value, assign it a key, and store it locally (to each actor) in a map. It’s unimportant what the key is (but we’re interested in avoiding collisions). They have read access to each others’ maps, but write access to their own map only. They would like to do their work with minimal coordination. Is it possible to get the two actors to reliably assign the same key for the same value such that when a value is present in both of them, they have identical keys? (This is a thought experiment)


You can either have a coordinated storage where you explicitly store a value-key map that both actors use and update, or you can have a function that always generates the same key if given the same value. I can't think of any other alternative.


Yeah, the first would be useful if the storage is faster at processing values than the actors (they could then saturate the storage). If it’s not fast, you might just as well just use one actor. Hashing the value seems straightforward, but you’d have to deal explicitly with collisions I guess. A third that occurred to me (although it’s probably a bad idea in practice) is to coordinate when a value is new, but not when it’s known. I.e., when a value that’s not present in either map appears, they have to access some shared resource and stop the other from trying to write the same value at the same time. Essentially a kind of mutex, I guess.

Alex Miller (Clojure team)13:06:52

is this not reinventing CRDTs?

👍 6

Maybe it is; I’m filling in gaps in my CS knowledge.


I’m guessing I should read up on and not Central Regional Dental Testing Service.

👍 4
😃 4

the other technique that’s usually contrasted with CRDTs is Operational Transforms, but CRDTs seems much easier to work with

Drew Verlee13:06:03

What is the word for something that handles dependencies?


"tooling", "wiring", "technical baggage", "boilerplate"


tools.namespace uses "tracker"



Alex Miller (Clojure team)13:06:12

what kind of "handling"?

Drew Verlee13:06:06

I suppose it would be enough here to express that it holds a set of things and tracks their dependencies. I think trying to go for one word here probably isnt helpful 🙂


it's time to get a channel for "the names of things"


Finally a channel I might be useful in..


But what shall we name it kappa?








I typed in "something that handles dependencies" and got "InitializerProxyManagementInitializerServlet"


so, there's our answer!

😂 8

There seem to be a surprising shortcoming in Clojure routing libraries. They either do not support reverse routing, or claim to support it but make near impossible to use because they require the routes map which cause circular dependency.


can’t the router add any necessary info for parameterized hrefs to the request? doing so would allow views to be pure functions of the request


That's what yada/bidi do


It's an orthogonal problem


You can also use a layer of indirection by using a keyword to identify routes instead of routing by value.


> That's what yada/bidi do Can you please elaborate? The only thing pertaining to the URL I see is the uri and the query string


> You can also use a layer of indirection by using a keyword to identify routes instead of routing by value. That doesn't seem to be possible without some twisted promise/deliver:


I still don’t understand why views must require the routes. seems like any of the circular dependencies can be handled with middleware, which live in the routes file


@U7RJTCH6J bidi reverse route function requires the routes map: (path-for route :index)


can’t middleware at the route information to the request to make it available for the view?


I'll try that and report back 🙂

👍 3

@U7RJTCH6J worked like a charm 🙏 - I was under the assumption that request middleware are for associating headers only

parrot 4
bananadance 4

middleware can be a bit tricky, but it’s a fairly powerful, flexible tool


@UAX4V32SZ patterns have nothing to do with what I'm talking about. If you have a route map in bidi, you can use keywords as handlers. Then you can match a route to the keyword, then pick which handler to call based on that. When handlers want to generate a url, they can find it by looking for the keyword.


@U09LZR36F I was just trying this without success.

(ns myproj.handler
  (:require [bidi.ring :refer (make-handler)]
            [ :refer [home-handler not-found-handler]]
            [myproj.account.sign-in :refer [handler] :rename {handler sign-in-handler}]))

(defn make-handlers []
  {;; Site
   :home      home-handler

   ;; Account
   :sign-in   sign-in-handler

   ;; Other
   :not-found not-found-handler})

(defn make-routes [handlers]
  ["/" {;; Site
        ""                    (:home handlers)
        [[#"[a-z]{2}" :lang]] (:home handlers)

        ;; Account
        "account/"            {"sign-in" (:sign-in handlers)}}])

(def routes
  (make-routes (make-handlers)))

(def handler
  (make-handler routes))


(path-for routes :sign-in)
is not working but passing the actual function works


@U09LZR36F (prn) the context/request explains the problem but I am not sure how to solve it: {:routes {:home #object[$home_handler 0x24435620 ... the functions are resolved before reaching my views


Unless I must return the promise and deliver it in my views as explained in doc/


make-routes should use references to the keywords


It shouldn't find the handler itself


The promise thing also works, but it's really just a way to do circular dependencies


Fwiw, if you use bidi with yada, yada will give you functions like yada/uri-info to solve this problem.


I’m not familiar with bidi or yada, but I was thinking of just wrapping your routes with some middleware similar to the following:

(defn wrap-path-for [handler]
  (fn [request]
    (let [request-path-for (partial path-for routes)]
      (handler (assoc request :path-for request-path-for)))))


your handlers can then just grab path-for from the request:

(let [path-for (:path-for request)
      my-path (path-for :index)]


That's what yada does, yeah.


But if you hand roll it, you're designing a custom system and you have no docs, etc

👍 3

@U09LZR36F I am using Liberator never tried yada. Is it better?


never mind I found the comparison in the official docs


> make-routes should use references to the keywords Can you elaborate on this. I don't get it 😞


Your routes shouldn't have functions in


(def routes ["/" {;; Site
    ""                    :home

    ;; Account
    "account/"            {"sign-in" :sign-in}

    ;; Other
    true                  :not-found}])
How bidi will know which function to use for routes in such case?


Oh, via make-handler second argument...


Thanks for all the help @U09LZR36F & @U7RJTCH6J - I have the desired result now; routing via keywords

bananadance 3

That is why you pass it as an argument to your handlers


Thanks - I am doing that now by passing it to the middleware and destructure the request


Your code has a static structure, the way it is arranged on disk in files that declare namespaces, and that static structure builds the dynamic structure which is the program actually running


Clojure forbids circular dependencies in the static structure


If you run into that wall, something to consider is: should/could this be passed as argument at runtime(dynamic dependency) instead of depending on a global def(static/know at compile time)


This is one of the reasons I prefer component to mount, mount tries really hard to force the dynamic structure of your program to match the static structure of your code, which encourages depending on globals instead of passing arguments

👍 4

All of the frameworks for lifecycle management/dependency injection can be abused. Write your start/stop functions first, as if you don't know which framework you are going to work with and it doesn't matter which you choose. They become interchangeable and you make sure you don't end up with spaghetti dependencies


This isn't abuse, the core mechanism mount uses to structure dependencies is vars in namespaces in files, so mount dependencies are tied directly to the static structure of your code


That's true, but you don't have to relate to them as globals. If you wrote your application without the knowledge that your dependencies are global, then your only option would be to pass them to your start function as an argument. So in practice your lifecycle function isn't aware that it's receiving a global variable. It just knows its receiving data or a protocol and can work with them the same as if they were global or not


The mere fact that your routes are def'ed as global turns it into a cyclic dependency if you try to require the namespace they are def'ed in in a namespace that defines a handler, which you need if you want to use reverse routing features


Writing your start and stop functions generically is not going to fix that


Just speaking to component vs mount. Would need to see an example of reverse routing. I admittedly don't understand the problem well but I usually work with this approach and I haven't encountered a situation in which it didn't work out


I found the component/co-deps to be overengineering, I am after absolute simplicity


I would encourage you to take another look at component. At it's core it is very little. contains all the functionality of component in 30 lines of code, the difference between that and the actual component library is ease of use and better error messages


Thanks for the advice, I will take another look


> That's true, but you don't have to relate to them as globals. As soon as you have globals and the code is shared between workers there is a high chance someone comes along and uses the globals directly, for whatever reason. It's just like teaching a kid to not drink that bleach vs not buying bleach in the first place, where the latter options is a safe route to make sure your kid never ever drinks bleach.


If you really stretch your imagination, you can imagine writing map anew for every different function you are mapping instead of passing in a function, so like maybe there is a single namespace(M) full of different map functions. And you have another namespace(X) that defines some cool function (F) and you want it able to map it, so you make M require X and define a new mapping function in M that calls F on each item in a collection. Now F can't use M without it being a circular dependency.


Making map a higher order function both eliminates the code duplication of so many different map implementations, and turns the static dependency into a dynamic one