Fork me on GitHub
#beginners
<
2022-09-20
>
Epidiah Ravachol18:09:00

Working my way through a tutorial, I came across an empty anonymous function (fn []). It was tucked into the end of an or form, presumably to provide a default result if the first part of the or didn't return anything. Playing around with (fn []) in the repl, I see that it just returns nil. That's all pretty clear to me, but I'm wondering if there's a reason to prefer that over something perhaps a little more explicit, such as (constantly nil) ?

dpsutton18:09:59

I think the first one is way more explicit than (constantly nil)

👍 1
hiredman18:09:18

(constantly nil) is definitely more explicit about what is returned, but they might not actually care about the returned result of the function, but maybe invoking for side effects, where (fn []) is a function that does no side effects

dpsutton18:09:30

I was thinking of arities.

paused-repl=> ((fn []))
nil
paused-repl=> ((fn []) :a)
Execution error (ArityException) at metabase.api.dashboard/eval193577 (REPL:1503).
Wrong number of args (1) passed to: metabase.api.dashboard/eval193577/fn--193578
paused-repl=> ((constantly nil) :a :b :c :d :e)
nil
(fn []) is more explicit about what arguments it can accept: none. (constantly nil) can take arbitrarily many arguments.

💡 1
hiredman18:09:31

(constantly nil) is also that, but from that view point which one is more explicitly that is less clear

hiredman18:09:26

for places that invoke no arg functions and don't care about the return value, I've been known to just use + which is not clear at all, in any way

1
Epidiah Ravachol18:09:46

Ah! Yes, I figured I was missing something key about (fn [])! Thank you.

dpsutton18:09:56

haha. i love the + trick but i would have to run git blame to figure out the intention if i wasn’t familiar with the idiom

skylize18:09:24

More explicit: (fn no-op [])

✔️ 1
James Amberger18:09:19

Is it fair to say that if there’s no way you’ll be exploiting hierarchies, there’s little advantage to a multimethod over a fn with equivalent conditional code?

hiredman18:09:45

No, a multimethod is open to extension

James Amberger18:09:59

so if someone consumes my code he can extend the multimethod but would have an ugly time wrapping the equivalent fn

👍 1
Vinicius Vieira Tozzi19:09:54

Hi everyone, I have been playing with clojure for a little while now and it’s really cool, but there is on topic I am very unsure about it, what’s the typical way to do error handling in clojure? Is just exceptions as in Java or is there any library that most of the community uses, I just would like general tips on error handling as I am having a hard time figuring out the best way to do.

Vinicius Vieira Tozzi19:09:51

I played arround with https://github.com/funcool/cats, but I have to say I am not quite sure yet how to handle the results that some functions give me.

seancorfield20:09:59

Exceptions are idiomatic in Clojure, just like in Java.

seancorfield20:09:52

But you will also see code that returns nil for a failure since nil-punning is idiomatic too.

Vinicius Vieira Tozzi20:09:58

I assumed exceptions are too side effectful and Clojure usually tries to have pure functions (where possible)

seancorfield20:09:08

You will sometimes see code that returns a hash map with either error-related keys or result-related keys. It depends on how your domain works. We have an example of this at work where we have several pipelines of photo processing code and they pass a hash map through the entire pipeline with :valid true indicating the data is still valid and the processing step should continue, or :valid false indicating an error has been detected and subsequent steps should not perform their work.

seancorfield20:09:29

On the JVM, you'll interact with a lot of core Java library code that throws exceptions so you can't avoid them in many cases. In addition, Clojure has ex-info to create information-carrying exceptions that you can throw (and the catch can call ex-data to get that information back).

seancorfield20:09:31

In general, I try to draw a mental line between "expected" failures and "unexpected" failures. For the former, these are errors that you could handle via nil or an error/result hash map for example. For the latter, I'd consider those good candidates for exceptions. For exceptions, I also try to either handle those very locally or just let them bubble all the way to the top (for a generic exception handler to deal with). For errors, those might bubble up the call chain a bit but something is expected to handle those (and possibly retry an operation, or perform an alternative operation). Unfortunately, some exceptions from Java libraries indicate "expected" failures that may indicate a retry is possible or that you are just meant to catch as part of normal data processing.

🙌 1
Vinicius Vieira Tozzi06:09:43

One issue I see with returning nil to represent an “expected” error is that sometimes a function can have more than 1 type of “expected” error to check, but in this case I guess I could use a error/result hash map like you explained

👍 1
Vinicius Vieira Tozzi06:09:23

Thank you for the detailed explanation 😀 it was very helpful, I definitely have a better overview now.

Bart Kleijngeld07:09:45

Adding to @U04V70XH6: when choosing to bubble up that category of unexpected failures all the way up and handle them there, the purity of the pure functions through which those exceptions pass is virtually unaffected. Just like some OS related memory error that terminates your script doesn't make those functions less pure, I feel something can be said for viewing them as pure as well even though some exceptions passes through to be handled at the top. I'm trying to say that with this strategy consistently in place, from the POV of your pure functions you can pretty much ignore those exceptions in your reasoning about those functions. Hope that make sense and it is clear 🙂. Also, if people disagree I would love to hear and learn

Vinicius Vieira Tozzi18:09:25

yeah that actually makes sense, it’s just that exceptions somehow have a bad reputation (at least from what I heard), so somehow it just feels wrong to have exceptions bubbling up but because they way clojure handle return values, in the end it does not matter at all, you can still reason as if the functions are really pure

seancorfield18:09:55

I'm curious where and what you've heard about exceptions being "bad"? I suspect people's feelings about exceptions are shaped by the languages they've used previously...

Bart Kleijngeld18:09:12

When learning about functional programming, I also read a lot of trash talking of exceptions, so I think I know the feeling @U027MB6K8RM has. But I think you're right that it's all about how they are used and what your language offers and prescribes as idiomatic.

Vinicius Vieira Tozzi19:09:08

I mostly heard that errors should be just values and not whole classes/objects like exceptions are, but in Clojure they are actually treated as values if I understood correctly.

pppaul20:09:57

clojure mostly hasn't had errors until recently

seancorfield20:09:55

@U0LAJQLQ1 Not sure what you mean by that...?

pppaul21:09:12

clojure mostly throws java exceptions. https://insideclojure.org/2018/12/17/errors/ clojure errors seem to be pretty new. spec added a lot of error messages as well, which is also pretty new.

seancorfield21:09:08

I think you're misunderstanding that article. Those errors have been around a long time. Clojure 1.10 just improved them in terms of the information provided.

seancorfield21:09:06

And mostly that's about tool chain support, not the actual errors themselves (for example, nREPL-based tooling continued to display those errors the way Clojure 1.9 and earlier did).

seancorfield21:09:22

All of the "errors" in that article are actually exceptions -- thrown by the compiler (or by the runtime, for the latter part of the article).

pppaul21:09:45

i guess i misunderstood. this is probably a bit off topic for this thread as well