Before I create Asks for either of these -- am I the only person who has written either of these fns/macros?
• interleave-all -- like partition-all is to partition, this stops when the longest sequence is exhausted, not when the shortest sequence is exhausted
• condp-> & condp->> -- like cond-> and cond->> but they thread the expression through the predicates as well as through the expressions
You can see the implementations here https://github.com/worldsingles/commons/blob/master/src/ws/clojure/extensions.clj and they're the only two things we use from this library at this point, so I'd like to retire the library. Having that fn and those two macros in core would be my preference (of course) but if no one has ever felt the need, that would be pointless.
I think that's rather non-idiomatic for Clojure. I can imagine it more in Common Lisp, I guess...
Maybe not very idiomatic. But so many nested if-constructs are also not nice.
when I have code like this, I try to drive conditionality into the steps and turn the main code back into a simple pipeline
I've also seen a handy macro floating around a few times for flattening this sort of thing: https://clojurians.slack.com/archives/C03S1KBA2/p1718287615736349?thread_ts=1718286671.849899&cid=C03S1KBA2
For context, I think we have just two uses of interleave-all and ten of condp-> (and none of condp->> -- that was added purely for symmetry in the library) -- in 150K lines of Clojure.
you've already asked for https://ask.clojure.org/index.php/12125/add-interleave-all-like-partition-all-is-to-partition
and there is an ask for condp-> https://ask.clojure.org/index.php/2365/adding-condp-and-condp-macros-to-core-library
Oh... and interleave-all got 14 upvotes and has a Jira. And interesting that it wasn't me who requested condp-> / condp->>! Thanks, @alexmiller
I have this version of interleave-all in one of my codebases (now that I look at it, it looks buggy...):
(defn interleave-all
[x y]
(into (vec (interleave x y)) (drop (count y) x)))@dj942 We have to do this across an arbitrary number of sequences, hence the generalized c1 c2 & colls arity.
The condp-> in that old Ask isn't quite the same, as it just invokes the pred on the expression rather than threading, so I'll add a note about that (and maybe add a comment to the Jira too).
https://github.com/weavejester/medley/blob/822981871facb27630dcba03cce2924a34989963/src/medley/core.cljc#L347-L364 exists in medley, which i take as a good sign for its general usage
and plumatic/plumbing and taoensso/encore. github gives me roughly 225 uses of interleave-all in a call-position
I do recall pulling interleave-all into several projects over the years. Copy and pasted it from medley. Just added an upvote on Ask.
I've occasionally written condp-> for one-offs but I've always wanted the predicate behavior to be opt-in per condition, kind of like how condp uses :>> to opt into calling a fn on the predicate result. Most of my condp->s have a majority of threaded predicates mixed in with a minority of constant predicates, and it's annoying to have to wrap those in ((constantly (the-actual-predicate ...))). I haven't spent enough hammock time on it to have a good idea of how it'd actually work though.
What about an extension to let instead of condp ? https://ask.clojure.org/index.php/14999/controlled-bailout-jira-issue-2213
Finally made a workable enough prototype of the query interface i've always wanted
So this:
(jsonquery/execute! (user/db)
{:select [:name
:version
:target_platform
:mandated
:synthetic
[:requires {:select [:module :version :static :transitive :mandated :synthetic]
:from :repository.module_requires
:join-on [:id :module_id]}]
[:exports {:select [:package :to :mandated :synthetic]
:from :repository.module_exports
:join-on [:id :module_id]}]
[:uses {:select [:service]
:from :repository.module_uses
:join-on [:id :module_id]}]
[:provides {:select [:service :with]
:from :repository.module_provides
:join-on [:id :module_id]}]
[:packages {:select [:package]
:from :repository.module_package
:join-on [:id :module_id]}]
[:hashes {:select [:module :algorithm :hash]
:from :repository.module_hash
:join-on [:id :module_id]}]]
:from :repository.module
:order-by [[:repository.module.name :asc]
[:repository.module.version :desc]]
:limit 5})
Will turn into a query like this
{:select [[[:json_build_object
[:raw "'name'"]
:repository.module.name
[:raw "'version'"]
:repository.module.version
[:raw "'target_platform'"]
:repository.module.target_platform
[:raw "'mandated'"]
:repository.module.mandated
[:raw "'synthetic'"]
:repository.module.synthetic
[:raw "'requires'"]
[[:array
{:select [[[:json_build_object
[:raw "'module'"]
:repository.module_requires.module
[:raw "'version'"]
:repository.module_requires.version
[:raw "'static'"]
:repository.module_requires.static
[:raw "'transitive'"]
:repository.module_requires.transitive
[:raw "'mandated'"]
:repository.module_requires.mandated
[:raw "'synthetic'"]
:repository.module_requires.synthetic]]],
:from :repository.module_requires,
:where [[:= :repository.module.id :repository.module_requires.module_id]]}]]
[:raw "'exports'"]
[[:array
{:select [[[:json_build_object
[:raw "'package'"]
:repository.module_exports.package
[:raw "'to'"]
:
[:raw "'mandated'"]
:repository.module_exports.mandated
[:raw "'synthetic'"]
:repository.module_exports.synthetic]]],
:from :repository.module_exports,
:where [[:= :repository.module.id :repository.module_exports.module_id]]}]]
[:raw "'uses'"]
[[:array
{:select [[[:json_build_object [:raw "'service'"] :repository.module_uses.service]]],
:from :repository.module_uses,
:where [[:= :repository.module.id :repository.module_uses.module_id]]}]]
[:raw "'provides'"]
[[:array
{:select [[[:json_build_object
[:raw "'service'"]
:repository.module_provides.service
[:raw "'with'"]
:repository.module_provides.with]]],
:from :repository.module_provides,
:where [[:= :repository.module.id :repository.module_provides.module_id]]}]]
[:raw "'packages'"]
[[:array
{:select [[[:json_build_object [:raw "'package'"] :repository.module_package.package]]],
:from :repository.module_package,
:where [[:= :repository.module.id :repository.module_package.module_id]]}]]
[:raw "'hashes'"]
[[:array
{:select [[[:json_build_object
[:raw "'module'"]
:repository.module_hash.module
[:raw "'algorithm'"]
:repository.module_hash.algorithm
[:raw "'hash'"]
:repository.module_hash.hash]]],
:from :repository.module_hash,
:where [[:= :repository.module.id :repository.module_hash.module_id]]}]]]]],
:from :repository.module,
:limit 5,
:order-by [[:repository.module.name :asc] [:repository.module.version :desc]]} SELECT
JSON_BUILD_OBJECT(
'name',
"repository"."module"."name",
'version',
"repository"."module"."version",
'target_platform',
"repository"."module"."target_platform",
'mandated',
"repository"."module"."mandated",
'synthetic',
"repository"."module"."synthetic",
'requires',
(
ARRAY(
SELECT
JSON_BUILD_OBJECT(
'module', "repository"."module_requires"."module",
'version', "repository"."module_requires"."version",
'static', "repository"."module_requires"."static",
'transitive', "repository"."module_requires"."transitive",
'mandated', "repository"."module_requires"."mandated",
'synthetic', "repository"."module_requires"."synthetic"
)
FROM
"repository"."module_requires"
WHERE
(
"repository"."module"."id" = "repository"."module_requires"."module_id"
)
)
),
'exports',
(
ARRAY(
SELECT
JSON_BUILD_OBJECT(
'package', "repository"."module_exports"."package",
'to', "repository"."module_exports"."to",
'mandated', "repository"."module_exports"."mandated",
'synthetic', "repository"."module_exports"."synthetic"
)
FROM
"repository"."module_exports"
WHERE
(
"repository"."module"."id" = "repository"."module_exports"."module_id"
)
)
),
'uses',
(
ARRAY(
SELECT
JSON_BUILD_OBJECT(
'service', "repository"."module_uses"."service"
)
FROM
"repository"."module_uses"
WHERE
(
"repository"."module"."id" = "repository"."module_uses"."module_id"
)
)
),
'provides',
(
ARRAY(
SELECT
JSON_BUILD_OBJECT(
'service', "repository"."module_provides"."service",
'with', "repository"."module_provides"."with"
)
FROM
"repository"."module_provides"
WHERE
(
"repository"."module"."id" = "repository"."module_provides"."module_id"
)
)
),
'packages',
(
ARRAY(
SELECT
JSON_BUILD_OBJECT(
'package', "repository"."module_package"."package"
)
FROM
"repository"."module_package"
WHERE
(
"repository"."module"."id" = "repository"."module_package"."module_id"
)
)
),
'hashes',
(
ARRAY(
SELECT
JSON_BUILD_OBJECT(
'module', "repository"."module_hash"."module",
'algorithm', "repository"."module_hash"."algorithm",
'hash', "repository"."module_hash"."hash"
)
FROM
"repository"."module_hash"
WHERE
(
"repository"."module"."id" = "repository"."module_hash"."module_id"
)
)
)
)
FROM
"repository"."module"
ORDER BY
"repository"."module"."name" ASC,
"repository"."module"."version" DESC
LIMIT
? 5
which in turns gives results like this
[{:mandated false,
:name "be.yildizgames.common.properties",
:target_platform "universal",
:exports [{:package "be.yildizgames.common.properties", :to nil, :mandated false, :synthetic false}],
:requires [{:module "java.base", :version nil, :static false, :transitive false, :mandated true, :synthetic false}],
:uses [],
:packages [{:package "be.yildizgames.common.properties"}],
:provides [],
:version "1.0.0",
:synthetic false,
:hashes []}
{:mandated false,
:name "com.azure.digitaltwins.core",
:target_platform "universal",
:exports [{:package "com.azure.digitaltwins.core", :to nil, :mandated false, :synthetic false}
{:package "com.azure.digitaltwins.core.models", :to nil, :mandated false, :synthetic false}],
:requires [{:module "com.azure.core",
:version "1.55.4",
:static false,
:transitive true,
:mandated false,
:synthetic false}
{:module "java.base", :version nil, :static false, :transitive false, :mandated true, :synthetic false}],
:uses [],
:packages [{:package "com.azure.digitaltwins.core"}
{:package "com.azure.digitaltwins.core.implementation"}
{:package "com.azure.digitaltwins.core.implementation.converters"}
{:package "com.azure.digitaltwins.core.implementation.models"}
{:package "com.azure.digitaltwins.core.implementation.serializer"}
{:package "com.azure.digitaltwins.core.models"}],
:provides [],
:version "1.5.0",
:synthetic false,
:hashes []}
{:mandated false,
:name "com.azure.resourcemanager.hybridkubernetes",
:target_platform "universal",
:exports [{:package "com.azure.resourcemanager.hybridkubernetes", :to nil, :mandated false, :synthetic false}
{:package "com.azure.resourcemanager.hybridkubernetes.fluent", :to nil, :mandated false, :synthetic false}
{:package "com.azure.resourcemanager.hybridkubernetes.fluent.models",
:to nil,
:mandated false,
:synthetic false}
{:package "com.azure.resourcemanager.hybridkubernetes.models", :to nil, :mandated false, :synthetic false}],
:requires [{:module "com.azure.core.management",
:version "1.17.0",
:static false,
:transitive true,
:mandated false,
:synthetic false}
{:module "java.base", :version nil, :static false, :transitive false, :mandated true, :synthetic false}],
:uses [],
:packages [{:package "com.azure.resourcemanager.hybridkubernetes"}
{:package "com.azure.resourcemanager.hybridkubernetes.fluent"}
{:package "com.azure.resourcemanager.hybridkubernetes.fluent.models"}
{:package "com.azure.resourcemanager.hybridkubernetes.implementation"}
{:package "com.azure.resourcemanager.hybridkubernetes.models"}],
:provides [],
:version "1.1.0-beta.1",
:synthetic false,
:hashes []}
{:mandated false,
:name "com.azure.resourcemanager.kubernetesconfiguration.privatelinkscopes",
:target_platform "universal",
:exports [{:package "com.azure.resourcemanager.kubernetesconfiguration.privatelinkscopes",
:to nil,
:mandated false,
:synthetic false}
{:package "com.azure.resourcemanager.kubernetesconfiguration.privatelinkscopes.fluent",
:to nil,
:mandated false,
:synthetic false}
{:package "com.azure.resourcemanager.kubernetesconfiguration.privatelinkscopes.fluent.models",
:to nil,
:mandated false,
:synthetic false}
{:package "com.azure.resourcemanager.kubernetesconfiguration.privatelinkscopes.models",
:to nil,
:mandated false,
:synthetic false}],
:requires [{:module "com.azure.core.management",
:version "1.18.0",
:static false,
:transitive true,
:mandated false,
:synthetic false}
{:module "java.base", :version nil, :static false, :transitive false, :mandated true, :synthetic false}],
:uses [],
:packages [{:package "com.azure.resourcemanager.kubernetesconfiguration.privatelinkscopes"}
{:package "com.azure.resourcemanager.kubernetesconfiguration.privatelinkscopes.fluent"}
{:package "com.azure.resourcemanager.kubernetesconfiguration.privatelinkscopes.fluent.models"}
{:package "com.azure.resourcemanager.kubernetesconfiguration.privatelinkscopes.implementation"}
{:package "com.azure.resourcemanager.kubernetesconfiguration.privatelinkscopes.models"}],
:provides [],
:version "1.0.0-beta.1",
:synthetic false,
:hashes []}
{:mandated false,
:name "com.azure.resourcemanager.powerbidedicated",
:target_platform "universal",
:exports [{:package "com.azure.resourcemanager.powerbidedicated", :to nil, :mandated false, :synthetic false}
{:package "com.azure.resourcemanager.powerbidedicated.fluent", :to nil, :mandated false, :synthetic false}
{:package "com.azure.resourcemanager.powerbidedicated.fluent.models",
:to nil,
:mandated false,
:synthetic false}
{:package "com.azure.resourcemanager.powerbidedicated.models", :to nil, :mandated false, :synthetic false}],
:requires [{:module "com.azure.core.management",
:version "1.15.6",
:static false,
:transitive true,
:mandated false,
:synthetic false}
{:module "java.base", :version nil, :static false, :transitive false, :mandated true, :synthetic false}],
:uses [],
:packages [{:package "com.azure.resourcemanager.powerbidedicated"}
{:package "com.azure.resourcemanager.powerbidedicated.fluent"}
{:package "com.azure.resourcemanager.powerbidedicated.fluent.models"}
{:package "com.azure.resourcemanager.powerbidedicated.implementation"}
{:package "com.azure.resourcemanager.powerbidedicated.models"}],
:provides [],
:version "1.0.0",
:synthetic false,
:hashes []}i know, huge chunks of text
but: notice how I got all the data I wanted from the db in a single round trip
no N+1 issues in sight
(ns dev.mccue.jsonquery
(:require [cheshire.core :as cheshire]
[honey.sql :as sql]
[next.jdbc :as jdbc])
(:refer-clojure :exclude [format]))
(defn- ->honeysql-helper
[query parent-table]
(let [table (:from query)
conditions (filterv some?
[(when parent-table
(when-not (:join-on query)
(throw (IllegalArgumentException. (str
"Missing :join-on for "
parent-table " and " table))))
[:=
(keyword (str (name parent-table)
"."
(name (first (:join-on query)))))
(keyword (str (name table)
"."
(name (second (:join-on query)))))])
(:where query)])]
(cond->
{:select [[(vec
(cons :json_build_object
(mapcat
(fn [selection]
(if (keyword? selection)
[[:raw (str "'" (name selection) "'")]
(keyword (str (name table) "." (name selection)))]
(if (:single (meta selection))
[[:raw (str "'" (name (first selection)) "'")]
(->honeysql-helper (second selection) table)]
[[:raw (str "'" (name (first selection)) "'")]
[[:array (->honeysql-helper (second selection) table)]]])))
(:select query))))]]
:from table}
(seq conditions) (assoc :where conditions)
(:limit query) (assoc :limit (:limit query))
(:order-by query) (assoc :order-by (:order-by query)))))
(defn ->honeysql
[query]
(->honeysql-helper query nil))
(defn format
[query]
(sql/format query {:quoted true}))
(defn execute!
[db query]
(mapv (comp #(cheshire/parse-string % keyword) str :json_build_object)
(jdbc/execute!
db
(format
(->honeysql query)))))that's really cool!