Fork me on GitHub
#beginners
<
2021-06-08
>
Rob Haisfield00:06:54

What’s happening here? I’m trying to install the REBL and I get this:

cognitect-dev-tools-0.9.58 % bash ./install
Installing: com.cognitect/rebl {:mvn/version "0.9.242"}
./install: line 5: mvn: command not found
Installing: com.datomic/dev-local {:mvn/version "0.9.232"}
./install: line 7: mvn: command not found

alexmiller00:06:18

Looks like you need to install Maven (mvn)

👍 1
Rob Haisfield15:06:04

Thank you, I ran brew install maven and it worked 🙌

popeye07:06:30

i was referring clojure book and I come across (def app (middleware/wrap-base #'app-routes)) , what does #` do here?

popeye07:06:45

here app-routes are list of routes

popeye07:06:12

why we cannot use as (def app (middleware/wrap-base app-routes))

seancorfield07:06:09

You can but without the var quote, if you change app-routes while you app is running in the REPL, that change will not be seen.

1
seancorfield07:06:32

With the var quote, there’s an extra level of indirection, so changes made by the REPL take effect without a restart.

popeye07:06:37

@U04V70XH6, I got that, but that wont be helpful while running code in production right? because production code will be freezed

seancorfield08:06:03

You want the behavior in dev.

🙌 1
seancorfield08:06:57

But you can also have that behavior in production if you want. We run REPLs in some of our production processes and modify them live, while they are running.

Juλian (he/him)08:06:28

is there an easier way to apply a list of functions (with side-effects) to a map? the way I came up with: (reduce (fn [state f] (f state)) initial-state function-list)

pavlosmelissinos08:06:48

How about (doall ((apply comp (reverse function-list)) initial-state)) ? edit: added missing parens

pavlosmelissinos08:06:36

I don't have a repl in front of me right now but I think that would also work

Juλian (he/him)09:06:38

didn't think about comp, it does make it easier

😉 1
Juλian (he/him)09:06:47

is the doall necessary? the function-list is lazy, but applying comp returns a function that shouldn't be lazy

Juλian (he/him)09:06:00

also, since my function-list is symmetrical, I'll leave out the reverse - leaving a short and nice ((apply comp function-list) initial-state) - thanks for your help!

pavlosmelissinos09:06:41

> applying comp returns a function that shouldn't be lazy Not so sure about this to be honest :thinking_face:

pavlosmelissinos09:06:34

Ah, it seems you're right!

(->> (range 12)
     (map (apply comp
                 [#(do
                     (println "Realized" %)
                     (- %))
                  #(+ 1 %)
                  #(* 2 %)]))
     first)
evaluates to -1 but prints:
Realized 1
Realized 3
Realized 5
Realized 7
Realized 9
Realized 11
Realized 13
Realized 15
Realized 17
Realized 19
Realized 21
Realized 23

noisesmith15:06:45

comp returns a lazy function when the first function arg is lazy

noisesmith15:06:17

whether you apply it to a lazy arg is irrelevant as it eagerly consumes its args

noisesmith15:06:01

@UEQPKG7HQ your example from map isn't showing what you think it is - map is lazy (even here) but laziness across some data types (like vectors and ranges) is chunked - chunking means that realizing one item means realizing n items (where n is usually 32)

pavlosmelissinos16:06:42

Ah, I knew about chunking but I thought n was something like 8. Didn't think to test with a larger input, thanks 🙂

noisesmith16:06:33

funny thing, when I run that code but change (range 12) to (range) it only realizes the one item, I wonder what trickery is going on there

noisesmith16:06:55

perhaps (range) doesn't have the chunking behavior that (range n) does

noisesmith16:06:05

if I give it (range 1000) it stops at 63 which is the 32nd result

noisesmith16:06:16

so yeah, chunked by 32

noisesmith16:06:34

but don't count on that number in your code - just account for the fact chunking of some N is possible

noisesmith16:06:08

anyway none of this is related in any way to comp

pavlosmelissinos16:06:48

Yeah, not sure what I was thinking there with map. So whether this is lazy or not: ((apply comp (reverse function-list)) initial-state) depends solely on whether the last function in the function-list is eager or lazy, right?

noisesmith16:06:38

right, reverse makes it more complex, but yeah, comp is never lazy, the laziness of the function comp returns is determined by the first function in its arg list

noisesmith16:06:19

in general, laziness in clojure is a behavior of a data type, and a function is lazy if it returns one of those lazy types

noisesmith16:06:03

and comp's return type is the return type of its first arg (this shouldn't be hard to figure out by first principles)

pavlosmelissinos16:06:48

That clears it up quite a bit, thanks.

popeye13:06:08

how to convert all the values from an map to string in clojure i have map like {:1 one :2 two} to {:1 "one" :2 "two"}`

delaguardo13:06:00

strait forward solution is to use reduce-kv

(reduce-kv
 (fn [acc k v]
   (assoc acc k (str v)))
 {}
 {:x 1 :y 2})
but it wouldn’t help if you have a map with nested maps. if that is the case you could use recursion

popeye13:06:57

yeah that works!!!any inbuilt function available in json ? I got :`key-fn` but did not get how it is used

delaguardo13:06:44

depends on underlying library. I think jsonista has some functionality to add custom encoder/decoder

Yair Taboch14:06:59

(into {} (map #(vector (first %) (str (second %))) {:1 :one :2 :two}))

1
noisesmith16:06:25

my version: (into {} (map (juxt (comp identity key) (comp str val))) m)

noisesmith16:06:53

almost like @U024HM0AZNY’s but notice that map is being called with only one arg, and I use key / val instead of first / second

noisesmith16:06:32

data.json is soemthing totally different - it turns the whole hashmap into a string that javascript can parse

Rob Haisfield14:06:02

Documenting some of my early experiences learning Clojure. A caveat is that this is my first general purpose language that I’m really getting into (I tried teaching myself Python a few years back but got bored with that, probably because I didn’t have much of a goal at the time) https://twitter.com/RobertHaisfield/status/1402249772543614976?s=20

Rob Haisfield14:06:13

Does anyone know any good animations that show how `apply` works in Clojure? Something like these? https://jstutorial.medium.com/map-filter-and-reduce-animated-7fe391a35a47

thomas14:06:20

Forgive me if this question is beyond me as I am not very versed in clojure but, do you mean like behind the scenes/how its implemented or something? Conceptually I think apply might be easier to understand by just remembering that:

; this:
(apply + (list 1 2 3 4 5))

; is the same as this:
(+ 1 2 3 4 5)

thomas14:06:00

this doesn't exist in most other languages because they dont have s-expressions but I guess this kind of looks like it does the same as reduce until you understand that it it isnt (see above). the way I think of it is that it is a convenience function that exists because some times its nice to have. like the identity function. doesnt make much sense as to why it exists (to me at least) until you need it.

Rob Haisfield15:06:27

So is apply basically bursting the arguments out of the list they were in?

thomas15:06:50

it just takes the function given as the first argument and "applies" it to the list given as the second argument.

thomas15:06:22

in lisp terms you just stick the first argument at the front of the second argument which is a list, easy as

thomas15:06:05

user=> (apply + '(1 2 3 4 5))
15
user=> (+ 1 2 3 4 5)
15
I think it might be confusing because: a) it looks like it does the same thing as reduce:
user=> (reduce + '(1 2 3 4 5))
15
b) it doesn't exist without s-expressions but its really simple (at least conceptually), dont overthink it 🙂

dpsutton14:06:13

You asked this yesterday and got some great responses from really knowledgeable people. Not sure what else there is to say if you don't have a specific question about apply

Rob Haisfield14:06:29

@dpsutton Oh wow I totally missed those. I’m used to Slack channels where people respond in threads vs. just in the channel, my bad

Rob Haisfield14:06:35

I’m gonna look back over them

Rob Haisfield15:06:34

The concept of apply is clearer to me now, so I guess I’m closer to figuring out how I would visualize it… still can’t find any examples of it animated

noisesmith16:06:55

how would I animate the application of a function to args? it's not multi step, it's a single event

noisesmith16:06:32

thinking it through.... (apply + 1 2 [3 4 5]) becomes (apply + [1 2 3 4 5]) which finally becomes (+ 1 2 3 4 5) I don't think there's more than that you could demonstrate - would an animation with those three steps actually help?

popeye16:06:35

@U02108ERRU5 Any documentation for apply.. I am trying to understand it ..

popeye16:06:57

did not get when compare to reduce and map

noisesmith16:06:07

it's not in the same category with those at all

noisesmith16:06:14

it takes a function and args and calls the function

noisesmith16:06:31

maybe my three step example above helps? maybe it doesn't. note that the simplification I show is conceptual and not reflecting the actual code - it's very low level clojure code that implements it

noisesmith16:06:59

@U01J3DB39R6 perhaps the source of your confusion is that for associative functions like + and * and conj using apply and using reduce happen to return the same result? that's not something special about apply, that's something special about those functions and how they treat their args

🎯 1
Rob Haisfield16:06:00

Perhaps an animation could show the apply and the brackets around [3 4 5] fading away

noisesmith16:06:57

interesting - I guess I internalized this so long ago I lost my intuition for what is counter-intuitive when first learning it 😄

noisesmith16:06:40

I guess that middle step I provided is arbitrary, and arguably should be skipped, I wanted to highlight the vararg behaviour of apply because I've seen too many experienced clojure users do things like (apply f (concat [a b] other-args)) where (apply f a b other-args) already works

noisesmith16:06:36

but that's more intermediate to journeyman level rather than beginner level

Rob Haisfield16:06:35

Yeah broadly speaking I’m really interested in tooling around “learnable programming” to help people understand what’s actually happening. From what I can tell, once people pick up the mental models they don’t struggle to visualize things in their heads but things like Reduce and Apply are pretty foreign to me (a brand new programmer)

Rob Haisfield16:06:18

One possibility I’m exploring is a more interactive REPL that could visualize things like this so people can have a visceral sense for how a function executes

Rob Haisfield16:06:55

I’ve worked on the onboarding experience for GuidedTrack (a domain-specific language meant for non-programmer researchers and educators) and it has made me more confident in the idea of domain-specific languages as a vehicle for end-user programming and maybe even an alternative to traditional apps. Clojure is my first general purpose language I’m learning! https://www.youtube.com/watch?v=ov3lynWp9oM&amp;t=13s

noisesmith16:06:43

that's cool. one thing to note is that clojure is intentionally "a language for experts" and in any design conflict between intuition for beginners and flexibility / optimal behavior for experts, the experts will win. there's at least one youtube talk where Rich describes this in length, I'm looking it up now

noisesmith16:06:31

I've even told people in the past clojure is a bad first language (don't let this stop you if you enjoy clojure of course)

Rob Haisfield16:06:29

Yeah that’s fair that it’s designed as a language for experts. I think in general I’m not interested in modifying the language so much as just making more intuitive and obvious tooling around it and I think that Clojure’s generally uniform syntax opens up a lot of possibilities. Racket is another target for the sort of stuff I’m working on. Macros may ruin all of my plans lol

Rob Haisfield16:06:03

When I first started playing with Clojure, its generally uniform syntax made it way less intimidating for me than Python was when I attempted learning that a few years back

noisesmith16:06:27

if you get overwhelmed by the special cases and corner cases of clojure, scheme shares the simplified syntax and is also explicitly made for beginners / education. the racket version in particular has the best docs I've ever seen for any programming language

noisesmith16:06:51

but unlike clojure it doesn't have the conveniences for enterprise software, so it hasn't taken off in the industry

noisesmith16:06:11

but so far it doesn't sound like you are at all interested in enterprise software

noisesmith16:06:46

not telling you you are in the wrong place, but rather mentioning the kind of thing that comes up in my experience for people coming to clojure the way you are

noisesmith16:06:28

racket definitely has the sorts of visualization of program steps you are talking about here in its IDE

noisesmith16:06:01

(and, big picture, apply and reduce and map work the same way in clojure and in racket)

Rob Haisfield16:06:56

Haha yeah I’m not personally interested in a career in enterprise software. I’m interested in replacing basic app functionalities with higher order functions so people can do the sort of stuff they were already interested in doing but with control flow and recursion to give them more flexibility over what a traditional GUI app would

noisesmith16:06:43

yeah, racket is meant for that kind of thing

noisesmith16:06:06

but please do continue using clojure, it's a great language 😄

Rob Haisfield16:06:26

And then providing tooling around those DSLs so people ramp up in skill level towards just writing fluently

Rob Haisfield16:06:17

Yeah Racket is definitely more purpose built for the sort of thing I’m interested in, it just seems more niche. I’ve made a basic program in it, and I just need to learn the different sorts of data structures

noisesmith16:06:15

oh yeah, that's some historical baggage from the lisp family (it's simpler to just use lists for everything right???)

Rob Haisfield16:06:25

One dilemma was basically: Clojure is built with smart defaults that will help me form a basic mental model. Racket has an encouraged style but is more agnostic. So I wasn’t sure if I should start out by forming my default mental models around Clojure mental models or by forming my own models from working with Racket

noisesmith16:06:39

there are libraries that add data types to racket but most racket examples and code are going to be lists, lists and more lists

noisesmith16:06:30

that's an interesting perspective, I definitely "leveled up" in software design by understanding clojure's design decisions

Rob Haisfield16:06:17

Yeah. Clojure does a lot of clever things to infer parentheses over pairs of things where Racket would make you manually specify them. I like how in Clojure the [ ] and { } indicate different types of things than the lists, but ultimately mean the same thing as (vector ) and (hashmap ). Makes it easier to visually parse.

noisesmith16:06:28

another thing to look into (also another input to clojure's design) is the ml family (OCaml being the biggest living example) which tends to create too many data types instead of having too few

Rob Haisfield17:06:09

Also, the fact that functions are designed to abstractions is a tool for experts that I think also makes a beginner’s life easier.

noisesmith17:06:00

oh that's not the aspect I think of as expert oriented

noisesmith17:06:38

I'm thinking of the complexity around java interop, type hinting, chunking of lazy sequences...

noisesmith17:06:48

re-listening to that talk I linked: "instruments are made for people that can play them, but beginners are not players yet"

noisesmith17:06:06

"should we make cellos that auto-tune?"

noisesmith17:06:12

"I don't think we should go to our tools and orient them to the first five seconds of usage"

Rob Haisfield17:06:49

I’ll check out the talk. I think that’s totally fair! I just don’t think those goals are mutually exclusive. Sometimes those design choices are going to work out for both experts and beginners, and sometimes tooling can be made around expert systems that make them easier for beginners. I see a lot of people using tools like Notion and Roam at extremely advanced levels, and think those people might benefit from something that ramps them up into programmability, which should ramp people up towards expert usage

Rob Haisfield17:06:16

GuidedTrack code basically parses to a JSON that gets interpreted by JS, but I’m imagining something that’s generally as simple as GuidedTrack in terms of the verbs (function operators) and syntax (basically just nested stuff within a structural editor) but gives access to the full programmability of a language like Clojure

noisesmith17:06:43

right, I'm not arguing for that approach, just warning that this is the philosophy behind clojure's implementation and it can bump up against your interests

noisesmith17:06:25

it's a conflict I've seen multiple times, so I'm helping manage expectations up front 😄

Rob Haisfield17:06:53

I appreciate that 🙂️ in making learnable programming tooling, I generally consider it valuable that I’m a behavioral product designer approaching programming fresh and naive, but I definitely rely on experts to tell me where my expectations are totally out of whack

Rob Haisfield17:06:52

I’m trying to learn from the history of what’s been tried moreso than get discouraged by it 😅

noisesmith17:06:32

you'd probably enjoy this talk from strange loop 2019 about teaching programming (and what mainstream cs eduction gets wrong) https://www.thestrangeloop.com/2019/how-to-teach-programming-and-other-things.html

🙏 1
Rob Haisfield17:06:55

Thank you 🙂

sova-soars-the-sora16:06:00

I think sometimes apply and reduce will give the same result

noisesmith16:06:50

right, but that's a property of the function itself not a property of reduce / apply

sova-soars-the-sora16:06:11

I guess I don't really grok the difference yet

sova-soars-the-sora16:06:28

but I will do some more reading ^.^ probably best just to see lots of examples

sova-soars-the-sora16:06:23

Okay apply is variadic and takes potentially many args Reduce must take a function of two inputs... and then args is that right?

noisesmith16:06:04

the variadic part is a distraction for your question

noisesmith16:06:32

reduce walks one function across many items

parens 1
🐝 1
noisesmith16:06:49

apply calls one function with a list of args

🍢 1
noisesmith16:06:54

that's literally the whole thing

noisesmith16:06:45

now, as an incedental thing, there are "associative" functions which act the same with apply and reduce, that's because someone constructed the function that way

noisesmith16:06:48

it's not a property of apply or reduce

sova-soars-the-sora16:06:16

Cool. I am looking at this example https://clojuredocs.org/clojure.core/reduce#example-542692ccc026201cdc326c40 for calculating primes and it is pretty awesome, I don't understand the (map (partial mod ...) part although I think I know what it's supposed to do

sova-soars-the-sora17:06:29

I need more practice using map and partial

sova-soars-the-sora17:06:36

seems like a powerful combo

noisesmith17:06:03

partial is just a stylistic alternative to fn or #() really

💯 1
Antonio Bibiano17:06:49

(partial func fixed-arg) will return a function that is like calling func with the first argument set to fixed-arg

sova-soars-the-sora17:06:46

Oh neat. I did not think of it that way!

sova-soars-the-sora17:06:05

so a partially determined anonymous function

Antonio Bibiano17:06:31

yeah if you look at the source code for it it's also using apply 😄

Antonio Bibiano17:06:04

I'm just wondering why in the source it is defined explicitly for up to 3 arguments

noisesmith17:06:13

that's a performance optimization

noisesmith17:06:42

it's ubiquitous in clojure's core code, to break up arg counts so the method bodies and call stacks are smaller

Antonio Bibiano17:06:43

to avoid the apply and the concat

Antonio Bibiano17:06:14

I see.. because my first instinct would be to specify just the minimum needed

noisesmith17:06:44

right, that's good code design when you are not in a bottle neck for performance, to have the smallest simplest code you can

noisesmith17:06:54

but clojure.core is in every clojure user's speed bottleneck

Antonio Bibiano17:06:07

makes sense, thanks for the explanation

Jeff Evans17:06:47

is there a way to use conditions as the final arg list to condp here? the idea is that the map will be used somewhere else, too, so I’d like to avoid duplicating them

(let [conditions {1 "Only one" 2 "Truly two"}
      v          1]
  (condp = v 1 "Only one" 2 "Truly two"))

hiredman17:06:55

(conditions v)

💯 1
Jeff Evans17:06:59

ah right, since it’s simple equality this would work

Jeff Evans17:06:03

the real case is more involved

Jeff Evans17:06:33

(let [conditions {#"^foo.*" "Some foo" #"^bar.*" "Some bar"}
      v          "bar-value-1"]
  (condp re-matches v #"^foo.*" "Some foo" #"^bar.*" "Some bar"))

noisesmith17:06:34

since condp is a macro, you'd need a data structure known at compile time and another macro

noisesmith17:06:43

you could probably do something more complex with some-fn and functions that return a result on regex match or nil otherwise

Jeff Evans17:06:34

ah, that’s a good idea

noisesmith17:06:54

(let [conditions {#"^foo.*" "Some foo" #"^bar.*" "Some bar"}
      v          "bar-value-1" 
      preds (map (fn [[re result-string]]
                   (fn [s]    
                     (when (re-matches re s)
                       result-string)))  
                 conditions)]
  ((apply some-fn preds) v))

noisesmith17:06:01

this returns "Some bar"

noisesmith17:06:46

perhaps it's better style to bind (apply some-fn preds) to a name in the let block

Jeff Evans17:06:48

thanks, appreciated