Fork me on GitHub
#beginners
<
2018-03-04
>
Chase00:03:25

so for someone kind of new to programming would learning racket/scheme (using the how to design programs book and then sicp) before diving into Clojure be a smart approach? I was going to check out Common Lisp too and a couple of the really good texts on that language. On a whim, though, I just tried out the little starter app with reagent and figwheel. All with a few command line lein prompts. And tried the live reloading. Wow!

Chase00:03:28

is clojure the better one to start with to create real world web apps and apps in general? is it much harder to learn first? Should I stick with my Racket/Scheme plan or is there similar resources for a beginner to jump right into clojure?

schmee00:03:13

no need to learn those other languages as a prerequisite, just dive right in to Clojure! πŸ™‚

schmee00:03:24

as you become more proficient at clojure, you can adapt sicp and how to design programs to Clojure without too much hassle

schmee00:03:06

this book has a good reputation in the community for beginners: https://www.braveclojure.com/foreword/

schmee00:03:49

also, a new version of Programming Clojure was released just last week: https://www.amazon.com/Programming-Clojure-Alex-Miller/dp/1680502468/

schmee00:03:10

I’ve read the 2:ed edition and I can definitely recommend it for beginners

Chase00:03:08

awesome. i'll check it out. i've heard clojure had a large learning curve. and that it would help to know java for the interop stuff (which isn't a big interest for me, but what do I know, maybe I'm missing out). sounds like it's doable though

sundarj00:03:18

fwiw, i know very little Java, and i get by in Clojure just fine πŸ™‚

sundarj00:03:40

the error messages and docstrings do expose you to some Java concepts, but it's not too bad imo

sundarj00:03:04

