Fork me on GitHub
#beginners
<
2020-04-30
>
Oz00:04:39

Hi, Joker says my do blocks are redundant, what does it mean?

Hi, Joker says my do blocks are redundant.
What does it mean?
(defn request-rows
  [range robot]
  (loop [i range]
    (when (> i 0)
      (do (tags/request-tag (str "ROBOT_" (:id robot) "_ROW_" i "_FISH_CELL_ID") 6000))
      (for [feeder (:feeders robot)]
        (do (tags/request-tag ((str "ROBOT_" (:id robot) "_ROW" i "_AMOUNT_LEFT_TO_FEED_"(:id feeder))) 6000)))
      (recur (dec i)))))
Blessed be you.

noisesmith00:04:05

@ozfraier many clojure macros (including loop, fn, and when,) contain an implciit do

noisesmith00:04:26

@ozfraier also do with only a single form in it is always redundant

noisesmith00:04:56

(do x) can be replaced with x, in any case where (do x) is valid

Oz00:04:49

Thank you, I will try to do without the do

noisesmith00:04:39

on that note, try is usually not needed either :D

😂 4
Chris K00:04:57

Question about clojure's == equality test. so I get that it is equality check with an additional type check, but when I do:

(== 1 1.0) ;;true
(== "X" \X) ;; false
So does clojure consider all the number related data types as same type? But not for strings and chars?

andy.fingerhut05:04:46

@sunchaesk == is not exactly = with additional type checks. For example (== 0 0.0) is true, but (= 0 0.0) is false. There are three "numeric categories" that are never equal to each other for = . See https://clojure.org/guides/equality

Chris K11:04:07

Thxs I'll look into it

andy.fingerhut14:04:54

That article is for Clojure specifically. And may differ from Clojurescript in ways I do not know

dpsutton00:04:04

check (doc ==)

dpsutton00:04:12

and i'm not trying to be snippy. i honestly didn't know the answer but trying to show you how i knew where to look to find an answer

Chris K04:04:01

Oh alright. I was just reading the examples on the clojure community website and I didn't quite get the documentation there, BUt still thxs for your answer

noisesmith00:04:22

there's nothing in clojure or java that considers "X" and \X the same

noisesmith00:04:32

you could make your own test function of course #(= (str %1) (str %2)) or more generally #(apply = (map str %&))

Chris K04:04:08

Oh alright. I was just reading the examples on clojure docs website, and just had this question while looking at the equality test examples. thxs for answering

seancorfield00:04:44

@sunchaesk I don't get false, I get an exception from that second comparison:

user=> (== "X" \X)
Execution error (ClassCastException) at user/eval143 (REPL:1).
class java.lang.String cannot be cast to class java.lang.Number (java.lang.String and java.lang.Number are in module java.base of loader 'bootstrap')
user=>
and the source of it shows it calling into clojure.lang.Number

seancorfield00:04:43

As @dpsutton says, if you look at (doc ==) it says that it works with "nums" so I'm not sure how/where you're running that == between a string and a character?

Chris K04:04:10

Oops I meant exception. No I wasn't using any of the codes but I was just reading the examples on the clojure docs website and just had this question

Chris K04:04:13

Thxs for answering tho

hiredman01:04:05

likely cljs not clj

dpsutton01:04:48

then even easier i suppose > Returns non-nil if nums all have the equivalent > value, otherwise false. Behavior on non nums is > undefined.

hiredman01:04:08

Yeah, same thing, just explains the lack of an exception

seancorfield01:04:53

Ah, yeah, cljs having different behavior to clj makes sense(!).

Rob Aguilera01:04:27

Hello 👋, first time poster. Working my way through Clojure for the Brave and True and confused on the example below. Why does the vector need the extra braces?

(into {:favorite-emotion "gloomy"} [[:sunlight-reaction "Glitter!"]])
; => {:favorite-emotion "gloomy" :sunlight-reaction "Glitter!"}

dpsutton01:04:58

