Clojure

Add more type predicate fns to core

Details

  • Type: Enhancement Enhancement
  • Status: Closed Closed
  • Priority: Critical Critical
  • Resolution: Completed
  • Affects Version/s: Release 1.5, Release 1.6
  • Fix Version/s: Release 1.9
  • Component/s: None
  • 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.

Activity

Hide
Marc O'Morain added a comment -

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!

Show
Marc O'Morain added a comment - 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!
Hide
Alex Miller added a comment -

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.

Show
Alex Miller added a comment - 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.
Hide
Marc O'Morain added a comment -

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.

Show
Marc O'Morain added a comment - 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.
Hide
Alex Miller added a comment -

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

Show
Alex Miller added a comment - They were left out because they are far less common. You can combine the checks for `double?` and `neg?` or `pos?` if needed.
Hide
Vitalie Spinu added a comment -

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.

Show
Vitalie Spinu added a comment - 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.
Hide
Alex Miller added a comment -

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.

Show
Alex Miller added a comment - 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.
Hide
Nicola Mometto added a comment -

map-entry? is included since 1.8

Show
Nicola Mometto added a comment - map-entry? is included since 1.8
Hide
Alex Miller added a comment -

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

Show
Alex Miller added a comment - 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...
Hide
Brandon Bloom added a comment -

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.

Show
Brandon Bloom added a comment - 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.
Hide
Reid McKenzie added a comment - - edited

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.

Show
Reid McKenzie added a comment - - edited 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.
Hide
Alex Miller added a comment -

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

Show
Alex Miller added a comment - Someone asked about a boolean? predicate, so throwing this one on the list...
Hide
Alex Miller added a comment -

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

Show
Alex Miller added a comment - I don't think it's worth making a ticket for this until Rich has looked at it and determined which parts are wanted.
Hide
Brandon Bloom added a comment -

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.

Show
Brandon Bloom added a comment - 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.
Hide
Brandon Bloom added a comment -

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

Show
Brandon Bloom added a comment - Predicates for core types are also very useful for portability to CLJS.
Hide
Alex Fowler added a comment - - edited

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

Show
Alex Fowler added a comment - - edited 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).
Hide
Alex Fowler added a comment - - edited

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

Show
Alex Fowler added a comment - - edited 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).
Hide
Alex Miller added a comment -

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

Show
Alex Miller added a comment - 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

People

Vote (18)
Watch (4)

Dates

  • Created:
    Updated:
    Resolved: