Cwp: Indentation-Based Syntax for Clojure
github.com2 pointsby geokon0 comments
:filename -> [readFileJPG] -> :image
:filename -> [readFilePNG] -> :image
(I'm going to use this much simpler example b/c it's sufficient to illustrate the problems) :filenameJPG -> [readFileJPG] -> :image
:filenamePNG -> [readFilePNG] -> :image
.. or the consumer has to decide: :filename -> [readFileJPG] -> :imageJPG
:filename -> [readFilePNG] -> :imagePNG
I think this is the part where you really have to think hard about who knows which branch should be taken. Who knows you need a fat db request or a skinny one. Is that the part providing the :id and whatnot, or is it the one receiving the result of the db query? ::jpg/filename -> [jpg/readFile] -> ::jpg/imageJPG
::png/filename -> [png/readFile] -> ::png/imagePNG
Now you can bridge keys either on one side or the other, effectively re-creating either a provider or consumer driver flow. But you can't bridge both sides, cus then you're back to the original problem ::filename + :is-jpg -> [jpg/readFile] -> :image
::filename + :is-png -> [png/readFile] -> :image
- let the consumer/downstream decide: nested outputs {:jpg [:image]} ::filename -> [jpg/readFile] -> {:jpg [:image]}
::filename -> [png/readFile] -> {:png [:image]}
Both options leave a bad taste the mouth. {:profile-pic [:filename]} => {:profile-pic [:image]}
{:background-pic [:filename]} => {:background-pic [:image]}
You can of course create mini-contexts arbitrarily if you want. The downside of these nested mini-contexts is that they are aggressively isolated and completely oblivious of the larger world, so you need to shove in all the keys you may use. If you have a resolver somewhere else that processes the image: :image + :username -> [addUsernameOverlay] -> :image-with-username
Then you'll need to jam all the necessary keys in to the mini-context: {:profile-pic [:filename :username]} => {:profile-pic [:image-with-username]}
5. ::filename -> [jpg/readFile] -> {:jpg [:image]}
You're a bit screwed. You've lost the natural extensibility of Pathom. You now CANNOT do ::filename + ::username => :image-with-username [XXXX - doesn't work!]
Nor can you do ::filename + ::username => {:jpg [:image-with-username]}
One hack is to have jpg/readFile pack the :jpg container (ie. forward the :username into it) ::filename + :username -> [jpg/readFile] -> {:jpg [:image + :username]}
This makes the second call work, but this is a bit gross.. B/c now the jpg/readFile resolver requires a :username that it actually doesn't use. The resolver is now coupled to downstream requirements. Eww {:jpg [:filename]} -> [jpg/readfile] -> {:jpg [:image]}
Now the jpg/readfile resolver doesn't take a :filename, but instead takes a :jpg as {:jpg [:filename]} and unpacks it. Here.. I think technically only consumer must know the type.. But now you can add the username transparently and this transformation works without jpg/readfile needing to forward anything. {:jpg [:filename + :username]} -> {:jpg [:image-with-username]}
The downside is that you may end up having to shove in a lot of variables in to the mini-context. If it's all using resolvers from a library/ns that are kinda doing an isolated set of stuff then this can be very viable though! (pco/defresolver get-user-with-customer
[{:users/keys [id]}]
{::pco/output [{:fat-pack [:users/id
:users/email
:users/customer_id
:customers/id
:customers/billing_number
:customers/phone_number]}]}
(println "get-user-with-customer FAT triggered")
{:fat-pack {:users/id 66
:users/email "[email protected]"
:users/customer_id id
:customers/id id
:customers/billing_number 666
:customers/phone_number 6666}})
(def env
(pci/register [get-user
get-customer
get-user-with-customer
user-customer-bridge]))
(p.eql/process env
{:users/id 1000}
[{:fat-pack [:customers/phone_number
:users/email]}])
;;{:fat-pack {:customers/phone_number 6666, :users/email "[email protected]"}}
I think you can see you get the same results, but there is no way for it to go wrong. Admittedly here I have the EQL request "unpacking" it, but it can also be unpacked transparently from a different resolver using nested inputs. (pco/defresolver fat-eater
[{:keys [fat-pack]}]
{::pco/input[{:fat-pack [:users/id
:users/email
:users/customer_id
:customers/id
:customers/billing_number
:customers/phone_number]}]
::pco/output [:response]}
(println "fat-eater triggered")
{:response (str "yum, just ate: "
(:users/id fat-pack))})
(def env
(pci/register [get-user
get-customer
get-user-with-customer
user-customer-bridge
fat-eater]))
(p.eql/process env
{:users/id 1000}
[:response])
;; {:response "yum, just ate: 66"}
This is very explicit and about equivalent to your original thought of "why not just have explicit imperative function calls". The consumer (the `fat-eater` or the user making the EQL request) knows a priori that he wants the fat call. (defn my-function
[{:keys! [username]
:keys [firstname
lastname]}]
(do-stuff username
firstname
lastname))
A minor downside is that now it seems `nil` is even more overloaded b/c you can explicitly pass in a nil and give it a special meaning. This generally cascades in to messyness (better to have a special key like `:missing-username`).
Usually these kinds of systems either don't scale dynamically or have caching issues. The first example, a spreadsheet, is "easy" because there are a fixed amount of cells to track. A GUI can be a lot harder (imagine sub windows and sub-sub windows dynamically popping up and tracking some redundant and some unique "computations". Entities can appear and then be removed at random). Though the wording carefully says "constructing views" so maybe it doesn't handle dynamism