Fork me on GitHub
#clojure
<
2021-07-10
>
furiel08:07:29

Hello, I am struggling with firing up named tools described in: https://clojure.org/reference/deps_and_cli#_using_named_tools

$ clj -Ttool list
-T is no longer supported, use -A with repl, -M for main, or -X for exec
For me this looks like a version problem. I am using
$ clojure -version
Clojure CLI version 1.10.3.855
that is the same version from the https://clojure.org/guides/getting_started. Not sure about the repair version, but according to github tags, 1.10.3 is the latest stable, but there is a 1.11.0-alpha1. Is named tools available in alpha only? If so, how can I get the proper download link? For
listing is not enabled, so I could not figure it out on my own. Can someone help?

p-himik08:07:07

Try the download link for 1.10.3.905 - that's the version mentioned in the original announcement.

furiel08:07:37

Thanks, it worked!

seancorfield22:07:19

It's also worth noting that 1.10.3 and 1.11.0-alpha1 are the versions of Clojure (the language) itself, not the CLI. The CLI has it's own versions -- http://clojure.org/releases/tools -- where the prefix indicates which version of Clojure it will use by default if you don't override it. But you can use any version of Clojure (from 1.0 all the way up to the latest 1.11 alpha) with the CLI.

ChillPillzKillzBillz18:07:44

Assuming I've got a code like the following: (def ch (chan)) (go-loop [] (let [val (<! ch)] (prn val)) (recur)) what is a good graceful way to kill both the channel & the go-loop?

ChillPillzKillzBillz18:07:15

that's it? wow! Thanks! I thought the go-loop part will be more complicated than that!! Thanks again!

Ben Sless18:07:20

@abhishek.mazy make sure to always check the value you take out of the channel If it's nil then don't recur

Ben Sless18:07:43

Also, depending on what you send, make sure to distinguish between when-let and when-some because the former's assumptions might be violated if you take false out of the channel

Ben Sless18:07:04

Wouldn't you want to shut down gracefully, though?

Ben Sless18:07:36

Not necessarily, a go-loop returns a channel, you can just wait on-it

Ben Sless18:07:11

The downside is that it "colors" your code

emccue19:07:08

top 10 blog posts of the decade

ChillPillzKillzBillz18:07:41

@ben.sless by wait on go-loop you mean <!! on the go-loop return... right?

Ben Sless18:07:04

i.e. (<!! (go-loop ,,,))

👍 3
ChillPillzKillzBillz18:07:32

hey btw, would this work? (let handler (go-loop...)) (<!! handler)

Ben Sless18:07:35

yes, everything in Clojure returns a value. Go blocks return a channel which will contains the value of the expression which runs within them

ChillPillzKillzBillz18:07:05

I mean if the above 2 lines are in 2 completely different locations in the code, it is still going to wait for the handler to die... right?

Ben Sless18:07:39

Your code won't compile

Ben Sless18:07:59

Lexical scope constraints apply

ChillPillzKillzBillz18:07:11

err.... I must have missed something... assuming the handler is available in the scope... still?

Ben Sless18:07:58

(let [handler (go-loop ,,,)] ,,, (<!! handler))
Or do you mean calling handler outside of the let's scope?

ChillPillzKillzBillz18:07:53

oh yes this works as well... but in general if instead of let, if handler was a def , even then within the same ns you should be able to <!! handler) no?

Ben Sless19:07:35

Yes, but please don't def things with a state which might change over time, it's a recipe for trouble

ChillPillzKillzBillz19:07:56

I know 😄 I'll keep that in mind. Thanks for all your help!! Take care

👍 3
Ben Sless19:07:46

Godspeed 🙂

Ben Sless18:07:46

in short, async code and non-async code don't mix well together

p-himik18:07:24

And yeah, since Clojure (but not ClojureScript) has blocking ops, you can avoid coloring.

Ben Sless18:07:41

Then you either need to immerse your code in async-ness, or not. Giving it two distinct colors

Ben Sless18:07:43

What if you were waiting for more than one block to exit? What if you wanted to perform different actions based on which block exited? calling <!! on a go-block "breaks out" of async world to synchronous world

