Fork me on GitHub

How would you name boolean defs? For functions that return booleans, we use bar?, but how would you name a let that will receive the result from bar? ?


In the past I either also called it bar? or is-bar


@hp_pedrors I try to avoid code that let-binds such results in general 🙂


You should be able to refactor your code to use the predicate in a conditional in just one place so you don't feel the need to let-bind it.


Hmm ok I'll try that.


It will for sure look prettier too.


It might leave me with a method that is something like

(defn update-position [params]
if (logic/position-valid? params)
  (if (logic/position-empty? params)
    (db/update (board/update-position params))
  (return-error "Position not empty")
(return-error "Position not valid")))


Is that an ok piece of code?


Is "empty" a valid position?


Or maybe those are just badly-named predicates?


Yes empty is a valid position


I would probably not use boolean predicates there. I would have a function that checked the position and returned a map with the position and a flag :invalid, :empty, :occupied and then do

(let [{:keys [status position] (logic/check-position params)]
  (case status
    :invalid (return-error "Position not valid")
    :occupied (return-error "Position not empty")
    :empty (db/update (board/update-position position))))

👏 8

So I have a board an I want to do 2 validations: first, if that position is inside the board (maybe inbouds is better than valid), and second if the position is empty.


I probably would not pass the whole params map to logic/check-position either -- I'd extract the position from it first and just pass that I think.


Using case makes it extremely elegant! I'll surely refactor it so it uses that...


Depending on what params actually is, I'd probably separate the knowledge of that structure out of the pure logic/ stuff and put it somewhere else...? Or if params is just a map with :x, :y for the position, I'd give it a better name 🙂


Makes sense.


I have a controller which is doing all these requests to logic. Does it make sense that the controller know that a position can only be updated if it is empty + it is inbounds?


Do you mean a Ring handler? If so, yes, the handler should know how to get the position data out of the request, and then it could ask the logic part for the status of that position (invalid, empty, occupied) and either present an error to the user or perform the update to put that new piece into the board at that position.


I'm not sure why you have logic/ and board/ tho' -- aren't check-position and update-position both just part of the game "model"?


I have a layer between the handler (which I have called service) and the logic, called controller. It is responsible for dealing with the db, and treating these errors, and so on. So basically the flow is the request gets in service-> controller -> logic to validate, and then controller-&gt;db is performed accordingly, returning if it was an ok operation.


I meant logic up there.


So there is no board/


Ah, OK. Makes sense.


Hahah sorry for confusing you.


So do you think it is ok for the controller to know which rules to check before updating a position?


Or should the knowing of the rule be inside the logic, and controller could just do logic/try-to-update or something like that.


I can only say "It depends".


I probably wouldn't have a "controller" layer at all. I'd start out with everything in the handler and refactor if it seemed "too big". I'd probably refactor board-related stuff out of the handler into a something.board namespace and the db-related stuff into I'd be using Component to manage start/stop lifecycles for anything resource-based, so I'd probably have an :application key in my Ring request, added by middleware, and in the handler, I'd pull out the database Component to pass into the storage subsystem: (storage/update (-> req :application :database) (board/place-piece position player))


Depending on whether the server is for a single game (and can keep the board in memory) or multiple games, the handler might need to start by fetching the board from the DB?


But, overall, I think it's fine for a handler to contain: * Validate parameters (if needed) * Ask storage system for data (if needed) * Ask model to check business logic (if applicable) * Tell model to perform some business logic * Tell storage system to perform updates (if needed) * Set up error response or success response


If each of those is a single function call, that's about five lines.


If any of that gets more complex, refactor it into helper functions. Over time, if there seem to be a lot of helper functions, especially shared helper functions, refactor those into some other part of the model.


Here's an example handler from one of our apps

(defn update-search-criteria
  "Update a member's My Match search criteria."
  (let [result (api-search/validate-search-criteria req)]
    (if-let [fail-codes (:fail-codes result)]
      (api/invalid-argument req fail-codes)
      (let [db-spec (-> req req/config :application :database :pooled-db)
            user    (-> req :params :user)]
        (replace-search-criteria db-spec user (:discovery result))
        (get-search-criteria req)))))
5-10 lines, validation and either return an error or call a helper (that interacts with the model and the storage), then return a (successful) result by calling another handler (which returns the member's current search criteria, so the updated results will be returned here).

👍 4

Nice!! Thanks a lot.


is cognitect.http-client not meant to be used? I picked it up after looking at its use in the aws-api project, but it seems lower level for an http-client


It's probably specialized for internal use in that app.


@johnjelinek If you're looking for a general purpose HTTP client library, clj-http is probably the gold standard.


do you like it more than http-kit (seems to have more stars in github)?


It has a lot more options than http-kit. We use both at work. I like the simplicity of http-kit and I like it's async behavior (and how easy it is to cancel an in-flight operation).


so, I should opt for http-kit then?


We're probably moving toward using http-kit more and more tho'. clj-http drags in a lot of dependencies and we've also gotten stuck on 3.4.* because there's a breaking change in 3.6.* (at least, breaking for us).

👍 4

You'll find it easier to get support/help from folks if you use clj-http because it's much more popular. But you may find http-kit simpler to use if it satisfies your needs. As with nearly everything in Clojure, there's no "one size fits all" and all library choices have trade offs.

👍 4

I've started writing a project that reads a file in to a mutable Java Object and ultimately wants to alter it and write out a new file. Is there a standardized way to "immute" it? The only thing I can think to do is to create a representation of the object in clojure literals and then do a translation from file > Object > data > f(data) > f(Object) > f(file)


@mitchell_clojure Is there a reason not to just do file > data > f(data) > file' ?


Well, my initial thought is that someone has taken the time to create a translation layer to Java and that I might be able to leverage that rather than parsing the file myself


that's probably a good intuition you're having not to want to change too much


it really depends on how complicated the parsing is, and how "mutably" the java does it


but the interface contains basically only methods that will mutate the state of the Object once it is loaded in. I'm not a dev by trade, so this might not be feasible, but I would think there would be a way to call methods on Java Instances from Clojure that just gives you a new Instance of the Object with the changes you wanted


but either way that's the pattern - turn it into data (not objects), transform data, write out data


Thanks guys, the input is appreciated and super helpful as always


@mitchell_clojure That's really going to depend on the specifics of that Java class.


If the class really only exposes read from file, mutate stuff, write to file, then you probably have no way to get at the full representation as data?


Yeah, I have thought about it some more. I think that you can treat methods applied to copies as "functions" in most cases, but sometimes you don't have access to the underlying implementation of the Object


Is it a public class whose documentation you can point us at?


yeah it is powerpoint files as described here


Oh... I'm so sorry 😐


yeah I think think that I might have to just do the legwork lol


I've only had to use POI a few times and it's always been unpleasant 😞


well, the bright side of it is that this is probably the first time where I have had a situation where I could start a project that is actually useful to some part of the community at large


just want to make sure I get the philosophy of it right, which as I see so far, is either creating a translation layer between a hiccup-like syntax to poi objects or going straight into parsing the file (which I think kind of defeats the clojure philosophy of letting the java people do the hard work)


Hey @mitchell_clojure you could use some inspiration from the various excel libraries that wrap POI. Searching for excel in clojars should give you a few hits.


I’d use a PowerPoint library for sure. Since one doesn’t exist, I’d start by just doing bare interop to cover my needs, then see if some abstraction forms.

Marcos Silva09:06:52

Hello I'm doing the exercise #2 of and got stuck, my atom don't ever change and i have no idea why (are all futures realized, am i using swap! right, etc, etc) does anyone have a clue?

Marcos Silva09:06:05

Pointers on how to improve the code are also welcomed =D


How do you know your atom never changes? Btw. you create a new atom for every "iteration".


I'd split it up more and fix the "update atom" function. Then you need to actually force the futures to execute and grab the value of the atom. I'd try something like this:

(defn- random-quote-words-counts []
  (let [quote (get-random-quote)
        values (count-words-on-quote quote)]

;; => {"see" 1, "warm" 1, "it" 2, "is" 1, "like" 1, "you" 1, "friendship" 1, "can" 1, "that" 1, "brings" 1, "on" 1, "but" 1, "peeing" 1, "feeling" 1, "yourself" 1, "only" 1, "get" 1, "everyone" 1, "the" 1}

(defn update-words-counts [atomic-counts new-counts]
  (merge-with + atomic-counts new-counts))

(update-words-counts (random-quote-words-counts) {"you" 10})
;; => {"more" 1, "going" 1, "doesn" 1, "garage" 1, "you" 12, "than" 1, "church" 1, "standing" 1, "makes" 1, "any" 1, "make" 1, "a" 3, "t" 1, "christian" 1, "car" 1, "to" 1, "in" 1}

(defn- update-atom
   (future (let [words-counts (random-quote-words-counts)]
             (swap! atomic update-words-counts words-counts)))))

(defn quote-word-count
   (quote-word-count 1))
   (let [my-atom (atom {})
         update (partial update-atom my-atom)
         ;; this is a lazy seq!
         futures (take n (repeatedly update))]
     (println @my-atom)
     ;; force futures and wait until they finishes
     (run! deref futures)
     (println @my-atom))))

  (quote-word-count 5)
  ;;=> prints
  ;; {}
  ;; {happen 1, going 1, his 2, horn 1, s 1, science 1, right 2, made 1, of 1, two 2, annoying 2, not 1, birthday 1, is 2, people 2, you 2, fiction 1, closer 1, those 1, just 1, for 1, screen 1, read 2, my 1, chair 1, recipes 1, already 1, that 1, same 1, go 2, nothing 2, think 1, brakes 1, moved 1, have 3, couldn 1, so 4, interrupting 2, on 2, tvs 1, when 2, t 1, and 1, i 7, one 2, louder 1, big 1, husband 1, talking 2, re 2, well 1, repair 1, your 2, way 1, to 5, get 1, we 1, as 2, end 1, the 3, wanted 1, there 2}


Here's the diff: Note: I didn't really read the instruction or all the code. I've only tried to fix the "missing atom updates" stuff and refactor a bit. However, my "solution" may not be the proper one for the problem you're trying to solve

Marcos Silva08:06:00

Based on your code I think I forgot to deref the futures, gonna try it Thanks so much, it gave me a direction

Marcos Silva08:06:59

it worked 👏👏👏 🍺


is there emacs/cider command that does in-ns XY from the current buffer namespace? i find myself doing (in-ns NS1), and then later (in-ns 'NS2) in repl especially in bigger projects when i am debugging or doing cross namespace stuff


C-c M-n M-n runs cider-repl-set-ns

👍 4

if you hit , in the repl there's an ns option in there as well


@dpsutton thank you!!! on my end it runs with C-c M-n

cider 4
Suni Masuno14:06:47

Just trying to add a file to an existing project as a super noob here, got ....

Exception in thread "main" Could not locate com/joe/jane/forms/mocro_test__init.class or com/joe/jane/forms/mocro_test.clj on classpath. Please check that namespaces with dashes use underscores in the Clojure file name.
I have a src/../macro.clj and test/../macro_test.clj so I'm not sure what I did wrong... I'm assuming something dumb. ns from the test...
(ns com.joe.jane.forms.mocro-test
    [clojure.test :refer :all]                                                                                                                                             
    [com.joe.jane.forms.macro :refer [=>]]))


mocro vs macro

Suni Masuno14:06:41

my shame knows no bounds

😅 4

did you get that error via running lein test?


what I would do is run a repl, and make sure that you can (require 'com.joe.jane.forms.mocro-test) -- don't rely on starting jvm's over and over via lein test


you'll catch little errors as you work. Starting things via lein test might give you an error message/stacktrace (of dubious quality) but the error is "dead" -- can't introspect it, poke at it


you want to work with live things that you can try/retry...


lein test's error reporting is not the best sometimes, and it's slow to start that process, which leads to a frustrating iteration cycle

Suni Masuno14:06:47

So that require is giving me the filenotfound even though lein test works, am I missing a step?


the require has the typo in it


i'm just saying you'd discover it quickly at the REPL

Suni Masuno14:06:42

Yeah, totally appreciating it/accepting/agreeing (tone is hard in text!) just new enough to not be succeeding at doing it >.<


no problem -- I hope you enjoy


it will take some time to get into the flow of that sort of iterative feedback, but once you get there, it will be a happy place

Suni Masuno14:06:00

Got a link to "how to test in repl?" I'm googling with mediocre success


I like the talk that Stuart Halloway gave a couple years ago:

Suni Masuno14:06:32

Is there a way to draw available namespaces at the repl? I'm pretty sure I'm just typing some little thing wrong. >.>


it kind of dives into more advanced usage, but just having a REPL open in another window and testing / messing with your code as you write it is way better than re-running lein test over and over 😅


@suni_masuno did you write this namespace in a file yourself?


you could copy + paste the ns at the top of the file


you might also want to make sure that your ns in your file matches the file path

Suni Masuno15:06:53

(ns com.joe.hane.forms.macro-test
    [clojure.test :refer :all]
    [com.joe.jane.forms.macro :refer [=>]]))                                                                                                                           

(deftest testing-testing
  (testing "that I can test things"
    (is (= 2 3))))



Suni Masuno15:06:38

So those aren't the real names, though I'll believe another typo... but it works in lein which is weird to me

Suni Masuno15:06:25

Apparently I'm just real reliant on tab completion in a cli


and so you’re doing lein repl inside of the jane dir and typing (require '[com.joe.jane.forms.macro-test]) ?


(clojure error messages take a while to get used to also)

Suni Masuno15:06:50

I was in the wrong folder!

Suni Masuno15:06:58

I knew it was something dumb... >.>

Suni Masuno15:06:30

It's working #happycry

Suni Masuno15:06:45

I have so much to learn about good workflows... -_-


(I’ve done the same thing 😉 )

Suni Masuno15:06:14

Thank you so much!

Suni Masuno15:06:14

How do I tell the repl to update with the latest code as I edit it?


(require '[com.joe.jane.forms.macro-test] :reload)


if you’re using cursive or emacs I believe there’s keybindings for that (cursive is shift + alt + L)


oh wait that only loads once? I dunno.


Cursive and Emacs both have functions (with hotkeys) for reloading a namespace in a connected REPL

Suni Masuno15:06:47

I can't find the cursive binding for that... any idea what it is?


I don’t use Cursive, but if you look up the keybindings in IntelliJ it should be something like “Load file in REPL”. anyone else know?


I use shift+alt+L to load it into the repl


and shitft+alt+R to switch to that namespace


but if not you can right click, and look for the REPL submenu and it should tell you most of the hotkeys there

Suni Masuno15:06:49

:thumbsup: So I'm using vim/terminals but a lot of my team mates were telling me that clojure couldn't do stuff. I am sharing the wisdom


hi, i'm trying to use honeysql and records. But honeysql doesn't take records for sset helper argument, cause it needs array-map. so is there any common method to covert record in array-map or i doing it wrong.


oops, forgive my question, it works


I don’t use Cursive, but if you look up the keybindings in IntelliJ it should be something like “Load file in REPL”. anyone else know?

Suni Masuno15:06:02

Ok, so repl testing is apx 1000% better. Now I'm trying to go further, and I think I'm screwing up my quoting.

(require '[clojure.test :refer [run-tests]]) 
(require '[test-location] :reload) 
(run-tests 'test-location)))

(retest com.joe.jane.forms.macro-test)
Does retestneed to be a macro or...? How do I even?


first, you can’t pass in com.joe.jane.forms.macro-test unquoted


(run-tests 'test-location) will run any tests that live in (literally) the test-location namespace (which probably doesn’t exist)


so I would change those two lines to: (run-tests test-location) (retest 'com.joe.jane.forms.macro-test)


now the next question is: does run-tests need the namespace written literally?


(because it’s a macro)


if it does, then the above change still won’t fix it


although - another option might be, if you're working at the repl - open the file with the tests that you want to run, run the editor command to switch the repl to the current namespace and then evaluate (run-tests)

Suni Masuno15:06:45

I suppose what I'm really trying to do is get down to (retest 'some-ns) and it runs that test. So if I could wrap jumping to that ns and run-tests there that's just as good. Next, still too naive, version of the first approach

(defmacro retest2 [test-location] `(do (require '[clojure.test :refer [run-tests]]) (require '[~test-location] :reload) (run-tests '~test-location)))


Doesn't need to be a macro

Suni Masuno15:06:38

(and then my secret dream is to put retest on the User ns when I jump into my or my project's repl so I can use it always and forever)


Your original function would work fine, but you don't understand quoting

Suni Masuno15:06:13

How am I misunderstanding them?

Suni Masuno15:06:24

(pretty sure I am, just wanting to learn to be better)


(require '[test-location] :reload) won't expand to your test location but to the symbol test-location

Suni Masuno15:06:02

so my next attempt...

(defn retest2 [test-location] (do (require '[clojure.test :refer [run-tests]]) (require [test-location] :reload) (run-tests test-location)))

Suni Masuno15:06:35

invoked with (retest2 'some-ns)

Suni Masuno15:06:10

Is there something like an RC for the repl that might let me put retest on my repl or my projects whenever it comes up?

Grigory Shepelev18:06:50

Hi! I started learning clojure from haskell. I don't understand a lot of clojure "devops" cycle. How can I install package from github in lein project? And if I can install packages globally?

Grigory Shepelev18:06:17

Could y please help or give a link at least


@altjsus In general, with Leiningen, you should rely on released packaged (which are on or Maven Central) and you just put the artifact name and version in your :dependencies vector.


The newer CLI tools (`clj`/`deps.edn`) are designed to let you depend directly on GitHub projects -- but they must be clj-ready, i.e., they need a pom.xml or deps.edn file (there are exceptions to this but...).


So, if you wanted to use you would add [clj-http "3.10.0"] to your :dependencies vector in project.clj for Leiningen, or clj-http {:mvn.version "3.10.0"} to your deps.edn file for clj.

Grigory Shepelev19:06:24

@seancorfield And how are they come together? Woun't it break if I use deps.edn in lein project?


lein requires project.clj


deps.edn can be used with Leiningen but you need a plugin declared (in project.clj) that understands how to read deps.edn and add those dependencies. If you're just getting started with Clojure, I'd stick to "standard" stuff: lein + project.clj is what most of the books/tutorials show.


If you want to learn about cli/`deps.edn`, the official Clojure site has this guide

Grigory Shepelev19:06:57

There is a lib I need for the project that only on Github..


Which lib is that?

Grigory Shepelev19:06:15

I wanted to play with specific one.

Grigory Shepelev19:06:21

Ok. I just test if it works and upload to clojure libs repo.


Hmm, that's never been released so you'll have to clone it and you can depend on it locally.


lein install will put it into your local library cache.


Then you can depend on [airtable "0.1.0-SNAPSHOT"]

Grigory Shepelev19:06:46

How do I install lib globally in clojure?


Not sure what you mean by that. JVM systems use a Maven cache (in .m2/repository in your home directory).


Generally libraries have to be published to a public repository for you to use them.


You don't "install" a library really. You tell lein/`clj`/etc that you "depend on" a specific library and version and it will fetch it for you automatically and cache it locally.

Grigory Shepelev19:06:41

And if I want to use one in Repl?


Easiest way is to create a folder with project.clj in, add the dependency, and then run lein repl in that folder.


Or use clj -- which lets you easily specify dependencies on the command line (see link above).

Grigory Shepelev19:06:53

Thnx. I remembet that I wanted to start with clojure couple years ago but configuring and package management scared me that days.


it's weird at first, but it's actually much safer and saner compared to how most tooling does things.


you aren't forced to make all your code use one version - each project can use appropriate versions of a package independently


and if you download a repo, you won't have to wrestle with versioning issues just to work with that code- the lib versions are all specified and specific to that codebase


It's not so bad once you get started. I tend to recommend clj/`deps.edn` to beginners these days and point them to that guide, but lein is still a lot more popular because it's been around "forever". It's unfortunate that airtable-clj never had a release (and maybe the author has abandoned it?).


You could use it via clj as a GitHub dependency (via :git/url and :sha) but you'll need to tell clj that it's really a deps.edn project (specifying :manifest :deps if I recall correctly) and you will need to specify that project's dependencies directly in your deps.edn file since it doesn't actually have pom.xml or deps.edn in the repo 😐


@altjsus If you want to go down the clj/`deps.edn` path with that library:

(! 699)-> cat deps.edn 
{:deps {airtable-clj {:git/url ""
                      :sha "09721ba5a5ca1d9b60e3ccc087d2e7d453397ee6"
                      :deps/manifest :deps}
        clj-http {:mvn/version "3.8.0"}
        cheshire {:mvn/version "5.8.0"}
        environ  {:mvn/version "1.1.0"}}}

Tue Jun 11 12:21:07
(! 700)-> clj
Clojure 1.10.1
user=> (require '[airtable-clj.core :as airtable] '[environ.core :refer [env]])


:deps/manifest tells clj to treat the project as if it had an empty deps.edn file (technically {}). Then the three dependencies from its project.clj have to be explicit in your deps.edn file.


(since I don't have an API key etc, I can't test it any further than that -- looks like it expects you to set up environment variables containing the API key and the base ID)


@altjsus Another option (which works with either lein or deps.edn) is


Basically it pretends to be a normal remote Maven repository, but the identifiers (GAV) of the libraries you reference include sufficient information for jitpack to find them on GitHub, clone them, and build them on demand for you (and then cache the results for subsequent requests).


Here are instructions on how you’d use it to retrieve EvanHahn/airtable-clj:

Javier Gonzalez-Compte23:06:11

has anyone used Chef for devops with leiningen in a CentOS machine? since leiningen has no installer