Fork me on GitHub
#beginners
<
2020-03-23
>
Albert Eng02:03:23

Super new working through some Exercism exercises and I’m running into a situation where evaluating code through the REPL and it works fine, but errors out whenever I run lein test it errors out when compiling. Am I missing something here? I’d imagine the REPL should error out if there’s an issue in the file. (I’ve also eval’ed the individual pieces of my code and tests to make sure.) I hate to post a code dump, but I’m unsure on how someone could help without it. Pretty new to the Slack, so feel free to shame me if this sort of thing isn’t accepted here. 😅

(ns rna-transcription)
(require '[clojure.string :as str])

(defn to-rna [dna] 
  (str/join "" (map #(convert-rna %) (str/split dna #"" )))
)

(defn convert-rna [dna-char]
  (case dna-char
     "G" "C"
     "C" "G"
     "T" "A"
     "A" "U"
  )
)

dpsutton02:03:30

Clojure is a single pass compiler. What that means for you is that you need to define functions before you use them. Your first function to-rna uses the yet to be defined convert-rna

hindol04:03:34

I know you have not asked for it, but you can also use a map for the case form.

({"G" "C"} "G") => "C"

hindol04:03:27

This works because a map is also a function!

Albert Eng11:03:12

You know I’ve played with Clojure before and stopped because I was getting frustrated with things that they should have worked. I’m pretty sure now that I was being bit by that very same complete gotcha! Thanks, that makes a lot of sense now on why my code wouldn’t compile.

Albert Eng11:03:46

That’s a nice tip @UJRDALZA5! I have to refactor anyway, I’m not handling an exception case in the tests right now, I’ll look to include something like that when I do.

hindol11:03:26

I can relate to your problem. The first time I tried to learn Clojure, I stopped and moved to Elixir and Rust, because Clojure was so different. And there is a scarcity of beginner level resources. Once you get past the initial barrier though, you will not want to leave Clojure. I just remembered, if for some reason you don't want to rearrange the functions, there is also declare, https://clojuredocs.org/clojure.core/declare

Albert Eng12:03:53

Ahh nice, that could be useful if I’m pulling my hair out, or have some weird nest of dependencies. And yeah, I was going through a book and got what I thought would be a working solution… then bam compiler error! 😂 I was going through a book at the time, so I wasn’t sure if it was something I did or some other weirdness that I wasn’t expecting. Kinda took the wind out of my sails, I left saying “I’ll come back to this tomorrow” and tomorrow never came. Luckily I have a coworker who really enjoys clojure and I’ve been seeing how much he enjoys it when we do our weekly code katas. When we pair and I work on a small exercise it all pretty much clicks, but there are a lot of gotchas / weirdness that you wouldn’t expect. (example I ran into today: how to get the length of a list isn’t length like you’d expect in other languages, it’s count!)

hindol12:03:55

Yeah, it's different. But Clojure has a small number of core functions that do a lot. After some time, you will be able to remember what is needed for what situation.

decim02:03:29

so can someone point me in the right direction here. I have my get-senator-data function. I actually want to add another argument and be able to iterate though “data-fields” plugging each item in, which I see I used map there. Really it should be a vector or a list. but how should I go about doing this? https://gist.github.com/cyberoctopi/3519af38101927a75dac80e167cdc60c I think this will be the first interesting function I write as a newbie so don’t like solve it for me...just kind of kick me in the right direction... what functions should i be looking at as I break this down and iterate through my json data

hindol11:03:40

You already know how to get one field out using map/mapv. You can experiment with for a little. Like (for [field fields] ...). Finally, zipmap can help you convert the resulting data into a map. Hope I did not completely ruin it for you.

decim02:03:05

I’m think I can zipmap each field into a { } ?

Ben Sless09:03:11

Can you describe how your input looks like and how you want the output to look? Perhaps you could approach the problem a bit more declaratively

hindol11:03:18

Why not capture the common root like this?

(defn get-senator-data
  "Fetch necessary fields from senate-data json feed"
  [data]
  (let [members       (->> data :results (mapcat :members))
        first-name    (map :first_name members)
        last-name     (map :last_name members)]
    (println-str (first first-name))))

👍 1
decim06:03:46

Yes! thats one part that I was trying to figure out how to clean up! then I believe juxt will get me what I want for the full functionality.. Thank you.

decim06:03:32

@UK0810AQ2 So i’m pulling data from inside :results/members Here’s the block for one person then I have every member of senate in this json file so 100 to pull data from. {:status "OK", :copyright " Copyright (c) 2020 Pro Publica Inc. All Rights Reserved.", :results [{:congress "116", :chamber "Senate", :num_results 101, :offset 0, :members [{:contact_form "", :votesmart_id "15691", :youtube_account "lamaralexander", :next_election "2020", :total_present 0, :party "R", :suffix nil, :fax "", :first_name "Lamar", :in_office true, :cook_pvi nil, :govtrack_id "300002", :seniority "17", :short_title "Sen.", :phone "", :facebook_account "senatorlamaralexander", :votes_against_party_pct 3.68, :last_updated "2020-03-11 14:00:53 -0400", :twitter_account "SenAlexander", :state_rank "senior", :dw_nominate 0.323, :state "TN", :middle_name nil, :leadership_role nil, :title "Senator, 2nd Class", :votes_with_party_pct 96.32, :cspan_id "5", :crp_id "N00009888", :office "455 Dirksen Senate Office Building", :ideal_point nil, :rss_url "", :id "A000360", :lis_id "S289", :icpsr_id "40304", :url "", :missed_votes_pct 17.51, :date_of_birth "1940-07-03", :missed_votes 87, :last_name "Alexander", :google_entity_id "/m/01rbs3", :gender "M", :total_votes 497, :ocd_id "ocd-division/country:us/state:tn", :fec_candidate_id "S2TN00058", :senate_class "2", :api_uri ""}

hindol06:03:05

@U06FM9QN9 Okay, if you want to use juxt with data-fields, look up apply.

Ben Sless07:03:00

@U06FM9QN9 the hint is in the terminology. you want to get the data in results.members? use get-in 🙂 To extract the keys you're interested in, select-keys is indeed the most idiomatic. Then as you have previously defined your keys, you can

(map #(select-keys % your-keys) ...)
over the members

decim12:03:01

@UK0810AQ2 thanks…that helped me well along my way. Toying with it now and working on feeding all of it to a database now.

zonkhead03:03:47

I’m trying to use Aether/Pomegranate. It has this notion of coordinates but what the heck type/types are they? Is it a vector with two elements? Is it a symbol? (like in the example) The docs just say:

:coordinates - [group/name \"version\"]

zonkhead03:03:30

I figured it out… sorta. It looks like it’s a vector of vectors of symbol and string. So it likes the result of:

(read-string "[some/proj \"1.0\"")

hindol07:03:39

That's the same as [some/proj "1.0"]

hindol07:03:55

Does that work?

skoude09:03:50

I need to define a nested map in spec. And the nested map can have dynamic values..I did not find anything related to this, so wondering if that’s possible in spec,

skoude12:03:21

damn, that was too easy:

(s/def ::extra_info (s/map-of keyword? string?))
and bang, it worked… Thanks for a help :thumbsup:

👍 1
Chase18:03:10

I'm finally starting a web dev project with this extra time. I'm going with Ring and Compojure as suggested here a few times. I am going to read through their docs and just go from there. Besides some basic html/css knowledge though I don't actually know "web development." Where would you recommend I learn such things with the goal of actually doing it with clojure?

Chase18:03:55

I have the Web Development with Clojure book but I'm thinking it starts a little advanced but maybe it just wants to show a full Luminus project before going back to the very basics?

Chase18:03:12

Or is it going to assume I always know web development and is just teaching me the Clojure way to do it?

Chase18:03:33

Will the Ring/Compojure docs get me started or are they assuming prior web dev knowledge too?

hindol18:03:13

Do you know HTTP/ReST?

Chase18:03:55

not really. This is my first project. I was starting the Odin project but ultimately my goal is to do full stack Clojure web development so trying to learn through that filter.

Chase18:03:40

I was going to use MDN (mozilla web dev docs) docs to learn some of the basics.

noisesmith18:03:53

I'd say learning about REST itself, and the js programming model / browser affordances and constraints would be important, I learned them by jumping in so I don't know of a good focused resource (and these things move very fast so most info online is out of date...)

noisesmith18:03:17

well, REST is stable enough, but how people use it in practice moves fast, very fashion driven

hindol18:03:21

I fear there is too much to learn. The frontend and backend are very different with their own ecosystem. The backend can be any language and you can do it in Clojure. The frontend is restricted to JavaScript or anything that can transpile to JS.

hindol18:03:21

Since the backend can be any language, the communication between the backend and the frontend is usually in terms of pure data, JSON is most popular, but anything can work, EDN too.

Chase18:03:25

Maybe I should go back to the Odin Project first then. My problem is I keep jumping from learning resource to learning resource without ever starting a real project. I'm raring to do now!

Chase18:03:17

I was going to skip front end for now and just do back end clojure with some html/css. (maybe explore hiccup/garden if I get fiesty)

Pavel Klavík18:03:41

I learned how to build full stack project year ago (for our startup http://orgpad.com). I started with building only front-end with shadow-cljs, reagent and re-frame.

Pavel Klavík18:03:24

Later I added backend based on luminus template, Ring, Bidi (better than Compojure, you want to have a routing library which works on both frontend and backend).

Chase18:03:25

interesting. The exact opposite of my plan. Lol! I have purchased the learn reagent and learn re-frame courses though

hindol18:03:29

Now-a-days, people's personal computers are so powerful that browsers can run quite a bit of logic. So, for starters, my suggestion is look up single page apps (SPAs). For simple projects you don't even need a backend, only a server that can server static files. Heck, you can serve it from Github pages for free.

Pavel Klavík18:03:13

For backend, Web Development in Clojure is good, the first chapter is quite a mess, but later they introduce everything in detail

Chase18:03:24

ok. I can see that approach as well. Unfortunately I've gotten the exact opposite advice from some other respected members of this community. They said start with Ring and Compojure.

hindol18:03:34

Recently, I did one SPA: https://hindol.github.io/genesis/. The topic may not be relevant to you but the codebase is small and uses Reagent. Caveat, the app-state in db.cljs is not very idiomatic.

Pavel Klavík18:03:43

Routing library is not that important anyway, you can always replace it for something else. Not sure whether I would choose Ring nowadays (it is best documented, you have most resources about it, but middlewares are quite a mess)

Pavel Klavík18:03:25

A bad thing about Ring is that a lot of things are black boxes and it is difficult to know how they work inside (CSRF, sessions, etc.). I still have much better understanding of frontend and the code is much nicer there.

noisesmith18:03:48

yeah - middleware are composed functions, and functions (especially composed ones) are very opaque - usually you need to track down which code composed your middleware functions, then read source of the relevant ones to figure out what's going on

noisesmith18:03:26

but clojure's reflective abilty / editor tooling can't help much since (fn [f] (fn [y] (g (f y)))) is very opaque

noisesmith18:03:42

and that's the kind of thing that builds middleware

Pavel Klavík18:03:43

Ya, plus it is really easy to make a mistake by bad ordering, etc. Interceptors are much better, but in general data description is much better than functional composition.

Pavel Klavík18:03:22

What are other alternatives for backend in Clojure?

noisesmith18:03:05

pedestal interceptors look nice, but I haven't used them in anger

noisesmith18:03:35

I just bit the bullet, made sure all "composed" middleware in my project was replaced by the individual middlwares and all applied in one place

noisesmith18:03:09

so instead of using the default composed mws from ring, looking at source and adding all those middleware and options in my own top level ns

Pavel Klavík18:03:37

I see, makes sense. I might do some cleanup in the future. Luckily the entire server is just 5k lines so it should not be difficult to do any changes when I feel like it.

Pavel Klavík18:03:52

Concerning the main question in the thread, I would start by concentrating on one part (front-end or back-end). It greatly depends whether you want to create a SPA or you more care about backend and have some simple frontend pages for it.

noisesmith18:03:36

agreed either side works (though learning clojure before cljs is much easier than visa versa - most cljs is designed with "be like clojure" as a semi-flexible goal)

Pavel Klavík18:03:11

maybe explains why I am still more familiar with cljs 🙂 but adding dependencies is much easier in Clojure, it is more involved in Clojurescript (even when using Shadow-cljs)

Chase18:03:43

Thanks for the advice folks!

Chase18:03:52

I actually think I will head back to the Odin Project and learn web development from the ground up. It doesn't seem there are any learn Web Development (using Clojure) resources. I want a strong foundation and I will slowly build my project while following the curriculum

noisesmith18:03:23

@UFHE11C83 basically what I did was replace usage of this function with a copy-paste into my own handler namespace (then customization after that of course) https://github.com/ring-clojure/ring-defaults/blob/master/src/ring/middleware/defaults.clj#L89

noisesmith18:03:43

it makes the whole thing easier to reason about at least

Pavel Klavík19:03:53

@U9J50BY4C certainly possible, but using Clojure resources is certainly possible (for frontend, reframe template can be used and re-frame documentation is actually awesome + doing some reading on react and reagent)

Pavel Klavík19:03:23

for backend, Webdevelopment in Clojure is actually a very good book (I read 2nd edition, 3rd edition is I think still unfinished)

Pavel Klavík19:03:44

plus looking for some general resources around how web works (HTTP, sessions, security): I would actually love to read a general Big Picture book about these things

Pavel Klavík19:03:56

but learning all this stuff might be overwhelming at the beginning, in my case I was working for Google before leaving for the startup, so I am a fast learner, but it still took me some time to absorb all this stuff to feel comfortable with the full stack

Chase19:03:18

ok, thank you. I did just skip the big Luminus app introduction chapter and Chapter 2 seems to be much more basic.

Pavel Klavík19:03:34

ya, exactly, Chapter 1 is hard to understand on the first time, the rest of the book is much better (and I build my server by copying and adapting parts of Luminus template, step by step, so I was able to understand everything how it is build)

seancorfield19:03:41

Yeah, Luminus has a lot of moving parts so starting with just Ring and getting "Hello, World!" working, then adding Compojure (or Reitit) and getting some basic routes working, and then building from there would definitely be my recommendation.

seancorfield19:03:44

This is a fairly simple backend example https://github.com/seancorfield/usermanager-example -- Ring, Compojure, Component, Selmer (for HTML), next.jdbc (for a database). Might be an easier starting point than Luminus.

Gulli20:03:22

I have a fuction that calls an external service via http. I want to create a test for this function, is there something similar to wiremock in the clojure world?

noisesmith20:03:25

but we have projects where we use wiremock

ghadi20:03:28

I think mocking is in general a bad idea because it couples you to implementation details (like clj-http). You should consider passing explicit SPI objects into your app / tests

ghadi20:03:05

e.g. pass in a function that takes a map representing an http request

ghadi20:03:23

in prod you pass in a function that calls clj-http

noisesmith20:03:25

wiremock is an external server, I don't see how it would need to change to use with clojure?

ghadi20:03:52

in tests you pass in a no-op function or something that captures the request

noisesmith20:03:30

I see that wiremock also can be used as a library - clojure is a jvm library too, you can use them together in one app

noisesmith20:03:29

agreed with @ghadi that passing in an SPI is a better design than using mocks in unit tests

ghadi20:03:57

the SPI could be a plain function, or a protocol impl

noisesmith20:03:02

though I'd prefer an external mock for things that we don't want to hit in an integration test but want to actually exercise the protocols and libs...

Gulli20:03:56

Thanks :face_with_cowboy_hat:

Gulli20:03:15

@ghadi Do you know any examples of SPI usage? This is new to me and I'm not finding a whole lot of info online

ghadi20:03:44

imagine you were integrating with a queue service like Amazon SQS or Kafka

ghadi20:03:54

some part of your code had to put something on the queue

ghadi20:03:42

if your code called (some-aws-lib/enqueue {:my :payload}) you'd be bound to the details of some-aws-lib

ghadi20:03:56

so you'd have to resort to mocking

ghadi20:03:04

in tests or local dev

ghadi20:03:41

instead, you can define an abstraction (like a protocol in Clojure)

ghadi20:03:58

(defprotocol QueueService
  (enqueue [_ payload]))

ghadi20:03:19

then make an impl of that protocol for some-aws-lib and another impl of that protocol for your local dev / tests

ghadi20:03:45

and another impl of it for something pathological, like an intentionally slow impl, or one that throws an exception

ghadi20:03:57

the key thing is that you pass an implmenetation of QueueService into your app

jlmr06:03:46

@ghadi I was just reading this and was wondering what exactly you mean when you say “pass into your app”? Do you mean deciding which implements to use based on for example a env variable?

ghadi12:03:03

Pass an explicit argument somewhere, like put it into the web request context, etc. Which impl is used could be driven by an environment variable or some setup routine

ghadi20:03:06

and it's an explicit argument

ghadi20:03:18

not an ambient / global side effect coupled to a particular queue impl

Gulli20:03:32

Yeah it does, thanks!

Gulli20:03:37

It makes sense for unit tests, but as @noisesmith said, I'd like a mock service for integration testing, but abstracting the service is definitely nice for unit testing and flexibility of the service

dpsutton20:03:13

> and another impl of it for something pathological, like an intentionally slow impl, or one that throws an exception doesn't this accomplish this?

ghadi20:03:31

tldr: program to interfaces and not concretions

dpsutton20:03:42

I meant in response to "I'd like a mock service for integration testing," seems like what you described is essentially that. You can make one that returns a known good payload, one that takes a long time, times out, throws 500s, etc

noisesmith20:03:04

the difference is having your code actually call clj-http

noisesmith20:03:33

for that, there needs to be another service (but probably not the one where you pay per request and the results are covered by PII protection laws etc.)

noisesmith20:03:01

at least that's our usecase for using wiremock (as a service external to the app, not as a lib)

ghadi20:03:49

is wiremock returning fixed answers?

ghadi20:03:31

you can combine the SPI approach and external mocks, I'm just advocating against internal mocks

💯 1
noisesmith20:03:35

it has a config - I don't have a deep knowledge of that part of the setup, but yeah, with some intellegence about the content of the payload I think

Gulli20:03:32

@ghadi Just curious, why do you advocate against it?

ghadi20:03:56

because internal mocks couple you to details

ghadi21:03:20

coupling -- a (the?) major killer of codebases

Gulli21:03:10

That is a good point

ghadi21:03:32

anytime you see with-redefs in tests, you're coupled to some details

OrdoFlammae21:03:27

Is there an idiomatic way to reverse interleave?

OrdoFlammae21:03:46

I'd like to take a string and put it into two different lists, switching between the two.

OrdoFlammae21:03:11

So "ababababab" would go into [\a \a \a \a \a] and [\b \b \b \b \b].

noisesmith21:03:01

one approach

user=> ((juxt (partial map first) (partial map second)) (partition 2 "abababababab"))
[(\a \a \a \a \a \a) (\b \b \b \b \b \b)]

OrdoFlammae21:03:50

I guess that could work.

noisesmith22:03:06

you could do something more general with reduce

OrdoFlammae22:03:29

Yeah, I'm just trying to get every other element in a string.

Pavel Klavík22:03:18

Try (partition 1 2 "abababababa").

OrdoFlammae22:03:16

I can do it with loops, I was just wondering if there was a function that basically undid interleave.

OrdoFlammae22:03:21

Oh, just found take-nth. I think that'll do what I want.

noisesmith22:03:01

oh yeah - something like

user=> (let [s "ababababab"] [(take-nth 2 s) (take-nth 2 (rest s))])
[(\a \a \a \a \a) (\b \b \b \b \b)]

OrdoFlammae22:03:36

Yep. Thanks for the help.