Enhance spec/double-in to handle open and half-open intervals?

125 views
Skip to first unread message

Mars0i

unread,
Jul 20, 2016, 3:03:28 PM7/20/16
to Clojure
clojure.spec/double-in defines a spec that tests whether a double is greater than or equal to a minimum value and less than or equal to a maximum value.  This is useful for many purposes, but sometimes you need to test whether a double is greater than a minimum or less than a maximum.  There are many mathematical applications that assume half-open intervals (> min and <= max; >= min and < max) or open intervals (> min and < max).  Example: You are going to divide by the double, so it must be > 0, not just >= 0.  Providing double-in but only allowing it to specify closed intervals seems arbitrary.

It's easy enough to write open-interval and half-open interval tests, either using s/and with s/double-in, or just by defining a function from scratch.  However, if Clojure is going to provide double-in, why make us do that?  Why not have a common interface that adds two optional keywords to specify whether the bounds are open or closed?  This is not a request to add the kitchen sink; it's a natural extension to deal with a common use case.  The alternative would be to define three additional versions of double-in, and people will do that on their own, or define a new more flexible double-in if there's no built-in spec function that does it.


I'd suggest a new keyword with one of the following forms:


:min-open, :min-greater, :min-greater-than, :min-strict, :greater-min, :strict-min, etc.,


and a corresponding keyword for the max side.


Default values should be false, for compatibility with the current definition of double-in.


I can add a JIRA ticket if this seems worthwhile.  I wanted to see what people think first.  (I'd have to sign up with the JIRA system, but I assume I'm allowed to do that.)  My apologies if there's already a JIRA ticket for this.  I don't seem to be able to find anything on double-in, but I'm not sure I understand the search syntax.

Alex Miller

unread,
Jul 20, 2016, 5:32:40 PM7/20/16
to Clojure
You can file a jira if you like, I'm not sure Rich's thoughts on this. 

Also, keep in mind that you can also compose preds and get this with slightly more effort now:

(s/and (s/double-in :min 0.0 :max 1.0) #(not= 0.0 %))

Mars0i

unread,
Jul 20, 2016, 10:41:59 PM7/20/16
to Clojure
On Wednesday, July 20, 2016 at 4:32:40 PM UTC-5, Alex Miller wrote:
You can file a jira if you like, I'm not sure Rich's thoughts on this. 

I understand.  Thanks--will do.
 
Also, keep in mind that you can also compose preds and get this with slightly more effort now:

(s/and (s/double-in :min 0.0 :max 1.0) #(not= 0.0 %))

Yes, definitely.  Though #(and (> % 0.0) (<= % 1)) seems simpler if one doesn't really need the NaN and Infinity tests.

Thanks.


Alex Miller

unread,
Jul 20, 2016, 11:02:17 PM7/20/16
to Clojure
You'll find that the generator for double-in is far better than what you're suggesting, and you should lean on it when doing things slightly differently. 

I didn't try it but I don't think your example would gen at all - you'd need to s/and double? in there too at the beginning and even then it's going to generate random doubles then filter to your range, but most generated values will not be in the range. s/double-in is designed to only generate values in the specified range.
 

Thanks.


Mars0i

unread,
Jul 20, 2016, 11:36:40 PM7/20/16
to Clojure

Ah.  Thanks.   I'm sure you're right.  I didn't understand the role of the spec logic functions generating for testing.  I hadn't thought about the generator functionality at all--just validation of real inputs.  I'm still feeling my way in the dark with spec.  I needed half-open and closed interval tests for user input the very small application that I'm using to explore spec, which is how the issue about double-in arose for me.  i.e. I have a real use case, but my testing can be pretty simple.

Steve Miner

unread,
Jul 21, 2016, 9:50:21 AM7/21/16
to clo...@googlegroups.com
With a little help from Java, you can make equivalent open intervals for the desired bounds. For example,

(s/and (s/double-in :min 0.0 :max 1.0) #(not= 0.0 %))

should be the same as

(s/double-in :min Double/MIN_VALUE :max 1.0)

Also, you can use java.lang.Math/nextUp and nextAfter to get adjacent doubles for your bounds.

(java.lang.Math/nextUp 1.1)
;=> 1.1000000000000003

(java.lang.Math/nextAfter 1.1 Double/NEGATIVE_INFINITY)
;=> 1.0999999999999999

There are a few tricky situations around the zeroes and infinities so you should read the doc on nextAfter, especially if you’re dealing with extreme doubles.

Alex Miller

unread,
Jul 21, 2016, 10:51:02 AM7/21/16
to Clojure
You can already get open intervals by just omitting :min or :max.

Mars0i

unread,
Jul 21, 2016, 11:13:17 AM7/21/16
to Clojure
On Thursday, July 21, 2016 at 9:51:02 AM UTC-5, Alex Miller wrote:
You can already get open intervals by just omitting :min or :max.

I think my terminology may have created some confusion; I probably shouldn't have used open/closed.  I meant "open interval" in the sense that an open interval is one that doesn't include the endpoints.  e.g. all real numbers x such that x > 0 and x < 1 are the open interval (0, 1).  A half-open interval is one that includes one of the endpoints, but not the other one.  

(s/double-in :infinite? false) sort of specifies an open interval in my sense, except that there are hard boundaries on what can be represented as a double, so I suppose it's really a closed interval.  I don't know.  That's getting too pedantic even for me. :-)

Steve Miner

unread,
Jul 21, 2016, 11:17:44 AM7/21/16
to clo...@googlegroups.com
> (s/and (s/double-in :min 0.0 :max 1.0) #(not= 0.0 %))
>
> should be the same as
>
> (s/double-in :min Double/MIN_VALUE :max 1.0)

I should have mentioned that Double/MIN_VALUE is the smallest positive double (just greater than 0.0), not a large negative value. It’s easy to get confused by the fact that Long/MIN_VALUE is extremely negative.

Mars0i

unread,
Jul 21, 2016, 11:19:05 AM7/21/16
to Clojure
On Thursday, July 21, 2016 at 8:50:21 AM UTC-5, miner wrote:
With a little help from Java, you can make equivalent open intervals for the desired bounds.  For example,

Also, you can use java.lang.Math/nextUp and nextAfter to get adjacent doubles for your bounds.

(java.lang.Math/nextUp 1.1)
;=> 1.1000000000000003

(java.lang.Math/nextAfter 1.1 Double/NEGATIVE_INFINITY)
;=> 1.0999999999999999

There are a few tricky situations around the zeroes and infinities so you should read the doc on nextAfter, especially if you’re dealing with extreme doubles.

Cool--very nice to know.  Thanks.  I may use it.

Ideally, I'd rather not have to descend to that level for a spec, because the meaning becomes less clear, and I'd rather let built-in language constructs hide differences between Clojure and Clojurescript whenever possible.  Testing for being > 0.0 and < 1.1 won't involve a meaningful difference in different clojure variants, for most purposes.
Reply all
Reply to author
Forward
0 new messages