Fork me on GitHub
#beginners
<
2021-12-15
>
Benjamin09:12:32

is there a function like fnil that checks the return for nil instead of the arg?

(defn f-or [f default]
  (fn [& args]
    (or (apply f args) default)))

Benjamin09:12:29

nvm I realized that is not even what I need

Stuart14:12:19

Is their a way to update a key in a map using update if it doesn't exist. e.g. this (update {:x 0} :y inc) would result in {:x 0 :y 1} I thought fnil could maybe help me here, but I can't figure it out. I thought fnil on the key to return 0 instead of nil? (update {:x 0} (fnil :y 0) inc) but that still NullPointerException I presume from inc trying to inc nil

Stuart14:12:06

Got it, (fnil inc 0) , not really sure how that is working

Ben Sless14:12:12

fnil is on the function which gets the nil not returns it

Stuart14:12:47

ah, so instead of inc trying to inc nil, it gets passed 0 instead of nil. Ok, that makes sense thanks!

πŸ‘ 1
sheluchin14:12:29

Is there a Clojure equivalent to Python's NotImplementedError? Just an exception to raise in stub functions that don't have an implementation yet.

Alex Miller (Clojure team)14:12:08

UnsupportedOperationException ?

sheluchin14:12:46

It's idiomatic to use as I described?

sheluchin20:12:24

Another Python comparison: does idiomatic Clojure also prefer EAFP over LBYL?

dpsutton20:12:29

just posting this here because i was unfamiliar with the terms: https://devblogs.microsoft.com/python/idiomatic-python-eafp-versus-lbyl/

πŸ‘ 2
dpsutton20:12:36

(for others who are interested)

ghadi20:12:00

exceptions are the reality of the platform, but clojure programs don't use exceptions as promiscuously as python

ghadi20:12:45

catching exceptions "at the edge" (I/O generally) and turning that into information (aka maps) is a good strategy

ghadi20:12:57

catching KeyError looks wild to me as a clojure programmer

sheluchin20:12:01

Could you elaborate on this point please?

ghadi20:12:58

Alex covered it below

dpsutton20:12:26

python iterators don’t check some notion of hasNext but instead catch an exception right? Exceptions are used for control flow if i remember correctly

sheluchin20:12:30

So a StopIteration is raised to indicate the end of the iterator.

Alex Miller (Clojure team)20:12:24

it is idiomatic in Clojure to raise exceptions for exceptional (unexpected) situations and to NOT raise them for control flow or when you have an invalid input in the range of expected inputs

sheluchin20:12:00

So if a function has some range of expected inputs but receives an argument that's out of range, what is the strategy to take if the function cannot logically complete? (get [0 1] 2) => nil but it's a simple case.

sheluchin20:12:41

I know it's also bad form to include nil values in maps...

seancorfield21:12:11

@alex.sheluchin It depends what you mean by "out of range". You show get on a vector (which folks don't often use) but get returns nil in a lot of cases that surprise beginners -- and it is designed to allow nil-punning (which is idiomatic in Clojure).

seancorfield21:12:15

dev=> (get [0 1] 2)
nil
dev=> (nth [0 1] 2)
Execution error (IndexOutOfBoundsException) at dev/eval36734 (dev.clj:1).

Alex Miller (Clojure team)21:12:58

if you're returning a primitive (long or double) that affects your choices

seancorfield21:12:04

If the function cannot process the inputs and cannot return a "sensible" result in that case, an exception makes more sense.

Alex Miller (Clojure team)21:12:40

if you're returning a coll or seq or other thing that is commonly used with nil punning, that affects it, etc

Alex Miller (Clojure team)21:12:25

there is of course a wide range of gray area here :)

seancorfield21:12:45

It Depends(β„’) πŸ˜†

Alex Miller (Clojure team)21:12:56

Clojure (aggressively) supports polymorphic operations on nil in the core, so it often makes sense in that context to return nil (which allows you to nil pun or chain with other such ops)

sheluchin21:12:59

lol I take "it depends" as an indicator that I'm starting to ask the right questions πŸ™‚

seancorfield21:12:31

In JVM-based languages, there are also two very different "types" of exception: β€’ An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch. β€’ The class Exception and its subclasses are a form of Throwable that indicates conditions that a reasonable application might want to catch.

Alex Miller (Clojure team)21:12:34

it means you're out of the range of "idiom" and into "design" :)

Alex Miller (Clojure team)21:12:18

I don't actually think the Error/Exception split is that important personally

seancorfield21:12:20

It's less important in Clojure, for sure.

Alex Miller (Clojure team)21:12:25

they both mean something unexpected happened but convey a very coarse level of severity. there are more interesting differences to convey (like with cognitect anomalies)

seancorfield21:12:45

Yes... I was just about to link to that library! πŸ™‚

seancorfield21:12:55

I really like the categories there and throwing ex-info with an anomaly in it works really well.

seancorfield21:12:11

I think the Exoscale folks have a variant of that as well...?