same for ClojureScript (though i do know JavaScript quite well, so maybe i just don't see the knowledge required)

sundarj00:03:13

Java/JavaScript knowledge is helpful though

Chase01:03:29

ok cool. i'm basically a beginner. i've only explored some beginning tutorials for a few languages, seeing what catches my interest. so no java and not much js but it is what it is!

sundarj01:03:09

yeah, i personally would not recommend Clojure as a first language (it's not designed with programming beginners in mind), but different things work for different people πŸ™‚. Clojure is definitely worth giving a shot

sundarj01:03:36

Racket/Scheme is also an excellent choice (and they're much more beginner-friendly)

sundarj01:03:43

another beginner Clojure book i've seen recommended is Living Clojure, by the way

sundarj01:03:09

one other language you might want to look into is Lua (which i've heard is also beginner-friendly)

Chase01:03:04

i think i'll do these first couple racket books and then decide from there. i'm all stoked on these lisp family languages though! seem fun and powerful!

sundarj01:03:54

oh, yeah, lisps are wonderful :~)

sundarj01:03:27

welcome to programming by the way, it's a lot of fun! (often ;)

seancorfield04:03:54

@U61HA86AG ClojureBridge does very well teaching Clojure as a first language. Not sure why you think it isn't?

sundarj21:03:05

@U04V70XH6 I'm quite sure it's possible to teach a curated subset of Clojure to programming novices - it's a cogently designed, wonderfully simple language, and a lisp to boot. I'm not sure why you'd choose to though; it takes a great deal of effort to make a language and its ecosystem (which in Clojure's case, being two languages in one, also includes the not-so-friendly Java/JavaScript/C# ecosystems) as a whole beginner friendly (and requires design compromises), and that is not where Cognitect's (or the majority of the community's) efforts have been directed, which is understandable, since Clojure is a language for jaded veterans - it's designed to be a violin, not a kazoo. Clojure is designed to be a practical and robust language for professional programming; not to be easy and fun to get started with and use. it's simple, not easy. Beginners need both (or even ease over simplicity; Ruby and JavaScript show beginners don't necessarily need simplicity). Beginners should not have to expend a lot of effort to learn something, it should come naturally, and that's not the level of skill Clojure has been optimised for. Everything from the errors, to the standard library, to additional tools like spec, to the documentation requires active buy-in and competence from the programmer. I don't consider that a bad thing, but nor do I think it's an ideal environment for novices. Since languages exist that are more optimised for the beginner experience, and aren't two languages in one, like Elm, Scheme, Smalltalk, or Lua, and it does not matter too much what your first language is (mine was Python), so long you are able to grasp the fundamentals of programming easily and enjoy doing so, there doesn't seem to be much reason to recommend Clojure as a first language.

schmee00:03:46

whether it has a large learning curve or not depends a lot on you previous experience as well as your expectations

schmee00:03:15

if you’re used to C-style programming and have no previous exposure to functional programming there will be a lot to take in πŸ™‚

schmee00:03:05

if you’re coming from, say, Javascript, which has first-class functions and dynamic typing the gap is smaller

noisesmith01:03:25

c++11 has lambdas and auto tags πŸ˜„

schmee01:03:45

yeah I’m thinking C99 πŸ˜›

seancorfield04:03:44

@schmee I would say the hardest background to come to Clojure from is one that has only ever known Java-style OOP πŸ™‚ /cc @noisesmith

Andrew Gracey05:03:46

Hey, I'm trying to get lein installed on windows 10 and having issues with the lein self-install command. It's complaining about the SSL cert but neither of the suggestions work for me.

Andrew Gracey05:03:09

Google isn't helping me and I'm typically a linux user so my powershell is pretty basic

noisesmith05:03:14

@agracey: I don't know windows, but is set actually part of the syntax for setting an env var?

Andrew Gracey05:03:41

It seems to be ok if I remove the offending -O but then the download still doesn't work

noisesmith05:03:35

@agracey: supposedly the syntax is $env:MyTestVariable = "My temporary test variable."

noisesmith05:03:36

so you would use $env:HTTP_CLIENT = "..." I guess?

Andrew Gracey05:03:56

Yeah powershell isn't happy about that either

Andrew Gracey05:03:50

Are there instructions on what the ~/.lein directory shoudl look like at the end? I can download the file and place it myself

Andrew Gracey05:03:13

Or does lein self-install take any additional flags?

noisesmith05:03:29

$ tree ~/.lein/
/home/justin/.lein/
β”œβ”€β”€ credentials.clj.gpg
β”œβ”€β”€ profiles.clj
β”œβ”€β”€ repl-history
└── self-installs
    β”œβ”€β”€ leiningen-2.6.1-standalone.jar
    β”œβ”€β”€ leiningen-2.7.1-standalone.jar
    └── leiningen-2.8.1-standalone.jar

noisesmith05:03:07

the only file you actually need is a single jar under self-installs/ the rest are optional / automatically created

Andrew Gracey05:03:42

hmm, it's trying to download a .zip file... Does it just rename it to .jar? The structure of the zip looks like a jarfile

noisesmith05:03:09

that has to be it, but I'd have no idea why it would need to do that?

Andrew Gracey05:03:23

Awesome, that worked. running the repl now.

Andrew Gracey05:03:55

On another topic, is there a favorite container base image that people tend to use for clojure projects?

Andrew Gracey05:03:33

Is it worth filing a bug with the issue I ran into?

seancorfield05:03:39

@agracey Might also be worth taking a look at Boot -- I think it's much more beginner-friendly than Leiningen, to be honest.

derpocious05:03:11

hey all, I've having some trouble here. For some reason this expression is always returning the entire "data" object instead of just the count of the "ids" array which is would think it should return

derpocious05:03:18

(.get Twitter "followers/ids" params
  (fn [err data]
    (do
      (println "count" (count (.-ids data))))
    (count (.-ids data)))))

noisesmith05:03:10

which expression, the .get?

noisesmith05:03:29

also we really need to know what Twitter is here to give a meaningful answer

noisesmith05:03:43

who does that fn return to? who calls it? what gets done with the value the function returns?

noisesmith05:03:31

(and a minor thing - do inside an fn body is redundant, it already has an implicit do)

athomasoriginal15:03:21

Hey all, I am working with a data structure like this:

(def data [{:type :first :items [1 2]} {:type :second :items [1 2]} {:type :third :items [1 3 ]} {:type :first :items [1 5]}])
I want to transform the above into this:
(def data [{:type :first :items [ [1 2] [ 1 5 ] ]} {:type :second :items [1 2]} {:type :third :items [1 3]} }])
So for all maps with the same :type I want to combine their items together I have tried a few things, but I want to confirm that there isn't a util that does exactly this?

noisesmith15:03:36

there's nothing that does that exactly, but if I was solving that from scratch I'd start with group-by

noisesmith15:03:42

(group-by :type c) then manage each of the vals individually

athomasoriginal15:03:15

Great. That's what I was looking for

derpocious16:03:53

thanks @noisesmith, sorry I fell asleep

derpocious16:03:10

here's the context around the other snippet I posted

derpocious16:03:14

(defn doStuff []
  (println "sending request...")
  (let [twitter twit]
    (def Twitter
      (new twit
           #js {:consumer_key        "CsRZ0L9oBGWhIs2pNp7CFdzU7"
                :consumer_secret     "fC5rr11BWtJGCTxU7prS9WMz4AK6HXgyl2DtVwGdT1zfdfdUcdE"
                :access_token        "3930103636-zJiKnCIljqIGPZJrGKWhUS55BgfrrTHWYQ31ytr5"
                :access_token_secret "2envh7Jz6mmbDD7yeewSGZhsKCZ7TsuLWSOajeWWkFQned"})))
  (def params {})
  (.get Twitter "followers/ids" params
        (fn [err data]
          (do
            (println "count" (count (.-ids data))))
            (count (.-ids data)))))