Ben Sless18:07:16

but if you're working with more than one channel and you don't want to block a thread, you have to wrap it all in a go-block

Ben Sless18:07:25

But that won't synchronize, either. I need the result

Ben Sless18:07:31

or at least, to wait for it

Ben Sless18:07:48

alts with default returns immediately which I might not want

Ben Sless19:07:46

Which is exactly what go-blocks give me

Ben Sless19:07:37

The only way to "synchronize" but not block is using go-blocks, which means I had to keep my code in go-blocks, which means it has color

Ben Sless19:07:28

Possible. You just got up, I'm about to go to sleep, too many opportunities for communication error

Ben Sless19:07:37

Coffee is good, highly recommended

Ben Sless19:07:26

Thanks, we'll pick it up later. Take a look at the functions color post, it lays it out well

didibus01:07:40

I think Ben just meant, if you want to work with non thread based concurrency you're stuck using go everywhere

didibus01:07:12

You can use <!!, but now you're doing regular old OS thread concurrency, which defeats the point of using go in the first place.

☝️ 3
Ben Sless16:07:24

A really good example of a function with color is core.async/map

Ben Sless16:07:14

merge takes channels, returns channel

Ben Sless16:07:29

the meaning of the content of the channels is somewhere else

Ben Sless16:07:23

If you program with the async versions of map, merge, filter, etc, you're programming with their async versions

Ben Sless16:07:33

i.e. not your regular functions

Ben Sless16:07:42

suddenly you're in a world of channels in, channels out

Ben Sless16:07:56

another thing is how exceptions aren't propagated "properly"

didibus22:07:35

I agree with that there is still color, but I don't know if having it is as bad as in JS, because channels are continuous streams, it seems like the way CSP works, the normal programming style just doesn't apply. Where as with promise it makes sense, because functions normally return a single result, so it be great to just have something automatically sync the async function call with the thing that waits for its single result. But processes are meant to run more like services, so a channel could return many things, so having them behave like collections where you can use transducers over them, but also understanding their async nature, I don't think you can retrofit this into a normal programming flow, like its just a different style of programming.

Ben Sless18:07:24

up and up you go until you reach the "breakout point"

Ben Sless18:07:35

then all the code up to that point is "contaminated"

Ben Sless18:07:46

because it all had to be written in the context of running in a go block

Ben Sless18:07:46

Which is why I prefer building "workers" with core.async and not use puts and takes directly

p-himik18:07:59

> calling `<!!` on a go-block "breaks out" of async world to synchronous world That's exactly why coloring is not a problem in Clojure. Within a single go block you can treat <! as blocking as well - the whole block is sequential in nature. In e.g. JS you just can't block. You simply have no way of waiting for an async op to continue your sync flow. That's where coloring exists.

Ben Sless18:07:41

It isn't a strong color but it still has a distinct hue 🙂 By which I mean it leaks. You feel it when you call <!! in a go block

p-himik19:07:55

To be clear, I'm talking about calling <!! outside of a go block. In a purely sync context.

emccue19:07:05

its not a problem at the abstraction level. It is still a problem at the performance level

emccue19:07:23

i.e. you can always turn Chan[T] , Promise[T] , or Future[T] into T - which is different from JS

emccue19:07:46

but you don't get the full performance benefits of being "async" without keeping your T wrapped

emccue19:07:07

that being said, if we pretend we are go and have VM threads instead of OS threads (which is the future, if not immediately), then chan still makes sense as an abstraction for running a concurrent process with a timeout or with alternatives or whatever

Jacob Rosenzweig21:07:14

I've never seen this function naming style but is it common to use arrows in names? E.g.

defn csv-data->maps [csv-data]
  (map zipmap
       (->> (first csv-data) ;; First row is the header
            (map keyword) ;; Drop if you want string keys instead
            repeat)
	  (rest csv-data)))

p-himik21:07:04

It's rather common, yeah. I often use it for maps, like id->entity.

Jacob Rosenzweig21:07:06

@U01F1TM2FD5What do you mean by using it as a "map"? DO you mean the returned value is a map or the arg itself? @U11BV7MTK speaks about type signatures out of the thread.