sheluchin21:12:53

I'm actually not too familiar with the term "nil punning" and what it means. I get it from the description https://lispcast.com/what-is-nil-punning/, but I'll have to read the rest to understand it better. I guess it's just the falsiness of nil?

Alex Miller (Clojure team)21:12:39

for example, with the new parse-long etc functions being added to core in 1.11, the expected range of inputs is "a string". So your cases are: β€’ valid string => long value β€’ invalid string => nil (here we opted for a sentinel value indicating invalidity, in particular the value nil which is useful for nil punning) β€’ not a string => throws

Alex Miller (Clojure team)21:12:35

(if-let [port (parse-long port-str)] port (report-bad port-str))

leif21:12:59

Hey, looking at cljs-ajax (https://github.com/JulianBirch/cljs-ajax), and I'm wondering what the best way to log responses is. When I callΒ `(println requrest)`Β , for theΒ `:body`Β section I get something like:Β `#object[org.eclipse.jetty.server.HttpInput 0x186d26d3 org.eclipse.jetty.server.HttpInput@186d26d3]`.

leif21:12:30

Where as I'd rather be able to log the body as, say a string (or byte vector).

leif22:12:33

I guess a simpler question would be: "How do I print out the contents of an HttpInput?

sheluchin22:12:16

Interesting design there @alexmiller. Two types of invalids... :thinking_face: I see what you mean by different levels of severity. Thanks for the input on this issue everyone. I think the bottom line answer was "it depends" πŸ™‚ but it does shed some light.

hiredman22:12:33

and if you google org.eclipse.jetty.server.HttpInput you will see that it is an InputStream

leif22:12:39

@hiredman Sure. But I don't know the best way to print out the contents of an InputStream buffer in clojure.

leif22:12:08

And google seems to be failing me here.

leif22:12:15

(Unless I"m just supposed to use Java APIs?)

hiredman22:12:36

an inputstream isn't a buffer

hiredman22:12:52

the data may not even be present yet

hiredman22:12:01

reading the inputstream may block until it arrives

hiredman22:12:39

and inputstreams (with some exceptions) are not rewindable, they don't keep track of what has been read, and you can't read from it again

hiredman22:12:32

so something like slurp will turn it into a string, but there are consequences to that

leif22:12:45

Wait, if there isn't a buffer, then would that potentially lock up the browser?

leif22:12:02

Or is data silently dropped if there isn't something to read it at the exact moment data comes in?

hiredman22:12:05

that isn't in the browser

leif22:12:36

Right, the InputStream is server side, but its connected to a browser on the other end, no?

hiredman22:12:35

there are buffers, on the browser side and on the server side, potentially lots of them, but that is kind of below the level of abstraction provided in ring there

leif22:12:51

Ah, okay.

leif22:12:58

That makes sense, thanks.

hiredman22:12:20

you get an inputstream, and you can read from an inputstream, and that is a side effecting operation, and once you've read from the it you cannot read again (except some streams support mark and some don't, but that isn't reflected in the class hierarchy because http://java.io's class hierarchy is a mess)

leif22:12:50

Ya, this makes sense.

leif22:12:55

Its like a pipe.

leif22:12:31

But then given that, any idea what the best way to log its contents if it does come in?

leif22:12:49

(Spinning up a new thread for each request seems...well a bad idea for many reasons...)

hiredman22:12:27

if you are using ring you are doing a thread per request

hiredman22:12:13

there are provisos and whatever, but it is fundamentally a synchronous view on http

hiredman22:12:22

it generally works pretty great

leif22:12:02

Okay. I do think I'm using ring.

hiredman22:12:17

jetty, what most people default to using when making ring apps, runs requests on a threadpool

leif22:12:25

(I'm following the example in the dev/user.clj folder pretty closely.

hiredman22:12:37

dev/user.clj folder of what?

hiredman22:12:12

ring is just kind of an interface definition, it defines how an http request and response can be represented as maps being passed to functions

hiredman22:12:36

so for a given webserver, like jetty, you have an adapter that adapts jetty to ring

hiredman22:12:14

if you are coming from some other runtime (the browser, ruby, python, etc) it can be surprising how cavalier people often are with threads

leif22:12:17

> if you are coming from some other runtime (the browser, ruby, python, etc) it can be surprising how cavalier people often are with threads As in threads with clojure, or threads in those other runtimes?

leif22:12:34

Since I thought python and the browser were (mostly) single threaded.

leif22:12:01

(modulo things like green threads.)

hiredman22:12:50

in clojure, the jvm in general

leif22:12:55

Ah, I see.

leif22:12:16

I mean, I'm fine with threads, just worried that concurrency is very tricky to get write.

leif22:12:34

Although I guess if I'm just logging then inter-thread communication doesn't matter all that much.

emccue03:12:12

noticing this thread here - i think this will do it for you

(require '[clojure.pprint :as pprint])

(defn debug-log-request [request]
  (let [body-str (slurp (:body request))]
    (pprint/pprint (assoc request :body body-str))
    (assoc request 
           :body 
           (ByteArrayInputStream. 
             (.getBytes body-str 
                        StandardCharsets/UTF_8)))))

(defn handler 
  [request]
  (let [request (debug-log-request request)]
     ...))

Filip Strajnar23:12:38

hello, me again πŸ™‚ I'm wondering if :require is a function, or macro or keyword?

Filip Strajnar23:12:56

i don't think i understand the utility of keyword very well

hiredman23:12:12

it is a keyword, part of the syntax of the ns macro

seancorfield23:12:15

:require is a keyword. I suspect you're referring to its use in the ns macro? That macro uses several keywords to signify operations that need to be called.

seancorfield23:12:39

There's a function clojure.core/require which, ultimately, the ns macro will expand into calls of.

Filip Strajnar23:12:02

yes, I am refering to ns macro

seancorfield23:12:11

ns is probably the most complicated piece of "syntax" that beginners have to learn.

Filip Strajnar23:12:30

the fact it's a keyword makes sense but, i don't understand why is it the first item in list then (:require ...)

Filip Strajnar23:12:38

usually first item is a function

Filip Strajnar23:12:08

I'm referring to this

seancorfield23:12:16

dev=> (macroexpand '(ns foo.bar (:require [clojure.string :as str])))
(do 
  (clojure.core/in-ns (quote foo.bar)) 
  (clojure.core/with-loading-context 
   (clojure.core/refer (quote clojure.core)) 
   (clojure.core/require (quote [clojure.string :as str]))) 
  (if (.equals (quote foo.bar) (quote clojure.core)) 
    nil 
    (do 
      (clojure.core/dosync (clojure.core/commute (clojure.core/deref (var clojure.core/*loaded-libs*)) clojure.core/conj (quote foo.bar))) 
      nil)))

hiredman23:12:21

that is the synax of the ns macro

hiredman23:12:42

macros to some degree create their own mini-languages

hiredman23:12:19

it's like :let in a doseq or for

Filip Strajnar23:12:58

i'm specifically wondering about the : part

hiredman23:12:04

it is a keyword

seancorfield23:12:09

Keywords always start with :

hiredman23:12:10

keywords start with ':'

Filip Strajnar23:12:14

i assume : makes something a keyword instead of symbol, is that correct?

seancorfield23:12:41

:foo is a keyword and it evaluates to itself.

seancorfield23:12:56

foo is a symbol and it evaluates to whatever it is bound to.

Filip Strajnar23:12:01

alright, so when i'm making an expression with parentheses, the first item in list can be either a keyword or a function?

Filip Strajnar23:12:09

or can it even be anything?

hiredman23:12:20

it can be anything

seancorfield23:12:27

That's a slightly complicated question to answer.

seancorfield23:12:36

Inside ns those are not function calls.

hiredman23:12:38

there is no "expression with parentheses"

hiredman23:12:45

you are making a list

hiredman23:12:21

macros can rewrite the code however they want, and the normal rules of evaluation don't apply

Filip Strajnar23:12:21

but the lists there are being evaluated? specifically here

hiredman23:12:30

those are not evaluated

Filip Strajnar23:12:31

since they aren't '()

seancorfield23:12:33

(ns ...) is a "function call" to the ns macro and all the rest are arguments. Macros do not evaluate their arguments so ns is invoked with a bunch of lists -- symbolic expressions.

seancorfield23:12:54

Functions evaluate their arguments. Macros do not.

Filip Strajnar23:12:08

and ns is a macro?

Filip Strajnar23:12:14

okay, that clarifies a ton

hiredman23:12:15

those are passed to the ns macro, unevaluated, and the ns macro does whatever it wants with them, and returns some code that is compiled in place of the call to the macro

seancorfield23:12:16

Yes. A very complex macro.

Filip Strajnar23:12:20

thank you both very much πŸ™‚

Filip Strajnar23:12:27

that clarifies a lot

seancorfield23:12:32

See the macroexpand call I posted above.

Filip Strajnar23:12:46

i'm afraid I'm not that advanced to be able to understand it

seancorfield23:12:59

That shows how ns expands to calls to in-ns, refer, and require amongst other things.

Filip Strajnar23:12:34

yea, the moment i was told the list isn't being evaluated, it made all the sense

awesome 1
Filip Strajnar23:12:52

that was the part that was bothering me

seancorfield23:12:01

A simpler example of a macro and its expansion:

dev=> (macroexpand '(when (zero? x) (println x "is zero!")))
(if (zero? x) (do (println x "is zero!")))

Filip Strajnar23:12:43

yea, the println list was not evaluated

Filip Strajnar23:12:50

it was only being passed through

Filip Strajnar23:12:54

makes all the sense

seancorfield23:12:00

so when you write (when cond expr1 expr2 expr3) it is expanded to (if cond (do expr1 expr2 expr3)) and then that is evaluated.

Filip Strajnar23:12:26

again, thanks to both