Andrew Gracey16:03:10

You really need to edit this to remove the tokens

derpocious16:03:57

"twit" is a js library that I added with (:require [twit :as twit])

noisesmith16:03:34

you might not want to share your secret key here, but the salient point is which thing is "returning" and who to? I would expect something with an api that looks like that to run your callback, and nothing to use the return value of your callback

noisesmith16:03:06

I would expect (.get ...) to return nothing useful, or at besta handle you can use to cancel a running http interaction

derpocious16:03:35

but the callback function for .get has all the data I want to return

noisesmith16:03:52

but nothing uses the return value of your callback

noisesmith16:03:01

it gets called by somebody else, and they ignore the return value

noisesmith16:03:10

you need to put the value somewhere, or launch something that uses it

noisesmith16:03:39

or maybe I'm misunderstanding your issue

derpocious16:03:11

(defn doStuff []
  (println "sending request...")
  (let [twitter twit]
    (def Twitter
      (new twit
           #js {...})))
  (def params {})
  (.get Twitter "followers/ids" params
        (fn [err data]
          (do
            (println "count" (count (.-ids data))))
            (count (.-ids data)))))

(deflambda run-lambda []
  (doStuff))

derpocious16:03:41

run-lambda is an AWS lambda function which I want to return the "data" value from .get

derpocious16:03:17

well I actually want it to return just the length of the "ids" property of data

derpocious16:03:29

but it's returning the full data object for some reason

noisesmith16:03:27

@derpocious - my hangup here is that nothing consumes that final count call

noisesmith16:03:01

and dostuff is either going to return a handle to an async operation, or nothing useful at all

derpocious16:03:33

hmm so what would be the right way to just return the count?

noisesmith16:03:44

you can't because the operation is async

noisesmith16:03:14

async is contagious - you need to launch another async thing from the callback, or push the data somewhere that something else will see it from in the callback

noisesmith16:03:48

this is why people use core.async or js/Promise

justinlee16:03:22

though to be fair, those things are just as contagious πŸ™‚

noisesmith16:03:41

right - because they are also async

noisesmith16:03:57

you have opted in when you go async, your options are all async, some are saner than others

derpocious16:03:10

ok I can try to use core.async

derpocious16:03:42

so do I then need to wrap the Twitter.get in a channel since it's not a normal js lib not meant to be used in core.async?

noisesmith16:03:52

in that case you would write to a channel with put! in the callback function, and have a go block that reads from that chan

derpocious16:03:19

ok thanks, and put! works for cljs too bc it's asynchronous right?

noisesmith16:03:41

right - it's the way to asynchronously put a value on a channel

noisesmith16:03:56

there's also take! which takes a callback and is async, going the other way

derpocious16:03:42

I see, and these are also the same and <! and >! ?

noisesmith16:03:36

not precisely the same, because they also lift into async (or drop back from async) and take a callback - they are like (go (f (<! c))) or (go (>! c v)) / (go (f (>! c v)))

noisesmith16:03:54

but cheaper because they don't need the go mechanisms

derpocious16:03:03

ahh ok thanks :+1:

derpocious18:03:48

hey all I'm wondering if this is "idiomatic clojure" code.

derpocious18:03:05

(def myChan (chan))
(def otherChan (chan))

(defn doStuff []
  (println "sending request...")
  (let [twitter twit]
    (def Twitter
      (new twit
           #js {:consumer_key        "efvrfvrfvrfv"
                :consumer_secret     "rfvrfvrfv"
                :access_token        "3930103636-rfvrfvrfv"
                :access_token_secret "rfvrtbg5tb5t"})))
  (def params {})

  (.get Twitter "followers/ids" params
        (fn [err data]
          (put! myChan (count (.-ids data)))))

  (.get Twitter "friends/ids" params
        (fn [err data]
          (put! otherChan (count (.-ids data))))))

(deflambda run-lambda []
  (doStuff)
  (go
    (let [firstThing (<! myChan)
          secondThing (<! otherChan)]
      {:f firstThing :s secondThing})))

derpocious18:03:56

It returns the map { :s 1915 :f 1920 } which is what I wanted, but I was just wondering if this is readable code and the "normal" way to do it.

derpocious18:03:30

In particular, I wasn't sure how to get access to things from two different channels at the same time so I just wrapped them in one big "let" block inside of the "go" block. Not sure if that's the best way to do it.

noisesmith18:03:34

that's exactly how it's normally done

noisesmith18:03:48

also to be clear, go returns a channel - async is contagious

derpocious18:03:35

wait, what do you mean exactly by contagious?

noisesmith18:03:22

I mean that the hash map on the last line of your code doesn't get returned by that "deflambda" - it gets put on the channel that go returns

noisesmith18:03:42

if nobody uses the channel go made, that value won't get used

derpocious18:03:39

hmm so if I wanted to return just the map, do I need to wrap the go block in take!

derpocious18:03:08

(take! (go
    (let [firstThing (<! myChan)
          secondThing (<! otherChan)]
      {:f firstThing :s secondThing})))

noisesmith18:03:09

take is also async, it returns nil

noisesmith18:03:15

like I said, async is contagious

noisesmith18:03:52

there's no easy way out of async into normal values - in some cases no way?

justinlee19:03:48

I’m pretty sure that you’re going to need to use https://nervous.io/doc/cljs-lambda/cljs-lambda.context.html#var-done.21 inside that deflambda

noisesmith19:03:50

you can fake it by chaining all your other non-async code in a function in a go block (or call all your other non-async code from a callback on take, which is equivalent)

justinlee19:03:05

with lambda you either need to use a callback (like done!) or you need to return a promise

justinlee19:03:39

the aws context will not understand an async channel, so I think if you use async, the easier thing to do is use the completion callback

derpocious19:03:19

thanks @lee.justin.m, is this what you mean?

(deflambda run-lambda [ctx]
  (doStuff)
  (take! (go
    (let [firstThing (<! myChan)
          secondThing (<! otherChan)]
      (ctx/succeed! {:f firstThing :s secondThing})))))

derpocious19:03:19

but for some reason on AWS it's giving me the error, ""Cannot read property 'call' of undefined"

derpocious20:03:58

I forgot to remove that take!, and also ctx is the second argument

derpocious20:03:06

(deflambda run-lambda [args ctx]
  (println args)
  (println ctx)
  (doStuff)
  (go
    (let [firstThing (<! myChan)
          secondThing (<! otherChan)]
      (ctx/succeed! ctx {:f firstThing :s secondThing}))))

jonathan22:03:00

When print a multi-line string in the REPL, it ignores whitespace indentation. But if I call a function on that binding, the white-space is included. How is this typically handled?

(def multline "aaaaaa
               bb
               cccc")

multline
=> "aaaaaa\nbb\ncccc"
(frequencies multline)
=> {\a 6, \newline 2, \space 30, \b 2, \c 4}
(frequencies "aaaaaa\nbb\ncccc")
=> {\a 6, \newline 2, \b 2, \c 4}

noisesmith22:03:24

the default printed form escapes the newlines if that's what you mean? - if you use print on it you'll see a multi line output

noisesmith22:03:57

oh wait - the spaces

jonathan22:03:01

yeah πŸ™‚

alexmiller22:03:32

may depend on what kind of repl you’re using too

noisesmith22:03:47

yeah, I can't reproduce that in clojure.main

jonathan22:03:51

i see. I'm using Cursive's default (nrepl)

alexmiller22:03:01

I think that’s a quirk of nrepl stdin handling

jonathan22:03:37

seems like a bug πŸ˜„

noisesmith22:03:39

I also can't reproduce with lein repl

noisesmith22:03:11

REPL-y 0.3.7, nREPL 0.2.12

dpsutton22:03:42

CIDER handles this correctly

user> (def multline "aaaaaa
               bb
               cccc")
#'user/multline
user> multline
aaaaaa
               bb
               cccc
user> (frequencies multline)
{\a 6, \newline 2, \space 30, \b 2, \c 4}
but i think we format the output when we get it back to do this. i remember fixing a bug having to do with \cr on windows i think

noisesmith22:03:11

wait, that repl doesn't use the pr form of the string?

dpsutton22:03:13

or maybe =r

noisesmith22:03:17

that's weird

jonathan22:03:21

I'm not really sure what version of nrepl I'm using πŸ™‚ I'm on OS X (Sierra), Cursive, Clojure 1.9, lein app template

jonathan22:03:11

I have a workaround to use a collection of strings and a simple function I found on stackoverflow:

(defn long-str [& strings] (clojure.string/join "\n" strings))
(def multi (long-str "aaaaa"
                      "bb"
                      "cccc"))

jonathan22:03:40

but that does make me a bit fearful of using multiline strings in the future

noisesmith22:03:25

@jonjanisch: if you start lein repl in a terminal it shows the nrepl version

jonathan22:03:56

REPL-y 0.3.7, nREPL 0.2.12

jonathan22:03:08

same as yours

jonathan22:03:35

but perhaps Cursive bundles its own version of lein

noisesmith22:03:19

might be a good question for #cursive

jonathan22:03:04

I see 0.2.12 in my IntelliJ dependency tree

dpsutton22:03:06

hmm. for (pr multline) this message comes back from nrepl

(<--
  id         "53"
  session    "324621db-cd95-4b19-a5b7-e1676de7e861"
  time-stamp "2018-03-04 16:27:26.190502726"
  out        "\"aaaaaa\\n               bb\\n               cccc\""
)
and for just multline we get
(<--
  id         "54"
  session    "324621db-cd95-4b19-a5b7-e1676de7e861"
  time-stamp "2018-03-04 16:27:28.448696117"
  pprint-out "aaaaaa
               bb
               cccc
"
)

noisesmith22:03:49

pr-str is likely a better comparison (it's the string pr would output)

eggsyntax22:03:10

I'm starting to question the legitimacy of my asking questions in #beginners, since I've been doing this for a few years now, but... beginner's mind? Sure, that'll do. & I didn't have any luck with this one in #clojurescript πŸ˜‰ In clj it's easy to extend a protocol to all types of maps (ArrayMap, HashMap...) by extending it to the interface java.util.Map. That's nice because it spares you the hassle of extending it to each map type separately. As far as I can tell, I can't do the same in cljs by extending the protocol to cljs.core/IMap, because there's not a reified interface for cljs protocols. Is that correct? And is there any other way to do the equivalent in cljs?

drewverlee01:03:16

Yes, when do you ascend to #clojure?

eggsyntax02:03:23

Depends how clueless I feel on any particular day πŸ˜†

eggsyntax22:03:19

But if anyone has any thoughts on any sort of workarounds or alternate approaches, I'd really love to hear them -- I hate to only extend what I'm doing to concrete types, because then if a user creates a type that implements IMap, they won't automatically be able to use my protocol functions πŸ˜•