Fork me on GitHub
#beginners
<
2018-03-24
>
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.

grierson09:03:13

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.

schmee09:03:17

@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

schmee09:03:07

have you looked at spec?

schmee09:03:40

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

schmee09:03:37

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.

schmee09:03:27

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

schmee09:03:56

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

schmee09:03:15

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

schmee10:03:52

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

schmee10:03:56

(ns foo.foo
  (: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)))
    true))

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

(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/and
    (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

schmee10:03:25

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

schmee10:03:18

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

schmee10:03:28

so in this case ::width expands to foo.foo/width, 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

schmee10:03:31

you could do something like this:

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

schmee10:03:24

spec can work in tandem with pre and post

Michael Fiano10:03:18

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

schmee10:03:46

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 foo.foo/meander-rate etc.

schmee10:03:29

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

schmee10:03:57

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

schmee10:03:03

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

schmee11:03:27

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

schmee12:03:36

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.

schmee12:03:24

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

schmee12:03:51

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 values...so I need to also destructure a let binding which seems repetitious

schmee12:03:25

can you post the complete function?

Michael Fiano12:03:16

Only using seed so far in the let binding, but there will be more: https://gist.github.com/mfiano/b46007fdbc4ae59130b1bd132e91424f

Michael Fiano12:03:45

Which is optional, so needs to be merged

schmee12:03:39

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)))     
       (do
         (rng/set-seed! seed)         
         "more stuff here"))))              

schmee12:03:17

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 http://clojure.org 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?

schmee12:03:47

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 πŸ™‚

schmee12:03:15

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.

schmee13:03:16

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.

schmee23:03:47

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

schmee23:03:51

glad to hear it! πŸ™‚

tdantas19:03:00

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

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

tdantas19:03:18

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

noisesmith19:03:28

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)
5

noisesmith19:03:42

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

noisesmith19:03:53

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

noisesmith19:03:26

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

schmee19:03:38

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

bronsa20:03:52

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

bronsa20:03:54

not direct linked

bronsa20:03:58

and there's no disabling for that

schmee20:03:26

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

bronsa20:03:52

inlined by the clojure compiler

schmee20:03:05

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

bronsa20:03:07

look at the metadata for #'+

bronsa20:03:17

you'll see :inline and :inline-arities

schmee20:03:22

ooohhh right!

schmee20:03:29

I forgot about those

schmee20:03:20

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

bronsa20:03:52

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

schmee19:03:56

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

noisesmith19:03:57

oh, right, the direct linking thing?

tdantas19:03:19

I see @noisesmith, thank you too @schmee

alexmiller20: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.

schmee22:03:55

you want nat-int?

Michael Fiano23:03:04

What about a set of at least 1 keyword symbol?

Michael Fiano23:03:33

homogenous set

schmee23:03:57

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

Michael Fiano23:03:22

It has pretty much everything. Thank you

schmee23:03:27

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