Fork me on GitHub
#beginners
<
2022-11-05
>
skylize03:11:17

Is there a simpler way to apply a map to a Record constructor?

(defrecord Foo [a b c])

(defn foo-from-map [m]
  (let [{:keys [a b c]} m}]
    (->Foo a b c)))

(let [my-foo (foo-from-map {:a :foo
                            :b :bar
                            :c :baz}]
  ...)

phronmophobic03:11:45

It gets generated by defrecord just like ->Foo

skylize03:11:55

Perfect. I knew that should be included. 🌷

👍 1
skylize04:11:00

Oh cool. That's also how I can get "optional", but still specified fields. I can leave out a field in the map if I'm okay with it being nil.

phronmophobic04:11:37

What do you mean by optional, but still specified?

phronmophobic04:11:07

If you're not implementing any protocols or interfaces, you should be able to just use a regular map

skylize04:11:15

Meaning the Record has the field, but I don't need to mention it on construction.

phronmophobic04:11:53

Even if you are implementing interfaces/protocols, you can also just create a record with no specified fields. You can always assoc additional keys.

skylize04:11:48

Yeah, but a real field is slightly different from being able to assoc on arbitrary keys later.

skylize04:11:03

In potential performance, but also in self-documentation of intended use.

valerauko13:11:13

could someone give me an example of defn where all these things are present?

(defn name doc-string? attr-map? ([params*] prepost-map? body) + attr-map?)

Fredrik13:11:10

I don't think I've ever seen that final attr-map? actually used in practice. But a toy example:

(defn foo "Returns true if all arguments are greater than zero"
  {:version "1.0"}
  ([x y]
   {:pre [(every? number? [x y])]}
   (every? pos? [x y]))
  ([x y z]
   {:pre [(every? number? [x y z])]}
   (every? pos? [x y z]))
  {:another :attribute})

Fredrik13:11:51

It get's merged into the function's metadata:

(meta (var)) =>
{:another-attribute :bar,
 :arglists ([x y] [x y z]),
 :column 1,
 :doc "Returns true if all arguments are greater than zero",
 :file "playground.clj",
 :line 1,
 :name foo,
 :ns #namespace [playground],
 :version "1.0"}

valerauko13:11:33

i'm especially confused about that final attr-map?

Alex Miller (Clojure team)14:11:54

It’s an extra place to put metadata. The thought was that if you had a big metadata map it might obscure the top of the function signatures so you can put it at the end to get it out of the way. It’s rarely used.

🙀 1
valerauko14:11:29

Thanks! I'm writing a macro that outputs defn and I needed some reference what that is

Wen Hou17:11:38

hello, i can’t find the answer for this, how do i do multiple things in cljs lambda callback? do i use do keyword? like this: {:on-click #(do (mutate component state) (mutate global state))} ?

👍 1
skylize18:11:18

A function body already implies do semantics. Each form will be evaluated, and the output of the last expression is returned. {:on-click #((do-thing) (another-thing))} If that isn't working, then either there is some other bug in the code or you are bumping against the limitations of the #() macro. Not all functions can be correctly transformed by that macro.

Wen Hou18:11:18

got it, thanks!

Fredrik20:11:44

If I understand correctly, the reading of a #(form) form gets converted into (fn [args...] (form)) , meaning there is no implicit do like for fn. So the do must be added manually like you have 🙂

skylize21:11:43

Yeah. Sorry Wen Hou. I made a mistake in my previous post, because I would not even be trying to use the lambda macro that way. I reserve #() for very short and straight-forward functions. What I said is true for normal lambdas, but not for #().

{:on-click (fn [] (do-thing) (another-thing))}

skylize21:11:51

The problem is the macro expands into a function-of-a-list-of-forms, instead of into a function-of-multiple-forms.

(macroexpand `#((prn :foo) (prn :foo)))

(fn*
 []
 ((clojure.core/prn :foo)
  (clojure.core/prn :foo)))
Stepping through evaluation of that brings you to...
(fn*
 []
 (nil (clojure.core/prn :foo)))
... trying to call nil as a function. So, as I did say correctly, not all functions can be correctly transformed by the reader macro.

Wen Hou17:11:16

i see, this is similar to the issue (#([1 2])) won’t work, i should use (#(vector 1 2)) . thanks for explaining, i learned how to use macroexpand 🙂

Fredrik17:11:19

It might be interesting to point out, as was said, that #(...) is a reader macro, not an ordinary macro. This means that the transform to fn* happens at read time, which is before compile time. Try evaluating this:

(read-string "'#((prn :foo) (prn :foo))")

skylize17:11:16

And there is also macroexpand-1, in case of nested macros that you only want to view the first rewrite. macroexpand calls macroexpand-1 in a loop.

Wen Hou17:11:05

@U024X3V2YN4 i am not sure i understand, is there good resources i can read to understand why #(…) has to be a reader macro ?

Fredrik17:11:16

Clojure has a number of these reader macros . You can learn more about them at https://clojure.org/reference/reader#macrochars and https://en.wikibooks.org/wiki/Learning_Clojure/Reader_Macros. These macros modify the behaviour during the read phase, while macros created by defmacro are functions that take what's returned by the reader and converts it to another expression during the eval phase.

Wen Hou18:11:21

cool, thanks !

skylize18:11:29

Reader macros can transform the syntax of the language entirely (in this case giving special meaning to a # prepended to a list). Plain macros calls appear as the first entry of a list, just like function calls. Many Lisp languages let you define your own reader macros. But Rich intentionally left that feature out of Clojure to avoid the community fracturing into different dialects with varying resemblance to the base language.

Jesse Snyder19:11:46

Starting in on “Clojure for the Brave and True”. Is Leiningan still the preferred way to bootstrap, or has Boot (or something else) taken over?

Bob B19:11:16

Leiningen is still a big player, Boot is out there but I don't know that it has a ton of market share, and another big player is the deps CLI: <https://clojure.org/guides/deps_and_cli>

👀 1
kennytilton19:11:32

I do not hear much about Boot these days. Leiningen is still super popular and my favorite. The new deal is deps. When trying sth new, I just start from its intro repo and inherit whatever build tool it used.

Jesse Snyder19:11:14

Going to run with Leiningen, then!

kennytilton19:11:54

btw, when Brave/True came out Boot was indeed the new kid in town catching a lot of enthusiasm.

Ben Lieberman19:11:19

I'm a relative newcomer to Clojure myself but my two cents (if they are worth that) is to use Deps CLI. I find the guides quite thorough and there's also https://github.com/seancorfield/deps-new

Jesse Snyder19:11:46

Oh no - choices! 😉

clojure-spin 3
kennytilton19:11:57

Build tools: the Dark Side of Clojure. 👹

Jesse Snyder19:11:23

I guess there’s a dark side to every ecosystem. With Python it’s packaging that everyone hates and complains about.

seancorfield19:11:25

@U049KEXDHL5 See https://clojure.org/news/2022/06/02/state-of-clojure-2022#_working_with_clojure for a graphic of relative use of Leiningen, CLI/`deps.edn` and other tooling. That survey was run early this year so you can extrapolate where we are now and where we are going.

👀 2
Jesse Snyder20:11:26

Ah, OK. Bye bye, Boot.

seancorfield20:11:53

At work, we started with Leiningen back in 2010-ish, switched to Boot in 2015, then to the CLI and deps.edn in 2018. And we're very happy with that for our 130k line monorepo, building two dozen services. See https://corfield.org for some blog posts about our switch to Boot and our later switch to the CLI if you're interested in more detail.

Jesse Snyder20:11:16

Excellent, thanks!

Chase20:11:21

Yeah, Leiningen is still perfectly viable and probably easier for beginners to explore Clojure. I would stick with that, especially when following books/tutorials using it. The community is gravitating more towards the cli/deps.edn and when you are ready to explore those I find this video pretty beginner friendly: https://www.youtube.com/watch?v=8aCO_wNuScQ

💯 3
👀 1
Sam Ritchie22:11:02

I would recommend leiningen for beginners

Sam Ritchie22:11:29

Tools.deps is my choice now too but without a good mental model for the data structures it’s quite confusing how all the tiny granular confit pieces you write merge together

pavlosmelissinos10:11:45

I don't see the problem with the clojure CLI to be honest. What stops a beginner from starting with an empty deps.edn and adding stuff to it from there organically, as they need it? What does leiningen offer out of the box that the clojure cli doesn't (and how does that make it confusing)? And would it be less confusing to learn leiningen and then switch at some point? edit: that said, I don't think it matters what you choose, both are good at what they do

practicalli-johnny10:11:59

The Clojure code for the app / library being written is the same regardless of build tooling. Leiningen is an all-in-one tool, so only Java and Leiningen install and your ready to code. Clojure CLI is very flexible and highly configurable, so initially a little more config (learning) involved, but after that there is not much difference. I maintain https://github.com/practicalli/clojure-deps-edn which bootstraps the Clojure CLI configuration with a large range of community tools to give more of an all-in-one experience

practicalli-johnny11:11:38

lein help lists all the tasks it can do, many of these tasks require a community tool for Clojure CLI. switching between Leiningen and CLI is a matter of creating a deps.edn file with the same dependencies, as long as Leiningen plugins that do a little magic with code are not involved (e.g. lein-ring). Leiningen dev-dependencies would become Clojure CLI aliases (as optionally would Leiningen profiles and aliases) There is some community tooling to help create a deps.edn from a project.clj if really needed. If there is some Clojure code in project.clj other than defproject (this is relatively rare) then that would probably be added to a custom user.clj file (or just not included) It's possible to have both Leiningen project.clj and deps.edn configs, although dependencies should be kept in sync, so typically one configuration will eventually take precedence. I don't see any issue starting with either Leiningen or Clojure CLI and then learning to use the other one as required, they have a lot n common and share a lot of the same features, although Clojure CLI needs to be configured to include many of those features.

kennytilton11:11:04

@U05254DQM wrote: "Clojure CLI is very flexible and highly configurable, so initially a little more config (learning) involved" I think this is where the wheels come off for me. I just want to write my app. I am not interested in learning how to build, beyond what should be three commands. Back in the day we had one command for compile, one for link, away we went. sigh But thx for the motivation! I was going to whine "Where is lein deploy clojars for deps?" and checked and discovered https://github.com/slipset/deps-deploy. Looks promising! I actually do not like being behind the eight ball technically, and deps is clearly the future... is there a "Deps for dummies" article out there, or mebbe a "Deps for Leiningen refugees" write-up?

practicalli-johnny15:11:31

For many projects and teams it’s perfectly fine to stick with Leiningen, especially if it’s a very (Leiningen) standard project setup and Leiningen just works for the team. However, there are setups and teams that want to take a different approach to that provided by Leiningen (which has some constraints, especially around plugins). Configuring and customising Clojure CLI is simpler in my view (not necessarily easier when used to Leiningen). I would encourage teams to look at Clojure CLI when they have time to do so, as its an approach that I find very productive and a very declarative and data centric approach. Although do remember that Clojure CLI was not designed as a replacement for Leiningen, but can be a very effective foundation for providing the same functionality in a highly flexible way. tools.build is also evolving very nicely to complement Clojure CLI and allow for a simple yet powerful build tool approach.

practicalli-johnny15:11:49

In https://github.com/practicalli/clojure-deps-edn#common-development-tasks I have tried to capture most of the common tasks for a Clojure project, with their command and if the aliases required are built into Clojure or part of the project or user deps.edn configuration

jrotenberg16:11:35

> Build tools: the Dark Side of Clojure. laughs in Maven and Gradle

🙀 2
jrotenberg16:11:17

for me, lein feels a bit more like build tools from other ecosystems, which is probably a great place to start.

jrotenberg16:11:09

while deps has a more minimal, “i’ll tell you what tools i need and how to run them” thing going on

Jesse Snyder00:11:23

Thanks for the plethora of thoughtful replies, folks!

❤️ 1
Sam Ritchie00:11:20

Good luck with the book!