Fork me on GitHub
Michael Fiano05:03:12

is there a predicate that checks if a value is a long?

Michael Fiano05:03:29

(or an integer)

Michael Fiano05:03:16

basically i want a "not a bignum" checking function, given an integral value.

Michael Fiano06:03:06

should I just do (not (= (type x) clojure.lang.BigInt)) ?

Michael Fiano06:03:56

@dpsutton That doesn't catch long

Michael Fiano06:03:35

Oh wait it does?

Michael Fiano08:03:38

I want the pre-conditions to somehow work even for the unspecified arguments. Using :or in the hashmap destructuring seems wrong here, as it would 1) be duplicating the external stage-defaults values, and 2) wouldn't work, because seed would be different between the 2.


Is there a way to integrate lein test-refresh results into an editor like NCrunch or Wallaby.js? At the moment I just have a floating terminal with lein test-refresh running but I would prefer a more integrated approach with my editor.


@mfiano I don't understand what you mean by "pre-conditions to somehow work even for the unspecified arguments", could you give an example of what is not working?

Michael Fiano09:03:26

@schmee What I mean is not all keyword arguments may be passed to the function call, so any of them could be nil and not pass


have you looked at spec?


pre- and postconditions are meant for simple arg-checking so they're not very flexible


also, may I suggest that you use a map instead of keyword arguments? it has a couple of advantages, most notably it is easier to combine arguments from different sources with merge, and it is easier to use with apply

Michael Fiano09:03:39

No I haven't. Basically this function is part of the user API, so I need to do a bit of type-checking and allow arbitrary keyword arguments with default values that are merged in.


this is a good fit for spec: spec has support for optional keys in your hashmap, so they will only be checked if they are present

Michael Fiano09:03:06

I'm very new. This is actually my first bit of code. I've been wrestling with this for 3 days

Michael Fiano09:03:15

I'll check what spec is


in that case my suggestion is: don't overthink it. just write some if statements, ors or whatever to get it working and then move on

Michael Fiano09:03:30

I'm trying to port my Common Lisp library. There is a lot of idioms like this


I see, give me a minute and I'll whip up an example of how you can do this with spec


here is a spec that should at least be a good starting point πŸ™‚


  (:require [clojure.spec.alpha :as s]
            [clojure.spec.gen.alpha :as gen]))

