beginners

Getting started with Clojure/ClojureScript? Welcome! Also try: https://ask.clojure.org. Check out resources at https://gist.github.com/yogthos/be323be0361c589570a6da4ccc85f58f.
st3fan 2020-11-12T00:20:05.015600Z

I had a day off which I used to re-learn Clojure. I’m converting some GitHub bots that I wrote in Go to Clojure and it has been pretty interesting so far.

🤓 2
🙂 1
dgb23 2020-11-12T01:31:39.018100Z

Hi! is there a standard clojure function which does the equivalent of (spec/conform (spec/cat …) […])? This is so useful I assume there must be a similar thing outside of spec that people use.

seancorfield 2020-11-12T01:56:41.018900Z

@denis.baudinot I would say that is the "standard clojure function" for it -- but it depends on exactly what spec functionality you're referring to there...

seancorfield 2020-11-12T02:01:14.019900Z

If you mean "take a list of names and a list of values and make a hash map" then there's zipmap but it doesn't do any conformance on the actual values:

user=> (s/conform (s/cat :a int? :b string?) [42 "Hello"])
{:a 42, :b "Hello"}
user=> (zipmap [:a :b] [42 "Hello"])
{:a 42, :b "Hello"}
user=> (zipmap [:a :b] ["Forty Two" 13])
{:a "Forty Two", :b 13}
user=> 

👍 1
dgb23 2020-11-12T02:13:02.022100Z

This is exactly what I meant, thank you for the clarification. (I like the spec version more when looking at this and thinking about it though. Conforming values in general seems to be a really useful thing to do.)

Mark Wardle 2020-11-12T07:33:34.023400Z

Thanks! It works. Much appreciated.

Mark Wardle 2020-11-12T09:18:04.029700Z

Hi all. I have a weird failing test that may be the result of AOT compiling and only occurs on a test first run, after deleting the contents of the classes/ directory. I have a defrecord containing numbers, strings and a java.time.LocalDate. Equality testing works fine based on the value of the LocalDate in local testing (even though the objects do not have the same identity) AND works fine on 2nd and subsequent runs of a test that serializes then deserializes the record into a key-value store (mapdb). The problem is on the FIRST run of the test which somehow decides that two LocalDates with the same value (but different identities) are not equal. It feels as if it is something to do with a cache but it simply shows up my ignorance of the process of AOT and its interaction with running with JIT compiled code. EDIT: Actually fascinating as the problem only rears its head if I run tests by executing AOT on the same clj run as the test - and doesn’t happen if I manually run AOT first and THEN the test run.

Eamonn Sullivan 2020-11-12T09:52:55.034100Z

I have a very beginners, possibly beginner functional, question: I have a collection of a couple hundred things. I have to filter those things using a REST call (to get an answer to a boolean question that I'm filtering on). There's no bulk version of the REST call I need to make, but making a couple of hundred sequential HTTP requests in a row is taking too long (`(filter is-it-a-thing? coll)`). What tool would a more experienced Clojure person reach for in this situation?

Audrius 2020-11-12T10:14:37.035800Z

Hello! are there any naming conventions for vars in Clojure beside earmuffs *var* . I did stumble upon something like this +sub-name+ and unsubscribe* is it something common or just up to the author?

Eamonn Sullivan 2020-11-12T10:48:37.036500Z

Looking at clojure.core.reducers/fold and family.

borkdude 2020-11-12T10:57:37.036800Z

I think +foo+ is not that common. foo* is, usually to indicate a variation or a more low level alternative

✔️ 1
bronsa 2020-11-12T11:30:56.038300Z

+foo+ is often used in other lisps to indicate a constant value, but not really in clojure

Eamonn Sullivan 2020-11-12T12:53:26.039300Z

I too stupid for fold, but result1 is a lot faster than result2.

user> (def result1 (remove nil? (pmap #(if (is-a-thing? %) % nil) things)))
#'user/result1
user> (def result2 (filter is-a-thing? things))
#'user/result2
user> (time (count result1))
"Elapsed time: 7988.541086 msecs"
73
user> (time (count result2))
"Elapsed time: 29946.487776 msecs"
73
user> 
I'm guessing this could be muchly simplified.

Eamonn Sullivan 2020-11-12T13:00:45.039600Z

Or another example:

user> (time (def result1 (into [] (filter is-a-thing? things))))
"Elapsed time: 23648.239951 msecs"
#'user/result1
user> (time (def result2 (into [] (remove nil? (pmap #(if (is-a-thing? %) % nil) things)))))
"Elapsed time: 5181.302774 msecs"
#'user/result2
user> 

Jim Newton 2020-11-12T13:31:27.047800Z

need clever way to iterate through a sequence. I have sequence, input-sequence and a function, f, which I can call on each element of the sequence. The problem is that f is expensive to call, so I want to call it as few times as possible. I can suppose that f always returns one of true, false, or :dont-know I want to return false if the sequence is empty, without calling f I want to return true if the f returns true on any element of the sequence. I want to return false if f returns false on every element of the sequence. otherwise I want to return :dont-know If I create a sequence like this (map f input-sequence) this sequence will be lazy. Am I guaranteed that f will only be called when I access that element of the output sequence? I.e., could first check the output sequence to see if it contains true, and if not, then check to see if it only contains false

bronsa 2020-11-12T13:33:24.048500Z

laziness in clojure doesn't guarantee 1 at a time laziness

Jim Newton 2020-11-12T13:33:46.049200Z

Ahh, so I need to do my own interation then to be sure?

bronsa 2020-11-12T13:33:56.049500Z

but you can (map f (unchunk sequence)) to turn it into 1 at a time

bronsa 2020-11-12T13:34:19.049800Z

the usual definition of unchunk would be

(defn unchunk [s]
  (when (seq s)
    (lazy-seq
      (cons (first s)
            (unchunk (next s))))))

bronsa 2020-11-12T13:35:01.050400Z

alternatively you could reduce over the collection with a boolean accumulator, and short circuit using reduced to return true

Jim Newton 2020-11-12T13:36:48.053800Z

yes reduce/reduced or even loop/recur will do the trick of course, but the logic is contorted. My suspicion is that the code is more concise If I can just check membership. without resorting to inline iteration.

bronsa 2020-11-12T13:36:49.053900Z

if I understand correctly, something like (reduce (fn [_ el] (case (f el) true (reduced true) false false :dont-know (reduced :dont-know)) false coll)

bronsa 2020-11-12T13:38:21.054900Z

I personally find this type of operation more natural to express/understand in terms of folds like above

Jim Newton 2020-11-12T13:40:52.056100Z

@bronsa, won't your function return :dont-know if it encounters a :dont-know? It should return true if there is a true anywhere in the list, even after a :dont-know.

bronsa 2020-11-12T13:41:25.056400Z

ah, yes :)

bronsa 2020-11-12T13:42:16.056900Z

(reduce (fn [b el] (case (f el) true (reduced true) false b :dont-know :dont-know) false coll) this then I think?

Jim Newton 2020-11-12T13:43:36.057800Z

(let [values (map f (unchunked intput-sequence))]
  (cond (some true? values)
        true

        (every? false? values)
        false

        :else
        :dont-know))
how about this?

bronsa 2020-11-12T13:44:18.058100Z

¯\(ツ)

bronsa 2020-11-12T13:44:43.058800Z

it works I guess, tho it's more expensive than the single scan using reduce

bronsa 2020-11-12T13:45:14.059400Z

FWIW forall would (every? false? values)

bronsa 2020-11-12T13:45:44.060Z

and member would be (some true? values)

Jim Newton 2020-11-12T13:46:19.060700Z

good suggestions. I normally find forall more readable, but in this case (every? false? values) is more readable.

Jim Newton 2020-11-12T13:47:09.061400Z

and them (some? true? values) or (some true? values) I always mix up some vs some?

bronsa 2020-11-12T13:47:26.061700Z

some is the pair of every? :)

bronsa 2020-11-12T13:47:40.062Z

some? is non-nil

bronsa 2020-11-12T13:47:59.062300Z

bit confusing but if you understand why they're named that way, it makes sense

bronsa 2020-11-12T13:48:33.062800Z

i.e. some is not a predicate, it doesn't return true/false, but the first non falsey value

bronsa 2020-11-12T13:49:40.063200Z

in other languages some is better named find-first or things like this

Jim Newton 2020-11-12T13:53:19.063600Z

is some the same as (first (filter ...)) ?

bronsa 2020-11-12T13:54:04.063900Z

no

bronsa 2020-11-12T13:54:09.064100Z

more like (first (keep (if keep returned on truthy rather than non-nil)

bronsa 2020-11-12T13:55:10.065500Z

let's say some is (first (filter identity (map f :)

Jim Newton 2020-11-12T13:55:10.065600Z

back to the previous question. what's more readable, the use of map depending on the lazy nature, or an explicit but very clever reduce which computes the value without dependence only laziness ?

bronsa 2020-11-12T13:55:56.066300Z

I personally find reduce significantly more readable here, and not particularly clever/obfuscated

bronsa 2020-11-12T13:56:36.066800Z

I'll give you that your version is more "declarative"

bronsa 2020-11-12T13:57:06.067300Z

but this sort of folds is super natural in FP so after a bit of getting used to it, it becomes second nature

bronsa 2020-11-12T13:58:41.067800Z

but really, it's up to personal preferences if you're not too fussed about the extra linear scans (which you probably shouldn't be)

bronsa 2020-11-12T14:00:34.068700Z

the one thing I'd say tho, is I'd advise you to prefer functions available in the clojure.core stdlib vs non standard macros that achieve the same thing (like that forall thing)

bronsa 2020-11-12T14:00:51.069Z

makes it easier for other people to understand your code

Jim Newton 2020-11-12T14:06:34.070700Z

Don't get me wrong. I love reduce, and I use it a lot. However, in this case (maybe I'm wrong, thus asking) am I trying to be too clever. For example, you're first implementation was wrong as well. Is there another bug in there somewhere. For example, does it really do the correct thing on the empty sequence. I suppose in either case I need to write unit tests to make sure, so perhaps corner cases should be considered moot.

bronsa 2020-11-12T14:13:25.071900Z

well, my first implementation was wrong cause I didn't read the spec well enough :) and re: empty sequence, yes, the behavior of reduce is defined to return the init value if the sequence is empty (from the docs:

If val is supplied, returns the result of applying f to val and the first item in coll, then applying f to that result and the 2nd item, etc. If coll contains no items, returns val and f is not called.

bronsa 2020-11-12T14:15:11.072900Z

but again, readability is subjective. use whatever feels easier to you, I still find your version very readable

Jim Newton 2020-11-12T14:17:19.074Z

actually I was under the impression that reduce calls the function with 0 arguments if the sequence is empty.

bronsa 2020-11-12T14:17:27.074200Z

if the init val is not supplied

Jim Newton 2020-11-12T14:17:38.074500Z

ahhhhhhhh...

bronsa 2020-11-12T14:18:02.075300Z

protip: forget about the non-init arity of reduce

☝️ 3
Jim Newton 2020-11-12T14:18:30.075600Z

in that case I have several pieces of code I can clean up. Because I sometimes pass a mult-arg version of fn to reduce just for the empty-sequence case.

Jim Newton 2020-11-12T14:19:08.076100Z

that's something that always annoyed me about clojure's reduce. Now perhaps I don't need to be annoyed.

Jim Newton 2020-11-12T14:21:52.077300Z

it's not clear from the doc, whether if I supply a singleton list, AND an init value, whether the function is called. (reduce (fn [acc item] (+ acc (count item))) 0 '("a" "ab"))

bronsa 2020-11-12T14:22:53.077500Z

it is

bronsa 2020-11-12T14:23:06.077700Z

If val is supplied, returns the result of applying f to val and the first item in coll

bronsa 2020-11-12T14:23:34.078200Z

doesnt' matter how many elements are in coll (unless coll is not empty)

bronsa 2020-11-12T14:24:05.078600Z

hang on, in that example '("a" "ab") is not a singleton list

bronsa 2020-11-12T14:24:07.078800Z

it has two elements

Jim Newton 2020-11-12T14:25:17.079700Z

yes exactly. reduce (as it seems to by experimentation) is supposed to sum the counts. which it does, even if the sequence is a singleton '("a")

bronsa 2020-11-12T14:25:36.080Z

yeah sure

Jim Newton 2020-11-12T14:25:45.080300Z

It does the right thing, but the docstring could be clearer in my opinion.

bronsa 2020-11-12T14:26:07.080600Z

clojure docstrings are notoriously terse

bronsa 2020-11-12T14:26:56.081800Z

but I find this behavior clear from the docstring tbh

Jim Newton 2020-11-12T14:27:13.082700Z

that fact that in some cases clojure's reduce calls the function with 0 arguments to get the unit value is bizare and clever. I don't know of other functional languages that do this. well I only know about Common Lisp and Scala.

dpsutton 2020-11-12T14:27:15.082800Z

Cursive and cider both have clojuredocs available and built in for more verbose info with examples

bronsa 2020-11-12T14:27:58.083800Z

yes, the no-init behavior of reduce is unfortunate and I'm pretty sure if the clojure/core team were to design clojure from scratch, it would not be in the language

Jim Newton 2020-11-12T14:28:02.084100Z

I always use the web pages such as. https://clojuredocs.org/clojure.core/reduce

bronsa 2020-11-12T14:28:15.084400Z

it's an unfortunate wart of clojure being backwards compatibile (which is an asset most of the time)

Jim Newton 2020-11-12T14:28:29.084800Z

it is nice for functions like + and *

Jim Newton 2020-11-12T14:29:40.085500Z

anyway, I'm happy to realize that I don't need to include the 0-ary version of the function every time when I'm not certain about the length of the input sequence.

Jim Newton 2020-11-12T14:29:48.085800Z

thanks for that revelation

Jim Newton 2020-11-12T14:30:05.086100Z

😁

Jim Newton 2020-11-12T14:49:04.087900Z

QUESTION about the distinct function. The documentation does not define anything about the order of the elements in the returned sequence. Or even whether it always return an = sequence if there are no duplicate elements. Can I depend on the order returned. Or do I need to write my own version of distinct if I care about the order?

Jim Newton 2020-11-12T14:52:52.090700Z

A few years ago I worked for a company which maintained a proprietary lisp implementation. At some point we improved the hashing function of the standard hash table to make it faster, or more dispersive. Enough of our customers complained that their code was breaking. Some of their code was depending on the iteration order of the map-over-hash function that we had to revert the changing, leaving in place an inferior function. In retrospect, the original hash iterator should have been intentionally non-deterministic.. But alas....

alexmiller 2020-11-12T14:53:59.091Z

distinct retains order of the original sequence

Jim Newton 2020-11-12T14:56:52.093700Z

thanks @alexmiller is that a feature that's guaranteed ? For example it would be faster (at least on large sequences), if it added the elements to a set, and then extracted them from the set, thus not maintaining the order. Also maintaining the order is not really a specification. For example there are two ways to uniquify (1 2 3 2) maintaining order. either (1 3 2) or (1 2 3) . one maintains left-to-right order and the other maintains right-to-left order.

2020-11-12T14:57:07.093800Z

I had heard that Clojure's behavior of reduce was influenced by Common Lisp's behavior with a similar function.

alexmiller 2020-11-12T14:57:33.094500Z

I would consider it to be implied by the current docstring and likely to break people if it changed

alexmiller 2020-11-12T14:57:47.094800Z

so I can't imagine it would change

alexmiller 2020-11-12T14:58:13.095400Z

if you want a different behavior, you'll need your own impl

alexmiller 2020-11-12T14:58:44.095900Z

current impl always keeps the first seen element

Jim Newton 2020-11-12T14:59:34.096600Z

Thanks for the info.

Returns a lazy sequence of the elements of coll with duplicates removed.
Returns a stateful transducer when no collection is provided.
is this the docstring you're referring to?

2020-11-12T15:00:13.097500Z

^^^ @jimka.issy

Jim Newton 2020-11-12T15:00:37.098100Z

SBCL common lisp tries to maintain order, and is fast for short lists. Allegro Common Lisp makes no promises about order, and is faster for large lists. Allegro maintainers are more interested in Big Data anyway. So I'm not sure which one is really better. There are lots of small lists in the world.

Jim Newton 2020-11-12T15:03:06.098300Z

the Common Lisp reduce function is a lot of functions rolled into one.

Jim Newton 2020-11-12T15:04:29.098500Z

If I recall correctly, you can specify right to left traversal, or that only a sub-range be traversed, and lots of stuff. in my opinion it's too much to think about.

2020-11-12T15:04:57.098700Z

I mention it because its documentation explicitly mentions that the function you provide to it can be called with 0 or 2 arguments.

2020-11-12T15:24:01.099800Z

As soon as one element causes the supplied function f to return true, I would guess he wants to avoid calling f again.

Daniel Stephens 2020-11-12T15:24:27.100Z

Oh yeah, my bad, I see what you mean 👍

2020-11-12T15:24:31.100200Z

As soon as an element is reached that causes f to return true, there is no need to continue evaluating.

2020-11-12T16:54:25.100500Z

if you run (defrecord Foo []) twice, you get two definitions of Foo, and they have the same name and definition, but are not =

2020-11-12T16:54:56.100700Z

creating instances of Foo, then aot compiling its ns, then creating Foo again, will create two totally different Foo

arielalexi 2020-11-12T17:12:20.106200Z

Hey, I want to add a new namespace to my project. I am using leiningen with Intelij. I checked the libary with https://clojars.org/java-http-clj , than added to the :dependencies in the project.clj and it also presented in the Leiningen dependencies tree. But when I am adding it to my core.clj :requires and reload the core file to the REPL I am getting an error of FileNotFound. Is there other place I need to add?

Jim Newton 2020-11-12T17:12:49.106400Z

hmm.. i forgot about that part. But now I seem to remember that it is indeed not called with 0 arguments if you provide an :initial-value.

dpsutton 2020-11-12T17:13:17.106900Z

what are you adding to the require part of your namespace?

dpsutton 2020-11-12T17:13:52.107200Z

and want to point you to the documentation of the library: https://github.com/schmee/java-http-clj#examples

2020-11-13T18:21:50.205900Z

I understand. I was trying to convey that information to you by reproducing the error you are seeing 🙂

2020-11-13T18:22:39.206100Z

If you go to Clojure 1.10.0, I'd recommend going to 1.10.1, since there are a few fixes there relative to 1.10.0, especially if you executed code in your projects from a user.clj init file.

✅ 1
2020-11-12T17:14:36.107500Z

The description of the behavior of when the function f is called with 0 args is pretty close between the Common Lisp Hyperspec and Clojure's reduce functoin.

arielalexi 2020-11-12T17:21:08.109900Z

I did use the documentaion of the libray, and yet getting an error only when adding

[java-http-clj.core :as http]
this is the error :
CompilerException java.io.FileNotFoundException: Could not locate clojure/spec/alpha__init.class or clojure/spec/alpha.clj on classpath., compiling:(java_http_clj/core.clj:1:1) 

acim1 2020-11-12T17:26:42.112500Z

Can someone confirm if I can invoke (use-fixtures...) multiple times (profitably) in a test? Basically, I want to register two fixtures like

(use-fixtures :once (fn [f] (start-db) (f) (stop-db))  ; wrap the whole suite, I want to get one connection for the test
(use-fixtures :each (fn [f] (f) (clean-up-test-data))) ; after each test, make sure created data is cleaned up

Mark Wardle 2020-11-12T17:27:37.113200Z

Oh. Yikes. I need to AOT because the class is used by an external java library. But that definitely explains some screwy things I’ve been getting such as “x can’t be cast to x” where x=x after even reloading the REPL.

Darin Douglass 2020-11-12T17:27:53.113800Z

Best way to find out is to just try it! (Spoilers: yes you can do that)

Mark Wardle 2020-11-12T17:28:32.114Z

Thank you for the explanation.

2020-11-12T17:29:42.114200Z

That namespace clojure.spec.alpha requires Clojure 1.9 or later. Are you using Clojure 1.8 or earlier in your project?

acim1 2020-11-12T17:29:51.114400Z

Sure, I understand, but I didn't want to go down a road where I start questioning the concept because PEBCAK. Now that I know I'm on the right path, I can proceed with confidence! Thank you

2020-11-12T17:31:32.114600Z

if you need AOT, definitely do it as a separate step and be sure the results of it are loaded before creating instances

2020-11-12T17:32:12.114800Z

and if you need to reload namespaces that use defrecord / deftype etc. you need to also destroy and recreate all those instances

2020-11-12T17:32:47.115Z

which is part of why stuartsierra/component and weavejester/integrant libraries are so useful - they automate this process

👍 1
2020-11-12T17:35:09.115200Z

I get that error message you show as well if I try to require the java-http-clj.core namespace from inside of a project that uses Clojure 1.8.0

Mark Wardle 2020-11-12T17:36:23.115400Z

Ah! That makes sense thank you very much!

arielalexi 2020-11-12T17:50:30.115700Z

@andy.fingerhut update in your project.clj to be [org.clojure/clojure "1.10.0"] than you will not have that error

Daniel Stephens 2020-11-12T17:54:31.115900Z

it doesn't work for the same 'type' of fixture

(use-fixtures :each (fn [f] (println "not here") (f)))
(use-fixtures :each (fn [f] (println "here") (f)))

(deftest a
  (is (= 1 1)))
will only print "here", but you can supply multiple fns as args in one call:
(use-fixtures :each
              (fn [f] (println "here") (f))
              (fn [f] (println "and here") (f)))

👍 1
Joe 2020-11-12T21:34:15.120200Z

What's a relatively painless way to do a PAAS hosting of a docker container running a JAR built from a Clojure project? Not looking for scale or configurability, just something simple that I can hit with HTTP requests. I looked at Heroku, but looks like a lot to learn and before I start I want to make sure I'm not going down a blind alley, or see if there's a better alternative.

practicalli-john 2020-11-13T08:42:15.162300Z

I use Heroku without docker, it automatically builds from source using the usual Clojure build tool. No need to add any extra layers. You can even get CircleCI to deploy to Heroku, using the Heroku orb on CirclCI. It's also easy to add a pipeline in Heroku if you want a staging environment, then simply promote the staging image when ready. If you really need persistent file system, use S3 or one of the plugins. Or use persistent storage/database suitable for the cloud.

2020-11-13T09:16:47.162600Z

I use Heroku (to which I publish a jar file, not source) for a tiny app that I haven’t updated in years. It was pretty painless to set up and still works after a few dyno upgrades. For me, dockerising my toy app would have been additional complexity without benefit.

➕ 1
2020-11-12T21:40:25.123200Z

I have a little open source Clojure chat bot running on Heroku, if a worked example is helpful: https://github.com/pmonks/futbot It runs on the “hobby” tier ($7/mth), since I didn’t want it getting quiesced when “inactive” (which it wasn’t, but I was concerned that Heroku wouldn’t detect any activity since the chat bot doesn’t have any traditional web traffic). That said, I don’t use Heroku’s Docker capabilities for this - I just deploy an old-skool “dyno” (which is built by Heroku).

1
phronmophobic 2020-11-12T21:40:29.123400Z

It depends on what you're looking for. I've been using $5 digital ocean servers as well as the equivalent amazon instances and just run the jar directly

phronmophobic 2020-11-12T21:40:43.123600Z

amazon also has options like like lightsail and elastic beanstalk

Joe 2020-11-12T21:46:45.123900Z

Thanks, it sounds like you and @pmonks just skip the docker step. Maybe I should try without for now. I have very little idea on what the options or best-practices are.

2020-11-12T21:53:43.128300Z

Yeah I’m not going to claim that what I’m doing is best practice, but it works well for me at this tiny “hobby” scale.

2020-11-12T21:54:31.128500Z

I basically don’t think about Heroku much at all, now that it’s setup - it’s all just source control (GitHub) shenanigans, some of which cause a new deployment (i.e. commits to my main branch).

2020-11-12T21:55:50.129400Z

Is there an established naming convention for “pure” namespaces (i.e. those that contain nothing but pure functions) vs “impure” namespaces (that contains state, for example)? If not, any suggestions for such a thing? e.g. a ! suffix for impure namespaces - my.impure.ns! The background is that I (try to) make as many of my namespaces as possible “pure” in this way, with state handling centralised into a few stateful namespaces, and there are times where it would be nice to know which is which, simply by looking at namespace names (e.g. filenames), and also figure out whether I’ve accidentally polluted one of my pure namespaces...

phronmophobic 2020-11-12T21:56:36.129700Z

what makes a namespace impure?

2020-11-12T21:56:48.130Z

e.g. (def state (atom {}))

2020-11-12T21:57:08.130500Z

With code in that ns swap! ing the value of the atom.

dgb23 2020-11-12T21:57:12.130600Z

I don’t think docker would be necessary for anything that is small to medium sized and/or doesn’t have very frequent updates for java deployments right?

2020-11-12T21:57:48.131700Z

Basically any ns that contains any fns that don’t rely solely on their arguments to do their thing.

phronmophobic 2020-11-12T21:58:06.132100Z

using swap! at the top level or just using swap! somewhere inside one of the functions?

2020-11-12T21:58:11.132300Z

The latter.

phronmophobic 2020-11-12T21:58:39.132900Z

what if the swap! is using an argument passed to the function and not a global atom?

2020-11-12T21:58:39.133Z

And to be clear, it’s not limited to atoms.

2020-11-12T21:59:51.134400Z

A function that declares an atom locally (e.g. in a let) could still be pure, no?

2020-11-12T22:00:03.134700Z

I do this for interop with nasty mutable Java code sometimes, for example, but the fn itself is still pure.

dgb23 2020-11-12T22:00:43.134900Z

also is my assumption correct that the JVM already abstracts from the platform? You can bundle it so it includes all the dependencies in a single jar is what I know. I did my last java production deployment 10ish years ago so idk.

phronmophobic 2020-11-12T22:01:17.135600Z

what's the filename look like for a filename namespace with a ! in it?

2020-11-12T22:02:00.135900Z

Yeah for my Heroku I build and run an uberjar. I tried running from source, but for some reason that more than doubled the runtime memory requirements, which blew through the (small, strict) amount of memory Heroku gives you (512MB for a hobby-tier dyno).

2020-11-12T22:03:16.137400Z

Haven’t gotten that far yet, though (in-ns 'my.impure.namespace!) appears to work just fine. I’m open to other suggested conventions, of course - indeed that was my original question! 😉

phronmophobic 2020-11-12T22:04:46.138600Z

in-ns is extremely rare in my experience. usually ns is used at the top of namespaces and require is used to reference code in other namespaces

2020-11-12T22:05:03.139100Z

Ok just verified that (ns my.test!) in file src/my/test!.clj works, at least on MacOSX.

2020-11-12T22:05:39.139200Z

I understand that - I’ve rarely if ever used it before. I was simply using that at the REPL to quickly check it. No point creating a foo!.clj file if (in-ns 'foo!) doesn’t even work…

2020-11-12T22:08:06.139500Z

Again, let’s not get hung up on the specific convention of using a ! suffix on the namespace - I’m more interested if anyone has used any convention for indicating pure vs impure namespaces, and if so what that might be.

phronmophobic 2020-11-12T22:08:50.139700Z

yea, I'm not sure how that would interact with :gen-class

phronmophobic 2020-11-12T22:09:22.139900Z

I'm also not sure pure/impure namespace is a clear distinction

phronmophobic 2020-11-12T22:09:48.140100Z

and there's some kinds of impurity that are "acceptable" (ie. interning a var via def)

phronmophobic 2020-11-12T22:10:07.140300Z

memoization is another type of impurity that is often "acceptable"

2020-11-12T22:10:29.140500Z

Right - concrete vs abstract side effects, to paraphrase Bertrand Meyer.

2020-11-12T22:10:56.140700Z

Let me ask you though: do you think the notion of a pure fn is, or can be, well-defined?

2020-11-12T22:12:01.141100Z

Because if so, then all I’m referring to by “pure namespace” is a namespace that contains nothing but pure fns (and I’d love to hear of a better label, if such a thing exists!).

phronmophobic 2020-11-12T22:12:24.141300Z

I think it can be defined well enough. what kind of impure namespaces do you want? the only kind I can think of is if it's necessary for clojurescript interop

phronmophobic 2020-11-12T22:15:27.141500Z

otherwise, I generally expect all namespaces to be pure

alexmiller 2020-11-12T22:15:46.141900Z

if you just make all your namespaces pure, you don't need a naming convention

👍 1
phronmophobic 2020-11-12T22:15:47.142Z

or at least, idempotent when loaded

phronmophobic 2020-11-12T22:16:30.142700Z

another example is when you have a global registry, which can be considered "acceptably" impure in some cases

2020-11-12T22:18:14.143300Z

So here’s one concrete example - not the only one, probably not even a good one - but it’s the one I’m most familiar with. 😉 I have a small chat bot (I happened to link it just above, fwiw) that runs as a standalone command line app. The bootstrap code for that app reads a config file and calls some REST APIs, and then caches that data away (in memory), and starts a bunch of timed jobs (functions that run at specific times throughout the day). Those “job functions”, and all of the underlying functions that they call, are pure - they receive all of the data they need to do their thing as arguments, and they don’t read or persist any state anywhere else. The “bootstrap” code however, is far from pure - it reads all sorts of things from the greater environment (config files, internet services), and then squirrels it away for later. Now I’ve tried hard to keep that “bootstrap” code as small and isolated from the rest of the (pure) codebase as possible, but at times I’ve let some statefulness accidentally creep in, or opened the wrong file because I’ve been away from the code for some time, or whatever. That’s where a naming convention would be helpful.

2020-11-12T22:18:50.143600Z

I agree, but take a look at https://clojurians.slack.com/archives/C053AK3F9/p1605219494143300?thread_ts=1605218686.138600&cid=C053AK3F9 for a concrete example of where impurity necessarily bleeds in.

phronmophobic 2020-11-12T22:20:12.144Z

I would move all the bootstrap code into functions and only call that code from a main function. there are also libraries like https://github.com/stuartsierra/component that can help

phronmophobic 2020-11-12T22:20:30.144300Z

then your namespaces are pure again

dgb23 2020-11-12T22:29:01.144500Z

last time I checked out Heroku, which is a while ago, I got turned off by the fact that you can’t write to files. Most small web applications have some sort of file storage requirement like a SQLite db, some primitive logging or something like that.

2020-11-12T22:31:18.144800Z

Yeah I use mount , which is similar in intent to component, but lighter weight.

2020-11-12T22:31:55.145Z

But I basically consider the state that mount creates as being equivalent to an atom, and so don’t let those (global) vars creep into my pure functions anywhere.

2020-11-12T22:32:18.145200Z

Instead I pass everything around, and keep references to the mount-managed state to the “impure” namespaces only.

2020-11-12T22:32:47.145400Z

For one thing it makes testing substantially easier (and yes mount supports testing, but it’s even easier to test without having it there at all).

2020-11-12T22:33:44.145600Z

Oh and the impure bootstrap logic is (mostly) within my -main fn (or at least the same main namespace).

2020-11-12T22:34:12.145800Z

But still, I’d like to be able to more quickly / easily differentiate the (impure) main ns from my pure nses.

phronmophobic 2020-11-12T22:35:01.146Z

a function that deals with state doesn't infect a namespace

phronmophobic 2020-11-12T22:35:49.146200Z

as long as loading a namespace doesn't have any unacceptably impure side effects, then I would consider the namespace itself pure for all practical purposes. no?

2020-11-12T22:37:49.146400Z

Perhaps “pure” is a misleading label then.

2020-11-12T22:38:14.146600Z

The differentiating criteria is “can I call any fn in this namespace, without having to setup state anywhere else first”.

2020-11-12T22:39:28.146800Z

When using component / mount and similar libraries, fns that directly consume the state created by those libraries no longer meets that criteria. i.e. you have to have those libraries create some form of state first (even if it’s just hardcoded “test data” type state).

phronmophobic 2020-11-12T22:39:40.147Z

ah ok. I was thinking of something different. I have marked functions with side effects using the ! suffix

2020-11-12T22:40:12.147200Z

Right, which is why my first naive thought was to use that same suffix at the namespace level. 😉

2020-11-12T22:40:27.147400Z

Obviously I’d much rather adopt an existing convention, if such a thing exists!

phronmophobic 2020-11-12T22:41:15.147600Z

yea, I'm just not sure if namespace, rather than function by function, is the right granularity

phronmophobic 2020-11-12T22:41:29.147800Z

like if a namespace contains both, it's still fine using a pure function from that namespace

2020-11-12T22:43:01.148Z

Good point.

phronmophobic 2020-11-12T22:43:15.148200Z

additionally, if in a new version, you want to add a function that is impure to a namespace, what do you do? do you create a new namespace to avoid changing the namespace name? that doesn't seem worth it if it otherwise logically still belongs

2020-11-12T22:43:51.148400Z

My speculative thought would be that that would indicate a design problem.

phronmophobic 2020-11-12T22:44:01.148600Z

I'm not opposed to trying new stuff, but just throwing out some considerations

💯 1
❤️ 1
2020-11-12T22:44:11.148800Z

Yep - and I greatly appreciate it!

2020-11-12T23:16:17.149200Z

Yeah could be. My chat bot has (so far) remained mercifully free of any persistent state that it can’t resurrect from 3rd parties, which has greatly simplified the runtime environment.