This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-12-05
Channels
- # adventofcode (111)
- # announcements (20)
- # babashka (19)
- # beginners (47)
- # calva (7)
- # clojure (56)
- # clojure-dev (27)
- # clojurescript (2)
- # events (1)
- # holy-lambda (1)
- # juxt (2)
- # meander (18)
- # minecraft (4)
- # missionary (107)
- # nextjournal (21)
- # off-topic (30)
- # reagent (7)
- # reitit (19)
- # releases (1)
- # tools-build (8)
- # tools-deps (7)
- # vim (22)
- # xtdb (4)
Hi, I must say that I'm disappointed by the exception handling in the new parse*
functions e.g.:
https://github.com/clojure/clojure/blob/abe19832c0294fec4c9c55430c9262c4b6d2f8b1/src/clj/clojure/core.clj#L8007-L8016
The exception, if untouched:
* bubbles up and reports errors right where they happen
* Instead of being a nil that maybe won't cause a problem much later, maybe 7 namespaces away
* Informs of the precise value at fault
* Increasing debuggability
And architecturally:
* this helper goes in the opposite direction of an imperative-shell-functional-core pattern
* i.e. an incorrect value is a form of impurity
* Said impurity should be caught at the edges
* Yet, a generic parse-uuid
can be slapped anywhere in the codebase, it looks just as an innocent/vanilla clojure.core defn.
wrt Java, I like from Clojure that historically it has remained unopinionated. Accordingly it fits naturally a variety of preferences and programming disciplines.
Now, if the "clojure.core way" has a specific flavor of exception handling, I can plausibly see myself fighting against that in team contexts. Seems an uphill battle that could be spared.
This is still compatible with offering wrappers at all for tooling convenience - just don't alter the semantics of the wrapee? They've been pretty well established for maybe 20+ years.
Kind of came up a few days ago: https://clojurians.slack.com/archives/C06E3HYPR/p1637696522135900 > 1) string in invalid format - this is an expected possibility and returns nil so you can nil pun and decide what to do in this case
Yes, I believe this is all covered in the jira. The parse fns take a string and return a parsed value or nil. On an unexpected input (non string), they are undefined but may and do throw. In many contexts you know you have a string as input. In others, you may need to add your own guard around a call to parse.
While we're at it, I was a bit surprised to find out that:
(parse-boolean "True") ;; => nil
(Boolean/parseBoolean "True") ;; => true
In all the situations I can think of, I'd expect "True"
to be parsed as true
.
I think parse-boolean
would be more convenient if it matched the behaviour of Boolean/parseBoolean
when the input is a string.I agree, I don't see it as only convenience but also as avoiding seemingly superfluous fragmentation / invalidation of existing knowledge
> if it matched the behaviour of `Boolean/parseBoolean` when the input is a string On second thought, it's more complicated than this. In a perfect world I'd expect:
(parse-boolean "True") ;; => true
(parse-boolean "False") ;; => false
(parse-boolean "invalid-string") ;; => nil
but the boundaries between false and nil are probably not very clear (even Boolean/parseBoolean
returns false in the third case). Not sure which behaviour I prefer :thinking_face:Well our preference is what is implemented. If you want parseBoolean semantics, call it! If you need something else, you can easily implement it.
From the jira: > Returning nil allows for nil punning and supports throwing in a wrapper if desired. Throwing does not allow for nil punning and requires exception based flow control. I don't see such an asymmetry. Let's say it was exception-based, consumers could also "support nils in a wrapper if desired"
n.b. I'm not saying any approach is better or worse, I'm saying that before Clojure was unopinionated, and that was a good thing. IME if people can use a construct in a sloppy way, they will (given real-world pressures). So I can very plausibly see an impoverishment of real-world Clojure codebases, backed by what can be interpreted as an official design recommendation. It would be clearer and less opinionated to e.g. offer these in a separate ns or even lib, that documented clearly its philosophy.
I find it amusing that you think Clojure was unopinionated :)
Clojure seems unopinionated in a few things e.g. interop vs. wrappers, exceptions vs. nil vs. Result, type-like systems, concurrency models etc. Thought experiment - what if core.async had been simply part of clojure.core back in 2013? Would it be perceived/used exactly the same way nowadays? How would 'competitors' compare to it?
if you want a different way to think about the parse functions - their purpose is to take a string, parse it, and give you a value of the proper type. giving a parse function an invalid string is not exceptional - that's a normal condition a parse function should handle (not an exceptional situation). giving a parse function something like a keyword is exceptional - you are using the API wrong. these are two independent cases handled in different ways. back to the "invalid string" case - your options there are to throw (which as I've explained is a mismatch) or to return a sentinel value indicating a parse failure. we could return some "new" sentinel indicating this but a) we want something that can be the same across parse fns of different types (so 0
is not good for parse-uuid or whatever) and b) we have lots of useful tools that work with nil punning, so nil
is a particularly unique sentinel value
I don't see anything here as "different" than prior design decisions in Clojure. We support the host and the use of exceptions as idiomatic when an exceptional case occurs. nil punning is also idiomatic.
I see Clojure as highly opinionated in most of the other things you mention too, so I totally don't understand those as examples
I have the professional impression that, say, 80% of consumers of this new defn won't carefully apply nil-punning, honoring the nil as a sentinel value and handling it in a robust way. So casual users will simply grab this defn as a convenience over interop syntax, accordingly making codebases worse (vs. what they would have done before). It doesn't help that clojure.core defns have (AFAICT) to have short docstrings, so one wouldn't have the chance to carefully explain / remind people of the intended usage pattern. It's something that happens in other areas of Clojure - things are underexplained, so 'properly' using them becomes a contentious/subjective issue. The intended usage becomes some sort of tribal knowledge that is scattered over books, chat logs, etc.
Clojure seems unopinionanted in the topics I mentioned, proof being that industry users use all sort of patterns/mechanisms without fighting the language. Clojure is more opinionated in e.g. immutability, inheritance, probably a few other things that are (rightfully) made hard or very evidently cumbersome.
parse-*
functions are basically impossible to port to other platforms
we can delegate to Boolean/parseBoolean
, or in long, Long/parseLong
(parse-long "1.")
in JVM will returns nil
. In cljs
there is no primitive with this behavior, it will probably return 1
I see parse-*
as toy/repl functions, like slurp
// split
. We can use in the REPL but we should not commit it.
As a alternative, I propose something like this:
(defn parse-*
[pred s]
(let [n (edn/read-string s)]
(when-not (pred n)
(throw (ex-info (str "Unexpected input: " (pr-str n))
{:n n :s s})))
n))
(defn parse-boolean [s] (parse-* boolean? s))
(defn parse-integer [s] (parse-* integer? s))
(defn parse-double [s] (parse-* double? s))
(defn parse-number [s] (parse-* number? s))
(parse-number " :33")
It would allow clojure team to invest time in clojure.edn
, that would benefit both edn
users and parse
users
Also, create less words for beginners. parse-long
returns what? a number? a int? a integer?
The expectation are well defined: it will accept true
and false
in edn
definition.
It is highly portable, once on every platform we always have a edn reader.Read reads the first token so it is not a good match. (Try (parse-long “5 :x”)
in yours. “read” and “parse” are fundamentally different ops. If you want read, you're right that we already have that. We did consider what you have above as an option - this is covered in the jira ticket.
These parse fns have defined grammars. You can implement that in any host.
the following two sentences in range
's docstring apply in the reverse order of precedence:
When step is equal to 0, returns an infinite sequence of
start. When start is equal to end, returns empty list.
i.e., if start is equal to end and step is equal to 0, you get the empty list. Swapping those two sentences would set the right expectations?Doesn't seem particularly important