(defn validate-height [{:keys [height extent] :as m}]
 (if height
    (>= height (+ (* extent 2)))

(defn validate-width [{:keys [width extent]}]
 (if width
  (>= width (+ (* extent 2)))

(s/def ::width (s/and pos-int? odd?))
(s/def ::height (s/and pos-int? odd?))
(s/def ::seed pos-int?)
(s/def ::density (s/double-in :min 0.1 :max 1.0))
(s/def ::meander-rate (s/double-in :min 0.0 :max 1.0))
(s/def ::door-rate (s/double-in :min 0.0 :max 1.0))
(s/def ::stage
    (s/keys :req-un [::seed ::density ::extent ::meander-rate ::door-rate]
            :opt-un [::width ::heigt])
    #(validate-height %)
    #(validate-width %)))

(def stage-defaults
  {:seed 12419
   :density 0.65
   :extent 5
   :meander-rate 0.0
   :door-rate 0.5})

(defn make-stage [options]
  (s/valid? ::stage options))
  ;; or (s/explain ::stage options))

Michael Fiano10:03:48

Hmm, I've never seen a lot of this stuff like the double colon keywords


the double colon keyword is called a "namespace qualified keyword"


if you're in namespace foo, then ::bar expands to foo/bar. this is used to avoid name collisions when merging keys from different sources


so in this case ::width expands to, and another namespace can define a ::width spec but since they're qualified there's no risk of collision

Michael Fiano10:03:29

So basically I shouldn't use preconditions for most things

Michael Fiano10:03:40

I sort of liked the concision, but oh well


you could do something like this:

(defn make-stage [options]
  {:pre [(s/valid? ::stage (merge stage-defaults options))]}
  "do the thing") 


spec can work in tandem with pre and post

Michael Fiano10:03:18

what is :req-un and :opt-un?


the default in spec is to require namespaced keywords for the inputs as well, so if you use req and opt here it means that the input map must have keys like etc.


req-un and opt-un means `'allow unqualified keywords as inputs"

Michael Fiano10:03:52

I don't understand what spec is doing at all from this code. I'll have to read about spec first I think


it's long but of course you don't have to learn everything in one sitting

Michael Fiano10:03:38

Ok, I'll read. Thanks for your help


you're welcome, hope it helps you solve your problem

Michael Fiano10:03:04

Wow spec is very nice. Clojure just keeps on impressing me. Thanks so much


glad I could help! πŸ™‚

Michael Fiano12:03:47

I can't understand how :opt or :opt-un affect validation. If I pass an argument not specified, it doesn't raise an error


that's a deliberate design in spec, all maps are open. you can only specify what is required, you can't forbid keys. opt is just for documentation purposes. IIRC, all keys in the map are validated if they have a spec registered, regardless of whether they are specified in keys or not.


this has proved to be a somewhat controversial design decision πŸ˜‰

Michael Fiano12:03:26

Ok. So the only issue I now have is I'm forced to merge twice

Michael Fiano12:03:48

in the pre-condition, and in a let binding for the body


in that case I'd just skip the :pre and do the check in a conditional in the body that reuses the let binding

Michael Fiano12:03:07

Actually, that's not the issue:

Michael Fiano12:03:03

my parameters are destructured, because these will eventually map to command line arguments: [& {:keys [width height seed density extent meander-rate door-rate] :as options}]

Michael Fiano12:03:36

then i do (:pre [(s/explain ::stage options)]}

Michael Fiano12:03:06

But the body needs all the merged map I need to also destructure a let binding which seems repetitious


can you post the complete function?

Michael Fiano12:03:16

Only using seed so far in the let binding, but there will be more:

Michael Fiano12:03:45

Which is optional, so needs to be merged


here's how I would write that function:

(defn make-stage [user-options]
  (let [{:keys [width height seed density extent meander-rate door-rate] :as options} (merge stage-defaults user-options)]
    (if (not (s/valid? ::stage options))
       (throw (ex-info "Invalid options" (s/explain-data ::stage options)))     
         (rng/set-seed! seed)         
         "more stuff here"))))              


note that I use a map instead of keyword arguments, since IMO those tend to complicate things for little benefit

Michael Fiano12:03:38

somewhere in the docs I read that outer user api functions should use keyword args and stuff not for the user should just be regular maps, so i was following that advice

Michael Fiano12:03:34

This function here is going to eventually be the main entry point to be exposed for running the jar on the command line with those required and optional parameters. is that going to be a problem?


huh, that seems like outdated advice to me. Note that I'm not saying that they're bad and should never be used, just that my experience has been that it's easier and more flexible to use maps instead πŸ™‚


won't be a problem as far as I can tell!

Michael Fiano12:03:55

The use of keyword arguments has fallen in and out of fashion in the Clojure community over the years. They are now mostly used when presenting interfaces that people are expected to type at the REPL or the outermost layers of an API. In general, inner layers of the code find it easier to pass options as an explicit map.


not bad advice by any means, feel free to use whatever you like πŸ™‚ my version uses maps cause I think it's easier when you're merging multiple arg maps

Michael Fiano13:03:05

Thanks again. I like your version.

πŸ‘ 4
Michael Fiano23:03:20

The only thing I dislike is how specs with an external dependency have to have a separate function, and then added in the aggregate. In your example at the top of this thread, it's disappointing that validate-height and validate-width are nearly the same, and that they are invoked in the aggregate ::stage spec, so the whole map is given as input and difficult to figure out the problem in the case of an exception.


yeah, it can make it harder to find errors for sure, but I'm not sure how it could be done differently. that the methods are nearly the same is just sloppy coding from my side, you could easily modify the function to take a parameter for the key and reuse that function for both cases πŸ™‚

Michael Fiano23:03:45

Yeah I was able to translate your sloppy coding. No worries. On a side note those validation function are always truthy πŸ™‚

Michael Fiano23:03:10

I have some other issues too, like re-validating a hashmap as keys are assocd to it later efficiently, but optimizations for another day I suppose.

Michael Fiano23:03:32

Appreciate all your help. I am definitely having fun with spec


glad to hear it! πŸ™‚


why is not possible to redef the clojure.core/+ ? do am I doing anything wrong ?

(alter-var-root (var +) (fn [f] -))


some special β€œtag” that do not allow alter var root ?


Clojure 1.9.0
+user=> (alter-var-root (var +) (fn [f] -))
#object[clojure.core$_ 0x502f1f4c "[email protected]"]
+user=> (map + [1 2] [3 4])
(-2 -2)
+user=> (+ 2 3)


i works as long as it's not compiled directly into a form, but passed as an arg


there's an optimization that prevents your change in a normal call


+user=> (apply + [2 3])


FWIW, that optimization can be disabled with a command line flag


the problem with + used that way is that it gets inlined


not direct linked


and there's no disabling for that


not sure I follow. is var redefinition contingent on JVM optimizations? or do you mean inlined by the Clojure compiler?


inlined by the clojure compiler


I thought that was what direct linking was πŸ˜„


look at the metadata for #'+


you'll see :inline and :inline-arities


ooohhh right!


I forgot about those


I see, that makes sense, thanks for correcting me :thumbsup:


np! figuring whats at play between inline/dl/compiler intrinsics can get tricky :)


but since the compiler is built with it you'd have to recompile the compiler for the core fns to see redefs of each other


oh, right, the direct linking thing?


I see @noisesmith, thank you too @schmee

Alex Miller (Clojure team)20:03:09

You can use the slim classifier version for an uncompiled version of Clojure

Michael Fiano22:03:28

What is a good way to define a "zero or positive integer" spec with s/def? I don't really need s/or's named tags here.


you want nat-int?

Michael Fiano23:03:04

What about a set of at least 1 keyword symbol?

Michael Fiano23:03:33

homogenous set


(s/coll-of keyword? :kind set? :min-count 1)

Michael Fiano23:03:22

It has pretty much everything. Thank you


yeah, you gotta love how direct the translation from your description to the spec code is πŸ™‚