<< Back to previous view

[CLJ-1298] Add more type predicate fns to core Created: 21/Nov/13  Updated: 18/Aug/17  Resolved: 07/Jun/16

Status: Closed
Project: Clojure
Component/s: None
Affects Version/s: Release 1.5, Release 1.6
Fix Version/s: Release 1.9

Type: Enhancement Priority: Critical
Reporter: Alex Fowler Assignee: Unassigned
Resolution: Completed Votes: 18
Labels: None

Approval: Ok

 Description   

Add more built-in type predicates:

1) Definitely missing: (atom? x), (ref? x), (deref? x), (named? x), (map-entry? x), (lazy-seq? x), (boolean?).
2) Very good to have: (throwable? x), (exception? x), (pattern? x).

The first group is especially important for writing cleaner code with core Clojure.



 Comments   
Comment by Alex Miller [ 21/Nov/13 8:42 AM ]

In general many of the existing predicates map to interfaces. I'm guessing these would map to checks on the following types:

atom? = Atom (final class)
ref? = IRef (interface)
deref? = IDeref (interface)
named? = Named (interface, despite no I prefix)
map-entry? = IMapEntry (interface)
lazy-seq? = LazySeq (final class)

throwable? = Throwable
exception? = Exception, but this seems less useful as it feels like the right answer when you likely actually want throwable?
pattern? = java.util.regex.Pattern

Comment by Alex Fowler [ 21/Nov/13 9:02 AM ]

Yes, they do, and sometimes the code has many checks like (instance? clojure.lang.Atom x). Ok, you can write a little function (atom? x) but it has either to be written in all relevant namespaces or required/referred there from some extra namespace. All this is just a burden. For example, we have predicates like (var? x) or (future? x) which too map to Java classes, but having them abbreviated often makes possible to write a cleaner code.

I feel the first group to be especially significant for it being about core Clojure concepts like atom and ref. Having to fall to manual Java classes check to work with them feels inorganic. Of course we can, but why then do we have (var? x), (fn? x) and other? Imagine, for example:

(cond
(var? x) (...)
(fn? x) (...)
(instance? clojure.lang.Atom x) (...)
(or (instance? clojure.lang.Named x) (instance? clojure.lang.LazySeq x)) (...))

vs

(cond
(var? x) (...)
(fn? x) (...)
(atom? x) (...)
(or (named? x) (lazy-seq? x)) (...))