(into existing-map [ [key-1 val-1] [key-2 val-2] ]

dpsutton01:04:21

putting space around them to make them not bleed together. its a vector. each element of which is a two element vector, key and value

dpsutton01:04:38

so the vector doesn't need extra braces, its a vector of two element vectors

Rob Aguilera01:04:37

Thanks for the quick reply! Clears it up perfectly.

👍 4
noisesmith01:04:24

the way maps work, the input could have been [{:sunlight-reaction "Glitter!}] as well - you don't see this form as often though

Rob Aguilera01:04:24

Interesting, I feel like I see that all the time in JavaScript. An array of objects.

noisesmith01:04:57

right, a map accepts either a map, or a vector (treated as a key-value pair, must be exactly length 2)

noisesmith01:04:20

there's also assoc which takes a freeform series of keys and values

Rob Aguilera01:04:42

That's the difference and which is pretty mind bending for a humble JS dev learning Clojure.

noisesmith01:04:44

user=> (assoc {:a 1} :b 2 :c 3)
{:a 1, :b 2, :c 3}

noisesmith01:04:07

we use maps a lot, so we have many features and conveniences around them

Rob Aguilera01:04:17

Oh, I didn't know about assoc yet, wow.

jsn08:04:20

@ozfraier even if when and for didn't have implicit do , your code still doesn't make much sense to me. Why are you wrapping single expressions in explicit do 's? do is only useful for grouping multiple expressions.

Oz08:04:57

@jason358 I refactored a do block that had multiple expressions into a for block, Also, I felt like I needed the do to run the request-tags function immediately for side effects, but that may be voodoo programming.

jsn08:04:47

"immediately" as in "not lazily"? do doesn't do anything for non-laziness, iirc

jsn09:04:49

Clojure is not a lazy-by-default language anyway, it doesn't make sense

jsn09:04:57

if your expr is in "lazy" context, wrapping it in do still does nothing, your do will now be in the same "lazy" context

jsn09:04:17

cf.

user=> (do (for [a [1 2 3 4]] (println a)) 1)
1
user=> (do (for [a [1 2 3 4]] (do (println a))) 1)
1
see, println isn't evaluated either way, because it's in for 's lazy sequence that doesn't get forced

Oz09:04:56

I think I confused do an doall in that case. What about

(doall (for [a [1 2 3]] (println a)))
?

Oz09:04:47

I wonder if there is a clojure repl for the smartphone :P

Oz09:04:19

There is one called replete for anyone interested :)

jsn09:04:47

doall does the trick, but what you are really looking for is doseq 🙂 as in (doseq [a [1 2 3]] (println a))

Oz09:04:09

cool, thank you for explaining simple_smile

hindol11:04:03

If I have code like this,

{:x (f)
 :y (g)}
Is f guaranteed to run before g?

Ewa Trzemżalska11:04:23

I dont think so, cause at map order is random.

noisesmith11:04:17

yeah, before (f) runs, it is read (to create the symbol inside a list, which eval then compiles and runs), and the reader is what creates the hash map and is allowed to change the order

noisesmith11:04:08

@hindol.adhya on the other hand: (hash-table :x (f) :y (g)) will always call f before g, since the map is created after eval

teodorlu11:04:47

@hindol.adhya I would not rely on that. I would go for (let [x (f) y (g)] {:x x :y y})

hindol11:04:00

Okay, then I will use let as well.

noisesmith11:04:37

@teodorlu it's fine to dislike it on style grounds, but it's an explicit promise that args are evaluated left to right

👍 4
teodorlu11:04:06

@noisesmith I was referring to @hindol.adhya’s example

teodorlu11:04:03

Sidenote: if you rely on the order of f and g, they probably have some side effect. I'd consider renaming to f! and g!.

hindol11:04:03

@teodorlu I have considered that. They both read from a reader. I ended up not adding ! mostly due to preference.

👍 4
teodorlu11:04:36

Yeah, in that case I might agree. Hard to come up with a general rule. Clojure internally isn't 100 % strict on this either, both slurp and spit are missing !

noisesmith12:04:23

and read and println and ...

Viktor13:04:24

Hello everyone, I wanted to use `comment` macro  as java `/ /` analog

(comment
  1todo
  )
but I got
1. Caused by java.lang.NumberFormatException
Invalid number: 1todo
Is there a way I can workaround this ?

manutter5113:04:07

I don’t think there is. The reader has to read the complete (comment ... ) form, and it can’t do that if there’s an error inside the form.

Viktor13:04:13

Okay, got it , thank you!

Oz13:04:56

If I have a collection of a collection of strings, let's say

[["hello" "world"]["hello" "mars"]["hello" "moon"]]
And I want to call str on them so I get a collection of strings instead
'("hello world" "hello mars" "hello moon") ;;I don't really mind the type of collection
How do I get there?

Alex Miller (Clojure team)13:04:09

(map #(clojure.string/join " " %) [["hello" "world"]["hello" "mars"]["hello" "moon"]])

Oz13:04:07

Cool 🙂 actually, the space was not really part of the problem 😅 I came up with (map #(apply str %) [["a" "b" "c"]["d" "e" "f"]])

Alex Miller (Clojure team)13:04:23

yep, that'll work too if you don't need the space

noisesmith14:04:27

@e2d4f6 I use #_ instead of comment, it's more powerful

user=> #_(1todo)
user=>
it consumes the next item without caring as much about syntax, and doesn't even return anything (where comment returns nil) edit: this was something nrepl accepted but a real repl does not

Viktor16:04:02

user=> #_(1todo) 
Syntax error reading source at (REPL:1:9). 
Invalid number: 1todo 
Syntax error reading source at (REPL:1:10). 
Unmatched delimiter: ) 
user=> *clojure-version* 
{:major 1, :minor 10, :incremental 1, :qualifier nil}

Viktor16:04:24

@noisesmith Unfortunately it does not work on my side. What clojure version do you use ?

noisesmith16:04:10

*clojure-version* evaluates to {:major 1, :minor 10, :incremental 1, :qualifier nil}

noisesmith16:04:37

also, that error message makes me think you are using nrepl, I bet what you are seeing is an nrepl or reply lib bug

noisesmith16:04:59

if you are using lein, try lein run -m clojure.main instead of lein repl - that won't start a server, but will give you a direct repl, and I bet the form works there

Viktor16:04:14

I tried running: - clj - cider - lein run -m clojure.main unfortunately the same result

Syntax error reading source at (REPL:1:9).
Invalid number: 1todo

noisesmith16:04:01

oh... maybe I had it backward and only my nrepl accepts that :/

noisesmith16:04:07

yeah, sorry, #_ doesn't accept invalid forms, it looked like it did due to a corner case nrepl behavior for me locally

Viktor16:04:48

okay, thank you very much for helping !

noisesmith14:04:43

it doesn't even care about classic issues like unbalanced hash-maps:

user=> #_{1todo}
user=>
and just like comment , #_ does inline / multi-line commenting
user=> #_{1todo} 42
42

Eugen14:04:15

hi, I don't know what is wrong with this:

(comment
  (defn destructure [aa] (println aa)
    ;(let [{:keys [c d]} :as aa]
    ;  (println c " " d " ")
    ;  )
    )
  (let [xx {:a 3 :b 4}] (destructure xx))
  )

Eugen14:04:36

I get Unsupported binding form: :a

Eugen14:04:21

it's realy frustrating since I am reading the docs and I can't figure out whare I'm going wrong

manutter5114:04:51

In your let, you don’t want the :as there.

noisesmith14:04:27

yeah, the :as belongs inside the hash-map, but aa is already bound, so you don't want it at all

Eugen14:04:55

well, the code as it is does not work

Eugen14:04:03

please ignore the ; comments

dpsutton14:04:08

(let [xx {:a 3 :b 4}] (destructure xx)) i think is the only code under discussion?

dpsutton14:04:19

the answer is do not call destructure at all

dpsutton14:04:23

pretend that doesn't exist

noisesmith14:04:36

another issue is clojure.core/destructure exists and wouldn't like your map most likely

👍 4
dpsutton14:04:40

but not clear what you are trying to achieve

Andy Heywood14:04:12

I think the code as pasted is fine?

Eugen14:04:21

I'm trying to learn how to destructure function parameteres

noisesmith14:04:38

it would be, if that destructure was the one he was calling when he called destructure :D

Andy Heywood14:04:49

yeah exactly 😄

Eugen14:04:01

my goal is to have (defn [a b {:keys [x y]}) (println ))

Eugen14:04:56

the error message in repl could be more helpful

noisesmith14:04:51

you can use *e to see the full previous error, including stack trace etc.

noisesmith14:04:56

that's often enlightening

dpsutton14:04:57

oh i'm sorry. i didn't put together your destructure . my mind went to clojure.core/destructure. that's why i said pretend that doesn't exist. i'm sorry for my confusion

noisesmith14:04:37

eg. the stack trace would have revealed that clojure.core/destructure was called, and not the one in your ns, most likely

Eugen14:04:33

ok, so thanks to your help I figured it out - I was loading the whole file in repl - but my function was inside (comment ) so it was not created

Eugen14:04:52

that is why it failed and was harder to get to the cause

Eugen14:04:11

thanks for your time

👍 4
Alex Miller (Clojure team)16:04:38

that seemed surprising to me :)

noisesmith16:04:02

haha I saw you almost replying before, I am glad I got the chance to correct the misinfo

Alex Miller (Clojure team)16:04:08

#_ works by reading then discarding so seemed like it still has to read just as much as comment

Yuriy Malenkiy17:04:25

Hey everyone. Could I please get some guidance on how to organize projects and deploy them to a cloud platform? For a simple scenario, I would like to have a client (re-frame) and server (some REST Api framework) projects. AWS Beanstalk SE platform seems to assume these would be bundled/deployed as one application which isn’t ideal - it would be nice to treat these as components that can be updated separately. Beanstalk Docker adds additional overhead. Any help would be very appreciated. I’m open to other cloud platforms. Thank you!

phronmophobic18:04:09

if you don’t intend to bundle the client and server, how would you like to serve the client resources (html/css/js/etc.)?

Yuriy Malenkiy18:04:15

I don't exactly know 🙂 This may be part of the question. Beanstalk can serve static files but I don't have it working nor do I know if this is a good way to do it.

phronmophobic18:04:54

if you can serve them as static files, that’s typically the way to go

phronmophobic18:04:29

i’ve only used beanstalk with python. are there clojure oriented docs? otherwise i would follow the java focused docs and deploy as an uberjar

Yuriy Malenkiy18:04:23

It's https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/java-se-platform.html#java-se-namespaces, and I'm experimenting with uberjar. In Beanstalk, server and client would effectively be bundled/uploaded together (unless I create multiple Applications). Perhaps this would be easier in EC2? Is it more typical to bundle such components in one project? Just trying to understand what the options are and what's more common. Thank you.

phronmophobic18:04:51

if you have two repos, you could create two environments

phronmophobic19:04:57

if they’re in the same repo, then I would just bundle them together unless there’s a compelling reason not to

phronmophobic19:04:19

it kinda depends on how much experience you have with devops

phronmophobic19:04:59

I’ve heard good things about heroku, but I’ve been doing devops for a long time so I prefer something more flexible like beanstalk

phronmophobic19:04:34

for hobby projects, I use digital ocean and just deploy an uberjar. the uberjar also serves the static assets needed on the clientside

phronmophobic19:04:08

it means that my dev and production environments are very similar

phronmophobic19:04:00

the reason I use beanstalk for production is that it provides a deployment process pretty similar to one I would write myself out of the box. it’s fairly flexible and has sane default for auto scaling, rolling deployments, security groups, load balancing, etc.

Yuriy Malenkiy19:04:57

This is helpful. Thank you.

phronmophobic19:04:00

are you required to run on AWS?

Yuriy Malenkiy19:04:37

No. This is for personal project(s). Just trying to figure out cloud deployment.

phronmophobic19:04:37

yea, for personal projects, I just use digital ocean and deploy an uberjar. (by deploy, I mean I rsync the jar to the server and then start the server in an emacs daemon).

phronmophobic19:04:03

if you’re open to other options, it may be worth asking the chat what they recommend for deploying personal projects (without narrowing it down to AWS). I seem to recall heroku being one of the easier options to get started with.

Yuriy Malenkiy19:04:33

I am open to other options, and re-phrased original post slightly. Thanks!

j18:04:46

I'm using clj -m namespace-name to call the -main function from the command line. It seems to not just run -main, but all functions in the namespace? Is there a way to run just -main?

noisesmith18:04:13

clojure doesn't have a "compile only" mode, unlike many other languages. the solutionis to put everything that shouldn't run on file load into defns, and bind them with let inside main

noisesmith18:04:25

(or some other more sophisticated variation, but that's the basic one)

Alex Miller (Clojure team)18:04:47

clj -m will load the file and evaluate every top level form

j18:04:47

I'm calling a 3rd party library that seem to generate side-effects that I'd like to not execute until I call it in -main. I'm using def instead of defn on the 3rd party lib. I'm not sure how to best go about this

Alex Miller (Clojure team)18:04:40

a) don't use def or b) use delay

noisesmith18:04:02

don't use the lib, or change it to not put side effects in def, there's no other solution without forking the compiler(?)

j18:04:58

I think I understand now, thanks @noisesmith and @U064X3EF3!

Marcus18:04:06

hello all! I am working with clojure spec. Does anyone know the best way to express s/def for a value that must either be a non blank string or nil? For then non-blank part I have (s/and string? (complement str/blank?))

Alex Miller (Clojure team)18:04:02

you could wrap that in (s/nilable ...)

Marcus18:04:39

yes.. thought about that but that but then I must wrap both predicates?

Marcus18:04:57

(nillable (complement str/blank?))

Marcus18:04:17

and (nilable string?)

Alex Miller (Clojure team)18:04:29

I would do (s/nilable (s/and string? (complement str/blank?))) I think

Alex Miller (Clojure team)18:04:17

nilable is optimized to cover the nil case so it's better at the top level

Marcus18:04:39

Thanks. I'll try that out 🙂

Alex Miller (Clojure team)18:04:40

I've come to believe it would probably be useful to have a pred/spec specifically for non-blank strings

Alex Miller (Clojure team)18:04:02

given that it's both common and cumbersome

Marcus18:04:10

Well there you have my little contribution to clojure spec 😉

Alex Miller (Clojure team)18:04:30

well it wouldn't be that - we can optimize the impl

Alex Miller (Clojure team)18:04:03

I think the example above should gen

Marcus18:04:26

Yes. Worked fine 🙂

Marcus18:04:40

love your talks by the way 🙂

Michael W18:04:57

(def d {:one {:two {:three 3 :four 4 :five 5}}})

Michael W18:04:13

How to get :three and :five from that?

didibus18:04:55

Use get-in

Michael W18:04:06

So 2 get-in?

didibus18:04:36

Ya, one call to get-in for each

didibus18:04:01

You can also use destructuring if you prefer

pyry19:04:53

Something like (-> d (get-in [:one :two]) (select-keys [:three :five])) should work as well.

didibus19:04:08

With destructuring: `(let [{{{:keys [three five]} :two} :one} d] [three five])`

didibus19:04:39

But my personal favourite is just two get-in with a let, or a let where you get the inner map and then use that to extract three and five using keywords. Simple and readable

didibus19:04:35

I also prefer to use -> over get-in

pyry19:04:33

Reasonable points for sure.

didibus19:04:48

`(let [two (-> d :one :two) three (:three two) five (:five two)] [three five])`

Michael W19:04:55

Where did inner come from?

Michael W19:04:01

Is that arbitrary?

noisesmith19:04:58

there's also (-> d (get-in [:one :two]) ((juxt :three :five))) though most people hate to see (( inside ->

didibus19:04:53

Clojure, where "there's always many and --preferably even more-- non obvious ways to do it"

😛 8
noisesmith19:04:37

if that weren't true, what would we do with all the unused capacity for aesthetic opinions?

didibus19:04:35

Make up a bunch of useless design patterns :man-shrugging:

didibus19:04:52

Although all ways will be obvious to you once you're told about them hehe

Michael W19:04:59

Ok thanks for that juxt is going to solve some other stuff for me too, what a useful but non-obvious function, no idea it was there

noisesmith19:04:27

I find it pleasing, writing a function that has juxt in it is almost like writing a haiku

ghadi19:04:32

people get enamored with juxt

noisesmith19:04:49

which is reason to be suspicious of it, of course :D

ghadi19:04:50

has a tendency to be overused when you first discover it 😉

didibus19:04:42

It's way more fun to use a juxt, but much more pleasant to read code using a let

noisesmith19:04:36

I'm reminded of that old copypaste which I can't seem to find now, where the beginner haskell programmer uses '1 + 1', the grad student uses a Peano arithmetic implementation, the phd does the Peano calculation in the type checker, and the expert uses '1 + 1'

😂 12
Cameron20:04:33

meanwhile, juxt is like the first 'functional programming thingy' I ever remember learning, and I just remember thinking 'ok, so this this is it, juxt, map, reduce, monads...', I even implemented it in PHP for some ghetto web 'framework'. And then never used it again

Mario C.20:04:34

Is it possible to use lein checkouts without having to restart the repl everytime?

noisesmith20:04:51

once the checkout is set up, you should be able to reload the library files as you edit them

noisesmith20:04:33

using the :reload or :reload-all arg to require, or whatever shortcut your editor offers

noisesmith20:04:26

even without a checkout load-file will redefine the ns from a file, and the checkout makes sure you see the changes if you restart too

Mario C.20:04:36

I must not be doing something right because the changes are not taking effect

noisesmith20:04:35

sometimes what I do is insert a deliberate error (like a garbage token at the top of the file) to prove whether the file is loaded from what I'm editing or not

Mario C.20:04:30

I have some printlns that I am doing to see if the changes took effect but the outputs is still the same

noisesmith20:04:24

but there are things that would prevent the printlns if they are not at the top level (eg passing a function isntead of var to a long lived process, attempting to redefine a defmulti...)

noisesmith20:04:49

@mario.cordova.862 what does (io/resource "m_ns/my_file.clj") return - it will show the actual path that clojure uses when reloading

noisesmith20:04:12

(that is, the path to your ns, without the containing directories)

noisesmith20:04:03

eg.

scratch=> ( "clojure/core.clj")
#object[.URL 0x25d61f8d "jar:file:/Users/justin.smith/.m2/repository/org/clojure/clojure/1.10.1/cloju
re-1.10.1.jar!/clojure/core.clj"]

noisesmith20:04:17

which shows I get clojure via a jar in m2

noisesmith20:04:32

with a checkout that's set up properly, it should return the path to the file on disk in the original project

Mario C.20:04:55

The dependency file I want reload is called corefuncs.clj and its namespace is myns.corefuncs and when I call ( "myns/corefuncs.clj") I get #object[.URL 0x3e4eea1 "jar:file:/path/.m2/reporistory/path/myns/corefuncs.clj]

Mario C.20:04:01

And then it returns the actual file contents

noisesmith20:04:26

so your checkout isn't set up

Mario C.20:04:00

Hmm its because its looking into the .m2 folder?

noisesmith20:04:05

it actually has jar!/... in the middle there

noisesmith20:04:16

I know that because the URI is a jar URI

noisesmith20:04:30

you can't hot reload from the jar, because you didn't change the jar

noisesmith20:04:01

what the checkout should be doing is setting your classpath so asking for that file gives you a path to the file on disk, and it's not doing that

Mario C.20:04:43

I created a checkout dir and in there I ran ln -s ~/Projects/my-dep

Mario C.20:04:05

Shouldn't that have set it up?

noisesmith20:04:32

oh wait, lein now has built in checkouts separate from that plugin...

noisesmith20:04:11

so the symbolic link should have worked (after just one initial restart)

noisesmith20:04:31

@mario.cordova.862 one gotcha, the magic dir is checkouts not checkout

Mario C.20:04:48

I did create the checkouts with the write name, made a typo up there

noisesmith20:04:31

and you restarted the repl at least once since adding the link in checkouts? if so I can't tell you what the issue might be

noisesmith20:04:20

another gotcha is that this checkouts scheme only works for other lein projects

Mario C.20:04:15

Yea the dependency is a lein project as well

Mario C.21:04:58

Hmm going to keep searching, thanks though appreciate the help!

Mario C.21:04:12

Figured it out

Mario C.21:04:18

It was the way I was starting my repl

Cameron21:04:44

damn I was just about to chip in ahahaha

Mario C.21:04:45

> Make sure not to override the `base` profile while using checkouts. In practice that usually means using `lein with-profile +foo run` rather than `lein with-profile foo run`

👍 4
robertfw23:04:17

is it possible to use with-redefs to wrap a function? that is, can I refer to the original function that is being replaced, from the function I am replacing it with?

Darin Douglass23:04:30

Yep, just store a reference to the original function in a let. I would give an example but clojure + phone = not good.

robertfw23:04:52

this did exactly the trick. thanks 🙂

robertfw23:04:16

gotcha, that's enough to go on. thanks 🙂

Darin Douglass23:04:50

alter-var-root is a thing too

noisesmith23:04:44

if you refer to a var by name in a function, it's resolved at runtime each time it's used, by using a different name in a let, you "capture" the value at one point in the execution