didibus21:07:10

It's a common naming convention for functions that convert one thing into another, where it goes:

(defn x->z
  "Converts an x into a z"
  [x]
  ...)

p-himik21:07:18

Seems like you @-ed a wrong person. :) I mean this: (def id->entity {1 entity-1, 2 entity-2})

Jacob Rosenzweig21:07:59

@U0K064KQV very succinct explanation, thanks.

didibus21:07:01

Other languages normally use to for the same, so when you see x->z Think of it as: xToz

Jacob Rosenzweig21:07:13

I like some of the symbols in the function names, prevents verbose names.

didibus21:07:11

You'll also see it sometime for constructor functions (sometimes called factory functions in some languages) where it'll have ->x which means to x

didibus21:07:36

(defn ->person
  "Construct a person map with values as provided from name and address"
  [name address]
  {:name name
   :address address})

p-himik21:07:31

Clojure itself generates functions with -> for records, at the very least.

didibus21:07:56

And again in other languages normally they'd use to instead, like toPerson(name, address)

didibus21:07:13

Ya, so when you create a record, Clojure will auto-create two such functions:

(defrecord Person [name address])
(->Person "John" "1111 street")
(map->Person {:name "John", :address "1111 street"})

didibus21:07:45

Which you can read as: toPerson and mapToPerson

didibus21:07:44

Also, generally Clojure distinguishes an uppercase to indicate a record or type like Person with uppercase P because it is a record. While it uses lowercase to indicate the entity is a common data-structure like person would generally imply it's a map or a vector

dpsutton21:07:15

when a function like this, often is basically a type signature f: csv-data -> maps and just keep the type signature. When a var, often for the same or a map with keys of the left side of the arrow and values the right side of the arrow. This gets a bit hazy too as maps are invocable as functions so you can treat it as the same case or not

Jacob Rosenzweig21:07:03

Sorry I lost you at "When a var, often for the same or a map with keys of the left side of the arrow and values the right side of the arrow." I get the idea of expressing the function type signature in the name though.

dpsutton21:07:02

(defn x->y [x] <computes y from x>) is a var named x->y (let [id->people {1 person-1 2 person-2}] ...) here the local of id->people is a map of id mapped to people. it is a map. but it's also a function since maps are invocable. so its kinda different, kinda the same

lukasz21:07:19

and Clojure auto-generates functions for records in the same way eg. map->SomeRecord

pavlosmelissinos22:07:47

Honest follow up question: I used to name conversion functions as a->b s as well but I've recently started to doubt it... If you have a function that "creates" a person, why not just name it "person"? Why does the type of input and the information that the function converts something to something else have to be part of its name?

pavlosmelissinos22:07:05

I can actually think of two reasons: • If the function that converts a to b is called b, what name would you bind the actual result of the function to? and • having multiple types of input (a->b, c->b etc) - which one would the function named b be? But even so I'm generally content with shorter names :man-shrugging:

p-himik22:07:58

I don't think there is a generic answer to any of those questions - it all depends on the context and on personal preferences, and even those fluctuate within the same person.

👍 3
seancorfield22:07:06

If you haven't already, I'd highly recommend reading Zach Tellman's "Elements of Clojure" which talks in depth about naming.

👌 9
pavlosmelissinos22:07:42

Yeah that's true @U2FRKM4TW ! I guess what I was trying to convey is that, from personal experience, I've found that trying to avoid the extra symbols has resulted in more readable code for the most part. It goes without saying that ymmv 🙂 There's actually a free sample online that includes the naming section and it's quite insightful, thanks @U04V70XH6

potetm23:07:19

Not quite the same, but related: I definitely think person is all-around better than fetch-person or get-person.

potetm23:07:24

I do less of the a->b stuff lately as well. I find it does work when the operation describes an actual conversion. For example, if you are transferring products from one system to another: product-a->product-b or (and this one’s odd) a->b:product

potetm23:07:02

May I ask why having a verb is helpful?

potetm23:07:19

(stepping out. bbl)

pavlosmelissinos23:07:36