The second group is too, essential since these concepts are fundamental for the platform (but you're right with the (exception? x) one).

Comment by Alex Fowler [ 22/Nov/13 6:35 AM ]

Also, obviously I missed the (boolean? x) predicate in the original post. Did not even guess it is absent too until I occasionally got into it today. Currently the most clean way we have is to do (or (true? x) (false? x)). Needles to say, it looks weird next to the present (integer? x) or (float? x).

Comment by Brandon Bloom [ 22/Jul/14 1:02 AM ]

Predicates for core types are also very useful for portability to CLJS.

Comment by Brandon Bloom [ 22/Jul/14 1:05 AM ]

I'd be happy to provide a patch for this, but I'd prefer universal interface support where possible. Therefore, this ticket, in my mind, is behind http://dev.clojure.org/jira/browse/CLJ-803 etc.

Comment by Alex Miller [ 22/Jul/14 6:12 AM ]

I don't think it's worth making a ticket for this until Rich has looked at it and determined which parts are wanted.

Comment by Alex Miller [ 02/Dec/14 4:33 PM ]

Someone asked about a boolean? predicate, so throwing this one on the list...

Comment by Reid McKenzie [ 02/Dec/14 4:51 PM ]

uuid? maybe. UUIDs have a bit of a strange position in that we have special printer handling for them built into core implying that they are intentionally part of Clojure, but there is no ->UUID constructor and no functions in core that operate on them so I could see this one being specifically declined.

Comment by Brandon Bloom [ 03/May/15 2:50 PM ]

This has been troubling me again with my first cljc project. So, I've added a whole bunch of tickets (with patches!) for individual predicates in both CLJ and CLJS.

Comment by Alex Miller [ 03/May/15 5:35 PM ]

As I said above, I don't want to mess with specific patches or tickets on this until Rich gets a look at this and we decide which stuff should and should not be included. So I'm going to ignore your other tickets for now...

Comment by Nicola Mometto [ 24/Nov/15 4:08 PM ]

map-entry? is included since 1.8

Comment by Alex Miller [ 07/Jun/16 11:36 AM ]

We have spent a significant amount of time looking at additional predicates for Clojure and a large commit was just added to master with many new ones in https://github.com/clojure/clojure/commit/58227c5de080110cb2ce5bc9f987d995a911b13e.

Those added were:

  • seqable? (also covered in separate ticket)
  • boolean?
  • long?, pos-long?, neg-long?, nat-long?
  • double?
  • bigdec?
  • ident? (like the named? proposed), simple-ident?, qualified-ident?
  • simple-symbol?, qualified-symbol?
  • simple-keyword?, qualified-keyword?
  • bytes? (for byte[])
  • indexed?
  • inst? (for Date, but backed by a protocol for extension)
  • uuid?
  • uri?

I'm closing this as I don't expect to add any more mentioned in the ticket.

Comment by Vitalie Spinu [ 12/Aug/17 11:39 AM ]

Any reason neg-double?, pos-double? were left out?

Also there cannot be `nat-double?` because `nat` stands for "natural numbers". When needed, one would need to define`nneg-double?` or similar. Might be good to stick with nneg from the start then.

Comment by Alex Miller [ 12/Aug/17 4:36 PM ]

They were left out because they are far less common. You can combine the checks for `double?` and `neg?` or `pos?` if needed.

Comment by Marc O'Morain [ 18/Aug/17 5:00 AM ]

These new predicates are great. Thanks for adding them.

Before these predicates are in a released version of Clojure, it would be really helpful to define and document how the negative cases behave. I would suggest that the docstrings are changed from (for example, {pos-long?})

Return true if x is a positive Long

to

Return true if x is a positive Long, false otherwise.

The reason for this is to avoid to potential confusion where predicates in clojure.core, such as {pos?} can return true/false/throw exception.

Comment by Alex Miller [ 18/Aug/17 6:31 AM ]

Predicates in Clojure never throw exceptions on the false case as this would break any of the higher order functions that use them. Also these docstrings match the style of all the other older preds. No plans to change.

Comment by Marc O'Morain [ 18/Aug/17 7:50 AM ]

Hi Alex, thanks for the update. Let me try to explain the issue from my point of view, with the hope of changing your mind.

Predicates in Clojure never throw exceptions on the false case as this would break any of the higher order functions that use them.

The predicates that can throw in clojure.core are the ones that are specific to the type of the operand, like realized? (CLJ-1751), pos?, zero?, neg?, etc, rather than the type-check predicates which don't throw, like integer?. To take a single example, when the user is going to call pos-long? in a situation where the value that they want to test could be nil, they need to understand if it behaves like pos?, which can throw, or long? which will always return true or false. From the docstring as authored, it is not clear how pos-long? behaves when passed nil.

Also these docstrings match the style of all the other older preds.

The docstrings do match the style of the existing predicate docstrings, so this might be a good opportunity to update the docstrings to full document the behaviour. As it stands (constantly true) would be a valid implementation of pos-long? according to the docstring, since the docstring does not indicate under what conditions the function returns false.

The reason that I'm labouring the point is that I personally have found it difficult to learn the core Clojure library by relying solely on the docstrings. I often find myself having to reply on the examples on clojuredocs.org and answers on Stack Overflow to learn how many of the function in core operate.

Thanks!

Generated at Sun Jan 21 11:00:13 CST 2018 using JIRA 4.4#649-r158309.