(defn loop-over-sets
[expr kb]
(filter #(and (= (first %) 'if) (= (second %) expr)) kb)
)
Im getting a dont know how to create from ISEQ error when my expr = 'a and when my kb = '(if a b)does anyone know why?
this works when i have kb = '((if a b)(if b c))
Having just '(if a b)
as kb
doesn't sound right based on what you've described: that's a sequence of just if
, a
, and b
. I think you want kb
to be '((if a b))
i.e., a sequence of expressions.
(and the reason for the error is that filter
will run that predicate over if
, a
, and b
and the predicate calls first
and second
which aren't valid on symbols (but are valid on sequences).
^ @ntrut
hmmm is there a way to change '(if a b) to '((if a b)) in code?
I think you should probably rethink how you're calling that function
are you trying to loop over a list of expressions and return the results for each?
its because kb can be both
yeah so, What i want to happen is, if kb is ((if a b)(if b c)) and expr is 'a. It would look for "if a" and then it would derive b. And if kb is just '(if a b), then it would still derive b
I think if i do if statement saying if first = 'if, then derive the 2nd nth else use that filer function
it would work
filter*
don't know if there's a better way to do it, but you can (if (list? (first kb)) kb (list kb))
I guess
It's not good practice to have a data structure that can be different shapes like that. kb
should always be a list of expressions, even if it has just one expression in it.
See https://stuartsierra.com/2015/06/10/clojure-donts-heisenparameter
(that whole series of do's and don'ts is worth reading)
I understand what im doing wrong but how do i make sure that kb is '((if a b)) and not '(if a b) when being passed in
You should solve that problem at the source of where kb
is being created -- and ensure it is always a sequence of expressions and never a single expression.
is there a way to change kb to '((if a b))
?
In other words, your "loop over sets" (terrible name 🙂 ) should always just assume it is passed a sequence of expressions.
It should not be trying to accommodate someone calling it with invalid data.
How/where is kb
created? That's where you should fix the problem.
(defn fwd-infer
[prob kb]
(cond
(= (first prob) 'and) (fwd-infer (first (and-elimination prob)) #(and-elimination prob))
(= (first prob) 'not) (fwd-infer (elmination-not prob) #(elmination-not prob))
(= (first prob) 'if) (fwd-infer (modus-ponens (nth prob 1) kb) #(modus-ponens (nth prob 1) kb))
)
)
This is where kb is createdIt is just passed through my infer function
so it's the and
case that might return just one element
but you're not creating kb there
you're just passing it
For my homework, we just simply use the REPL to pass our data in
so it is a requirement that it accepts both '(if a b) and '((if a b) (if c b))?
i'd say just transform the data when you read it
if the first form is not a list, wrap everything in a list
There is maybe a better way to do this but this is the only way i could think of doing it
Ok ill try that
or you add an 'or and do '(if (or a c) b) 😛
how would i wrap everything in a list
(list x)
i gave you the code earlier
hello, I have a lein project i’m trying to add shadow-cljs to, first time using it (I’m working from the book Web Dev with Clojure) I have, src/clj
and src/cljs
folders each with a guestbook.core
namespace. Could this be causing an issue? I am getting this error trying to start up shadow-cljs, I’ve looked over my project.clj and shadow-cljs.edn a few times and nothing’s jumping out at me as incorrect…
[:app] Configuring build.
[:app] Compiling ...
[:app] Build failure:
The required namespace "guestbook.core" is not available, it was required by "guestbook/app.cljs".
"guestbook/core.clj" was found on the classpath. Should this be a .cljs file?
Your <http://guestbook.app|guestbook.app>
namespace is requiring guestbook.core
-- but <http://guestbook.app|guestbook.app>
is ClojureScript and guestbook.core
is Clojure, based on the extensions.
If both src/clj
and src/cljs
are on your classpath, you can't have the same named file in two places -- only one of them will be found.
So it's finding src/clj/guestbook/core.clj
ClojureScript apps do tend to have .clj
files in them for macros (since ClojureScript relies on Clojure for macro definition and expansion, prior to compilation -- to JavaScript).
what is the best way to redefine a binding in an outer scope, for ex:
(let [x 1]
(do-something-to-x-to-make-it 2)
(+ x 1))
You cannot
thanks. just tried renaming the cljs file and ns, but it’s still not finding it..must be something else im not seeing
what could be the approach? I'm traversing a tree and need to maintain some state
Local bindings (established via function application or let) are immutable
Look at tree traversals in functional languages
General you use recursive function calls
That style is what works in clojure, but sometimes you use loop/recur in place of recursive function calls, but it will have the same shape as a recursive function call style
i'm traversing the tree alright
an example of tree traversal using an atom: https://github.com/clojure/clojurescript/blob/master/src/main/clojure/cljs/util.cljc#L241
the problem is i want to pass some state between sibling branches
I know about atom, thought it was just used for concurrency stuff
its a threadsafe mutable container.
https://mitpress.mit.edu/sites/default/files/sicp/full-text/book/book-Z-H-11.html#%_sec_1.2
You can return information from traversals and pass it to sibling traversals
Are you getting that same error, or a different one?
yeah, that's what i was thinking
http://sarabander.github.io/sicp/html/1_002e2.xhtml#g_t1_002e2_002e1 (i always link to this nicely formatted version)
in this case i'm traversing the tree using a multimethod
so i should wrap it in another function that deals with the state
and call the multimethod inside that
https://github.com/hiredman/qwerty/blob/master/expand.clj#L313 is a rather complicated function (a macro expander for another programming language) that walks a tree (expressions in the language) and passes information down and up and around
The error above is that it is finding guestbook.core
but it's expecting a .cljc
or .cljs
extension on it.
@hiredman that's more or less what I'm doing here
the "trick" to make sure your state goes into the siblings is to use a reduce to keep accumulating state between the siblings.
ok, my issue is that now I have to deal with state at every node when the majority of them don't care about it
oof…i was missing :source-paths [..., "src/cljs"]
in my project.clj …
build completed now with the original filenames maintained, there’s a core.clj and a core.cljs using the same ns in the project. Not sure if i’ll run into issues down the line…
i was using lein cljsbuild previously, and removed that config entirely which had its own source path prop
I think atom is what I want here
(defn loop-over-sets
[expr kb]
(filter #(and (= (first %) 'if) (= (second %) expr)) kb)
)
This gives me the correct output with (if a b)(if b c) as kb and b as expr but when i have kb = (if a b) a (if b c) and my expr = b, I want to get (if b c) but im getting a "Don't know how to create ISeq from: clojure.lang.Symbol"@joao.galrito Do you know if i could change this function so it can read both ((if a b)(if b c)) AND '((if a b) a (if b c))
what do you want to do with the 'a in the middle
ignore it?
yeah
because this function only looks for ifs
it should look for ifs
then you need to add another condition to your filter predicate
to check if what you're reading is a list
because right now you're trying to call (first 'a)
i see now
I figured it out
I think
sequential?
is probably the predicate you want here.
nvm i havent got it
haha this is hard
Problems only lead to more problems
@seancorfield any particular reason? If just working with lists
(filter #(and (sequential? %) (= (first %) 'if) (= (second %) expr)) kb)
(instead of list?
for example)
user=> (list? ())
true
user=> (list? (range 5))
false
user=> (list? (map inc (range 5)))
false
user=> (sequential? ())
true
user=> (sequential? (range 5))
true
user=> (sequential? (map inc (range 5)))
true
user=>
gotcha
if at any point it turns into a lazy list it breaks
list?
is very specific and if Nazar is building the kb
programmatically, he might get sequences that are not lists.
he said kb
is coming from the REPL
But he's processing it with fwd-infer
for the reductions?
but even then just using filter
s and map
s can result in a lazyseq
Yup. Test for the abstraction, not the concrete type.
Glad you got it working, and thank you for reporting back with the solution!
I am using following code to create an empty file.
(defn create-seed [name]
(with-open [wtr (io/writer (get-seed-file-name name))]
(.write wtr "")))
Is there a shorter version?I was hopping to have something as concise as touch afile.txt
@suren Using spit
...
@suren You get used to reading (Java) stack traces but they can be daunting at first.
Yeah too much noise in the stack traces.
Thanks I will try it.
(spit (get-seed-file-name name) "")
It's a built-in version of the code you posted:
user=> (source spit)
(defn spit
"Opposite of slurp. Opens f with writer, writes content, then
closes f. Options passed to <http://clojure.java.io/writer|clojure.java.io/writer>."
{:added "1.2"}
[f content & options]
(with-open [^java.io.Writer w (apply jio/writer f options)]
(.write w (str content))))
nil
user=>
Now I see the source, I guess you could even do (spit (get-seed-file-name name) nil)
which might be more explicit?
Since (str nil)
is ""
Thanks @seancorfield it worked like a charm
I wish we had a karma coin, so that I could give some to you. 🙂
(.createNewFile (io/file "bob"))
read javadocs for File and there's something that could help right away
Also, what do you want to happen if the file already exists?
Just error out.
(spit ... nil)
will overwrite it with an empty string; (spit ... nil :append true)
would append an empty string to an existing file (i.e., leave it unchanged apart from the mod date/time).
I suspect .createNewFile
would error out if it already exists
Ah, no, it would just return false
(and it returns true
if it successfully creates the file)
io/file
is very confusing.
I am using following code to list the files in a directory.
(->> (io/file "resources/seeds")
(.list))
As @dpsutton pointed out (.createNewFile (io/file "bob"))
creates a new file.
What exactly is (io/file 'path)'
?user=> (.createNewFile (<http://clojure.java.io/file|clojure.java.io/file> "/tmp/foo"))
true ; file did not exist, this created it
user=> (.createNewFile (<http://clojure.java.io/file|clojure.java.io/file> "/tmp/foo"))
false ; file existed so it was not touched
user=>
io/file
creates a <http://java.io|java.io>.File
object so you'll have to read the Java API docs for that.
But it's basically an abstraction for a file.
So it can be a file that doesn't exist, a directory, a file that does exist, or any implementation of something that can behave like a file.
hmm got it
For my use case I will use (.createNewFile
Java has dramatically improved its support for file ops in more recent versions, so now there's a java.nio.*
package hierarchy with abstractions for paths and files and entire file systems 🙂
Clojure pretty much just delegates to Java interop for file stuff.
Yeah I can see that. I think clojure's biggest weakness is its error messages.
Most of the time the relevant files are buried somewhere in the middle of stack trace.
Not sure if this is the best section to ask but for a serious web project would you go with ring/reitit or pedestal?
@decim both are definitely for serious projects. I would go with ring/reitit but it’s because I’m more familiar with it and my colleagues are more inclined to it. I’d still go through both documentations and decide which is more to your liking. Also reitit comes with swagger support out of the box but that functionality is easily available for pedestal as well so they’re not too far apart. I’m still very much a clojure beginner so somebody with actual production experience with both should chime in.
I would check the reitit faq for some explanation of the differences https://github.com/metosin/reitit/blob/master/doc/faq.md
Assuming a function has too take two arguments, but you want one of them to just not do anything, is just doing ()
an ok option? I only want to log something based on a if-dev
macro which I am unable to modify
actually I think nil
is better now that I think of it
Are there any clever clojure function to convert booleans to 0/1?
Maybe you want a default result:
(defn judge [x]
(get {true 1 false 0} x -1))
maps have the same interface as get)
({true 1 false 0} x -1)
also will give you default value
Thank you @delaguardo, I have read from somewhere that only get can append a default value.
like if
?
(if x 1 0)
Yea, but the other way around 🙂
Oh nvm 😄
Thank you
({true 1 false 0} x)
Why are
(apply * []) equal 1
(apply + []) equal 0
?
Has it anything to do with 1 and 0 being the identities of monoids with operators * and +?Yes
does doseq
not realize a lazy seq?
no
sounds like doall
?
so doall
/ dorun
+ map
for iterating over a seq for side effects?
thought that was what doseq
was for
I see there's also run!
doseq
does iterate over lazy sequences, but it doesn't return anything. Use it when you need the side effects, but don't care about the values!
I tried doseq
but nothing happened
if you want to iterate for side effects, I would recommend run!
What did you try? Here's an example:
user=> (doseq [x (range 5)] (prn x))
0
1
2
3
4
nil
(doseq [operations chunks]
(http/post
(str (env :es-host) "/_bulk")
{:body (str (string/join \newline operations) \newline)
:content-type :json})
(println "Inserted " (count operations)))
where chunks
is
(->> documents
(map #(str (json/write-str {:index {:_index collection-name
:_id (get % id-key)}})
\newline
(json/write-str %)))
(partition-all max-chunk-size))
oh, I think doseq
realizes the sequence before executing the body
anyway in my case I can take advantage of parallelism so ended up using pmap
and dorun
Hi all. I have a design question: I have this map object, which serves as the main "data model" I use in my app. I pass it around, modify it, bla bla. In addition, in order to make it easier to update it without having to break changes in the places that use this map, I ended up encapsulating it with constructor, update and getter functions. something like this:
; Constructor
(defn generate-my-model
[id]
{:id id
:field-a []
:field-b []})
; Updater / setter
(defn update-my-model
([id a b]
(assoc (generate-my-model id)
:field-a a
:field-b b)))
; Getters
(defn my-model->id [m] (:id m)
(defn my-model->a [m] (:a m)
(defn my-model->b [m] (:b m)
I emphasize: these "encapsulation" functions exist to make it easier to update my code later on without breaking things for outside users of my library. Otherwise, everywhere i had to read that map's fields i would have to use the field name explicitly. The getters and updater allows me to hide these implementation details from the outside world.
My question: Is there a better way to do this? I hate writing all these getters and updaters, it's just ugly boilerplate code. In OOP-land these things are a must due to the same reason (encapsulation). But in Clojure .. i dunno, i remember Rich Hickey saying it's ok to pass data maps around. I just don't like the cost of it. It will create a headache for me every time i want to change the implementation of this entity map. 😬
Wdyt?I'm not generally against having helper functions that provide a way to explicitly list what the common operations are, but I would warn against pretending that it "encapsulates" your data. I also wouldn't start with defining getters/setters and would only consider adding them on a case by case basis. In my experience, getters/setters provide little benefit for data at rest or being sent over the network which is the important part to get right for maintaining long running programs and systems To address "what keys?" and "what entity?", I would just use spec. As he mentions in the talk, it's meant to address these problems. The big difference between spec's approach and defining getters/setters is that spec is data focused and getters/setters are operations focused. The differences between a data specification and an operational specification are really critical. Most programs would be better served by focusing on the data than on the operations. Depending on your domain, it may be useful to use something like specter or getters/setters to help declutter deeply nested code. In my experience, there are many domains where an extra layer of abstraction is unnecessary. I really wish computer science would be renamed data-ology.
i think this is an interesting discussion. i don't have anything to add except to note that re-frame basically has getters and setters on a big map (`app-db`) in the form of subscriptions and events. for a while i really felt like i was back in oop land, writing a getter and setter for each piece of data in app-db
.
in that context, i do think there's something to be said for separating out the data from "exactly where in this huge nested map does it belong"
i've run into many snags trying to reorganize the structure of app-db
and unknowingly breaking code that depended on that exact configuration
I think if you're running into "what's the shape of the data supposed to be?", I would reach for spec or defining the data specification rather than getters/setters
it would be great to hear other's thoughts on the subject though
it depends on what you're trying to model so you may need to provide more context for a better answer. if you have getters that are simply just wrappers around specific keys, then they add a layer of indirection for little value. instead of getters/wrappers, it's may be worth creating protocols or multimethods that can provide an interface to your data model eg:
(defprotocol IShape
(area [this])
(perimeter [this]))
(defrecord Circle [r]
IShape
(area [this] (* Math/PI r r))
(perimeter [this] (* 2 Math/PI r)))
(defrecord Square [l]
IShape
(area [this] (* l l))
(perimeter [this] (* l 4)))
(area (->Circle 2))
(perimeter (->Square 4))
Thanks. Thing is, i don't really need polymorphism here. Just some sort of accessors to set and get fields while hiding the implementation.
protocols are the way to separate what from how (ie. hiding the implementation)
ok. what about accessors? the setters
how to set stuff.
Hello again
I'm playing with a deps.edn
project and I'm having some difficulty starting up my REPL properly
So, here's my deps.edn
file:
{:paths ["src"]
:deps {org.clojure/clojure {:mvn/version "1.10.1"}
com.datomic/datomic-free {:mvn/version "0.9.5697"}
com.datomic/client-pro {:mvn/version "0.9.63"}
org.postgresql/postgresql {:mvn/version "9.3-1102-jdbc41"}
http-kit {:mvn/version "2.4.0"}
metosin/reitit {:mvn/version "0.5.5"}}
:aliases {:server {:main-opts ["-m" "grok.core"]}
:dev {:extra-paths ["config/dev" "env/dev"]}
:test {:extra-paths ["test" "config/test"]
:extra-deps {lambdaisland/kaocha {:mvn/version "0.0-529"}
lambdaisland/kaocha-cloverage {:mvn/version "1.0.63"}}
:main-opts ["-m" "kaocha.runner"]}
:socket-repl {:jvm-opts ["-Dclojure.server.repl={:port,50505,:accept,clojure.core.server/repl}"]}}}
When I do clj -A:dev:socket-repl
, I get a REPL in the namespace user
(defprotocol IShape
(area [this])
(perimeter [this]))
(defprotocol IScale
(scale [this s]))
(defrecord Circle [r]
IShape
(area [this] (* Math/PI r r))
(perimeter [this] (* 2 Math/PI r))
IScale
(scale [this s] (->Circle (* r s))))
(defrecord Square [l]
IShape
(area [this] (* l l))
(perimeter [this] (* l 4))
IScale
(scale [this s] (->Square (* l s))))
(area (->Circle 2))
(perimeter (->Square 4))
(scale (->Square 2) 2)
However, I can't do (ns com.my-project.core)
and have any definitions in said namespace be loaded in the REPL
see IScale
that declares a namespace. you probably want (require 'com.my-project.core)
the idea is to have a stable interface that's independent of the implementation. it can be worth it even in cases where there's just one implementation.
I see. This forces me to declare a record even though i only have a single record type. That is, no square, just a circle.
ah ok
https://clojure.org/guides/repl/navigating_namespaces has some good information
besides using protocols and records, is there another way to implement updaters?
one of the biggest improvements is just spending time thinking about what the stable interface should be
I tried that but it's unable to resolve any of the definitions
can you paste what you've tried?
Lambda Island (https://lambdaisland.com/guides/clojure-repls/clojure-repls) says the REPL that appears when you do this isn't the socket REPL per se, and you need to connect to it via netcat (`nc localhost 50505`)
Hold on
as for updaters, the answer is always "it depends" 😛 . you can also use multimethods
can you show an example?
my main concern is hiding the internal fields e.g. :field-a
:field-b
user=> (require 'grok.db.core)
nil
user=> database-uri
Syntax error compiling at (REPL:0:0).
Unable to resolve symbol: database-uri in this context
'(require '[grok.db.core :as grok]) (grok/database-uri)`
i guess my question for you is what happens if you want to change :field-a?
nothing complex. just set the field. i just don't think it's a good idea to expose the field name, the key
or you can read about in-ns
from the link above to know how to get into a namespace
do you change
(defn my-model->b [m] (:b m)
to
(defn my-model->c [m] (:c m)
or do you still have
(defn my-model->b [m] (:c m)
where the field and function name don't matcheven if i do, just because refactoring tools exist to easily rename functions rather than fields (keys), it makes the job much easier
Mmm, still not resolving. I also tried doing (in-ns 'grok.db.core)
to no avail
I think of the big insights I picked up from learning is using clojure is to focus on the data model and work with that directly
It's weird, because there was one time it worked and I could access everything from the REPL
rather than focusing on the getters/setters and other functions around the data
there's a bunch of reasons why that mindset works well in the long run compared to the getter/setter approach
ok, so what do i do if i rename a field for example? i have to rename it everywhere i use it, either read or set it
it also breaks backward compatability
When I reconnected via netcat, I suddenly had the same errors appearing again
if you've already run (ns grok.db.core)
you've created a new empty namespace. restart your repl and then do
(require '[grok.db.core])
(in-ns grok-db.core)
that's a good question
i understand your point. but this is the cost
All right, lemme try that
it's the data that really sticks around, not the functions around it
and if you're just using a bare repl, don't netcat just type in the repl. no need for another process involved
so if you write your data to disk or send it over the network, it's the data that is being sent
so it's really worth trying to get your data model right and thinking about how it might evolve over time
writing getters/setters is convenient in the short term, but it's just kicking the can down the road
> so it's really worth trying to get your data model right and thinking about how it might evolve over time i can't plan 2 years ahead. no matter how hard i try
> plans are useless, but planning is essential
right, you can't predict the future, but that doesn't mean that the decisions you make about your data model won't affect you down the road
ok, i get it
the OO claim is that getters/setters will insulate you from these changes, but in my experience, that is a false promise
there's several reasons against using getters/setters, but as far as isolating you from changing field names, it only helps you within your program. if you write your data to disk or send it over the network, you still have to figure out how to deal with changing field names. further, if you package your code as a library, changing a field name can still break library users when they upgrade
i understand. thank you
also for being patient :)
these are all good questions
i don't think i'm explaining this alternate approach all that well
btw, this guy says something along your lines: https://clojureverse.org/t/do-you-encapsulate-domain-entity-maps/6147/5
(defn find-not-in-kb
[expr kb]
(filter #(and (sequential? %) (= (first %) 'if) (= (second %) (nth expr 1))) kb)
)
Currently, this looks for an "if" in the first of a sequence and then an random element in the second of sequence, how would i change my function so it looks for the third element of the sequence instead of the second ?this method used to work for me --> (.getURLs (java.lang.ClassLoader/getSystemClassLoader))but now i get the runtime error --> java.lang.IllegalArgumentException: No matching field found: getURLs
i wonder what has changed and how i might repair this ?
the default classloader after java 9 is no longer a URLClassLoader
see https://blog.codefx.org/java/java-9-migration-guide/#Casting-To-URL-Class-Loader @codeperfect
(it was never promised to be a URLClassLoader in 8, but you could cast it)
ahh, migration is a good search term
marvellous, a simple (seq (java.lang.System/getProperty "java.class.path")) is working for me in the repl
Note that (java.lang.System/getProperty "java.class.path")
returns a string, so (seq
ing it will return a sequence of characters (which may or may not be what you’re after).
(clojure.string/split (java.lang.System/getProperty "java.class.path") #":")
might be more like what you’re after?
yes, i am always surprised at how i mess up data presentation, and splitting on the ";" is just right
No worries - I do it all the time too. I find the REPL and (clojure.pprint/pprint)
are my best friends for helping to visualise data. 😉
There are also fancier GUI-based tools that can graphically present Clojure data structures (often integrated with the REPL), but I’ve found (pprint)
to be more than enough in most cases.
hi! Functions that have side effects are often named with an ! in the end. How do you name functions that use resources that can change. E.g. querying a db or using other non-immutable resources.
I tend to just use a normal function name, even though the function could return different results over time. Otherwise everything up your call chain to -main
would need to be annotated with something indicating "side effects" 🙂
The original intent of !
was to indicate functions that would not be safe in a "transaction" (STM) but its use isn't very consistent -- some folks use it on anything that causes side effects, some only for functions that cause external side effects, some even for functions that call down to such functions.
In clojure.java.jdbc
and next.jdbc
, functions that are intended to be "just" queries have regular names (`query`, get-by-id
, find-by-keys
, plan
-- since plan
itself doesn't actually do anything, only when you reduce
it do you get the side-effects), as opposed to update!
, insert!
, execute!
(`next.jdbc` is a bit more aggressive about this because execute!
and execute-one!
can be used for reading the DB as well as updating it, unlike c.j.j -- and there are next.jdbc.plan/select!
and next.jdbc.plan/select-one!
which are helpers that wrap plan
with reductions, even if they are queries)
if you wanted to write a new faq entry for https://github.com/clojure/clojure-site/blob/master/content/guides/faq.adoc, this is a frequent question - I think you've said the critical bits here
How would i filter a seqeunce like this "((if a b) a (if b c)) and only return a "a"
?
So i want my filter function to find a specific element in my sequence
you might want some
instead
Ok thank you!!
Under Reader and Syntax?
"What do ? and ! mean in function names?" perhaps?
(following "What does an _ mean...")
That sounds like a good place for it
is point free style dead?
what's the latest on point free style. does the community like it?
if all my functions were unary, i'd feel unconflicted about composing them. but since they aren't, it makes point free style more... involved.
point free was never alive in clojure
10-4, @ghadi
Despite the presence of comp
and partial
, the use of anonymous functions is generally preferred.
That being said, comp
is common when composing transformations as part of a transducer invocation.
Can you say more about it? I think I heard that partial
is "better" because it doesn't create a closure. Is #()
considered more idiomatic?
Rich is on record (several times) as saying he considers #()
to be more idiomatic than partial
.
Huh. Thanks, I felt so too initially, actually.
partial
definitely does create closures -- look at its source.
Well, I figured it must create at least something equivalent (callable and holding the arguments)
partial
creates a closure with multiple arities -- since it doesn't know how many arguments your function takes (so it doesn't know how many are still "missing") so if you have a function that takes four or more arguments, you're going to be running via a variadic function using apply
.
If you use #(f arg1 %1 %2)
then you get a 2-arity closure and your function f
will be directly invoked with three arguments -- so you'll get IDE/linter support on the call of f
in terms of validating the arity, and you'll also get runtime arity checking when that closure is called. With partial
, you lose any IDE/linter support at the call site when writing the code, and you also defer the runtime arity checking (since you go through another call layer between the source code and the actual call site of your function -- and it might go through apply
, pushing the arity checking a little further away from what you wrote. All of which makes errors harder to spot and debug.
Yes, got it, thank you!
FWIW, when I got started with Clojure a decade ago, I was very enamored with comp
and partial
... but I hardly ever use them nowadays.
(I do still like juxt
tho')
So partial
seems to be a good fit for when you really want varargs, though? Like (partial + 10)
#(apply + 10 %&)
would be the alternative, I guess
But in what contexts would you actually want a varargs (anonymous) function? I can't recall ever using %&
in a decade of production Clojure and I think I've only used partial
on a variadic function "by accident", i.e., when the actual call(s) of that expression are known arities.
Yeah, just grepped my code here: no instances of %&
in well over 100k lines of Clojure (including all my OSS work). I have just a handful of (partial apply =)
in test code (but that is not variadic, it takes a single collection argument -- so I could (and should) just use #(apply = %)
Across our entire codebase, we have just over 100 calls to partial
in just under 60 files.
Yeah, I can probably come up with an artificial example, but I guess you're right, it's not common at all
By contrast, we have well over 500 anonymous functions with #(..)
across nearly 200 files.
(I actually had to check the docs before even mentioning %&
for its existence; never used it in real life)
Thank you!