> if your function name is just a noun, what does the naming tell you about what the function does? > Well, take keys and vals for instance. Would ->keys or map->vals be better names for them? I don't see verbs as a requirement in function names but yeah sometimes they do give some context, e.g. in a namespace called email, fetch could be a nice name for a function (a similar example is actually in Elements of Clojure as well 😄 ).

didibus00:07:25

If you don't care about the input type, then just use ->person

didibus00:07:22

I do think its a Lisp-1 problem. You will for sure want to do: (def person (->person "John")) And you can't do:

(def person (person "John"))
(def other-person (person "Bob"))

potetm00:07:13

> if your function name is just a noun, what does the naming tell you about what the function does? Ah. My current take: I read (person "John") as “it returns a person named John.” I don’t care what it does to get there.

potetm00:07:42

and as for naming:

(def john (person "John"))
(def bob (person "Bob"))

(defn foo [name]
  (let [p (person name)] ...))

potetm00:07:05

I really prefer the component thing to signal i/o.

potetm00:07:15

(person conn "bob")

potetm00:07:52

^says to me “It’s going to use conn to get the person "bob"

didibus00:07:05

Ya, like it's okay that m is map, which is necessary because map is a function, but then I find using p becomes cryptic, because p is not generally understood as a person, maybe you also have a provider entity in your app, and now things get weird, etc.

potetm00:07:33

@U0K064KQV Notice I used it for a locally-scoped thing.

potetm00:07:07

So if there’s a question, “what’s p ?” you look up at the definition, “oh, p is a person”

potetm00:07:25

Also: if you do it regularly, it becomes like m for map in a codebase.

didibus00:07:41

I saw it, and still think its not best for readability

potetm00:07:20

like when you’re doing an http call?

potetm00:07:50

I usually still pass that stuff in. It allows you to do things like rate limiting and circuit breaking.

potetm00:07:07

Right AWS usually requires a client of some sort for connection pooling.

didibus00:07:08

I would probably agree with others, functions perform computation, seem logical that they tend to be verbs, and variables define things, seems logical they tend to be nouns.

didibus00:07:06

The "but my function is pure so it can be treated as a value" I don't really buy into, because it still needs to be parameterized, even if all it does is lookup the right output for the given input, that feels like an action to me

potetm00:07:00

I have no idea what you’re trying to say 😂

didibus00:07:18

What gets weird are getter like "keys", but to me that makes sense a noun, because its really like a kind of field access

potetm00:07:16

@U8QBZBHGD that’s basically what I do. I make a namespace for a particular service. Everything in there is expected to probably do some i/o.

potetm00:07:35

Though I don’t use a verb in the name.

didibus00:07:04

Ya, I'd verb them too.

didibus00:07:29

I feel I'd rather the service be a detail I don't care about

didibus00:07:47

So having a namespace for the service seems strange to me.

didibus01:07:32

Like as a Clojure wrapper for Zulip? Or as like where you put your business logic for things that need stuff from Zulip?

didibus01:07:28

As a zulip wrapper I do the same thing. And normally use get for lookup queries, and fetch for search

didibus01:07:38

So get-message is lookup, so would take like an id, where as fetch-message would be a search so maybe say it takes a regex to find all messages of match in their content

didibus01:07:33

Euh, sorry, I never use fetch lol, I meant find

didibus01:07:24

Ya, I mean if I'm wrapping an API, I stick to their names

didibus01:07:53

But I tend to not wrap APIs, and use the Java client directly though

didibus01:07:06

So like on some API if I need to lookup a message somewhere I'd probably have a get-message function for it in the API namespace. If another API also needs to do the same then I'll just move the function to a shared helper namespace

didibus01:07:40

And if it was a search, I'd call it find-message

didibus01:07:50

Right, so this is my apps get-message for some API. The fact it uses Zulip would be like an implementation details. So inside get-messagemaybe I use Zulip, or something else, and maybe that has its own get-message. That's what I meant like this is different then if you created a ZulipClient namespace which was like a Clojure based client for Zulip

didibus01:07:54

Anyways, style questions have no good answers, really just personal preference.