This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-03-13
Channels
- # announcements (1)
- # asami (6)
- # babashka (7)
- # beginners (94)
- # biff (1)
- # calva (18)
- # clj-kondo (35)
- # cljsrn (1)
- # clojure (7)
- # clojure-europe (1)
- # clojure-nl (1)
- # clojure-uk (1)
- # clojurescript (18)
- # core-logic (1)
- # datalog (2)
- # editors (10)
- # exercism (2)
- # figwheel-main (1)
- # fulcro (2)
- # introduce-yourself (1)
- # lsp (33)
- # malli (7)
- # off-topic (3)
- # pedestal (1)
- # portal (2)
- # re-frame (16)
- # releases (1)
- # shadow-cljs (10)
- # specter (3)
- # tools-deps (8)
Started reading Data Oriented Programming. The ideas from the book seem to make sense only in Clojure or Erlang/Elixir (functional and dynamic languages :thinking_face:). Does anyone agree with me? The author states any language, regardless if OOP or FP, can apply those ideas, but it would be too cumbersome to program like that, let's say, in Java. Clojure by the other hand was designed with such constraints and ideas in mind
Not heard of this book, but the table of contents and summaries remind me of a conversation I had with my Dev team last week. We mainly develop web services in Java and we see a very common pattern of POJOs with no behaviour being passed down to Spring beans/services. Under simple conditions, things are fine, but when the business logic is complex, we have awkwardly named classes, with no reuse. Due to the breadth of experience of the team, we get a cocktail of paradigms/ideas, for example Java functional streams with side effects and mutable variables, or mostly final/immutable classes, except for one field as it's calculated later in the procedure. In my view, languages like Java make it harder for developers to follow consistent design, but is accessible to many people. I feel that more focused languages like Clojure have that initial learning curve that forces you to think more about design. Hope this helps at all and love to hear your views!
It is true that while it is POSSIBLE to restrict one's style in many programming languages to one particular style, if you are dealing frequently with code written by others, whether within your organization or outside of it, as most people are, then you are often dealing with code written in that other person's preferred style, and in many cases you must adapt to their practices.
Rich Hickey has mentioned in some of his talks that defaults are important, and what code is idiomatic and the style it is written in by most users of the language is important.
For Clojure, that idiomatic style is much different than it is for most other programming languages.
Great points Andy. Thanks for the detail
My view is: without immutable data structures as "first class citizens", I dunno how I would apply those ideas described in the book. Even Python doesn't allow me to use that style, since dicts and lists are mutable there. I know there are libraries... I may give a try later and see how it works. Still this would be an alien idiom for my colleagues at work, I wouldn't try those new ideas there. If I use a more "aggressive" FP style with lots of lambdas, they already complain lol
If you are the only one writing code in this style, and the code is purely a personal project, then of course you can use these idioms all you want without anyone being confused, or complaining. Most software is written collaboratively.
@U035WLPF552 you can use mutable collections as if they were immutable, this happens in the implementation layer of clojure in many places. but given the choice just using an immutable data type saves a lot of trivial errors.
Hi there. What function should I use if I want something similar to cond->
but also does rebinding? For example, the following code extracts the auth token from a HTTP request:
(cond-> req
true (get-in [:headers "authorization"])
true (string/split #" ")
true (second))
Is it possible to rebind the results from each clause back to req
, i.e. by the time it hits the final clause req
will be the token? Thanks in advance.I can think of 2 ways to go about this: ->
macro and nested if-let
, but just want to make sure if there's a simpler way 🙂
Do you want as->
?
It isn’t the exact same as what you want
But it does do the rebinding for you
It isn’t conditional however
I've thought of as->
but then I realised that it only does the rebinding part and not conditional, i.e. I'll have to have if
at every clause.
Do you stop at the first false clause?
Somehow this seems like an anti-pattern.
(when-let [auth-header (get-in req [:headers "authorization"])]
(-> auth-header (string/split #" ") second))
What check do you need if an authorization header is found?
Just your usual suspects: check that it has "Bearer <token" format, check that it's a valid JWT token, check that the claims is a valid user id, etc.
What about some->
?
With some->
you’d have to complement your predicate checks
Yeah. I think I'm going about this the wrong way. Perhaps I'll just stick to nested ifs.
can’t you do the following?
(and (has-bearer-token? req) (valid-jwt-token? req) (valid-user? (get-user req))))
(cond->
{:path "/" :http-only true} ;; http-only means only the browser can read the cookie, not scripts!
secure? (assoc :secure true)
remember-me? (assoc :expires (cookie-expiry thirty-days))))
If its just a validation, then a bunch of ifs should do it
But if you want to associate data as you go along, then you’d have to use cond->
or go down to macro land
Oh I see. I thought that cond->
only threads through the forms themselves, not the predicates
In your example above, the map will be threaded through secure?
as well right? I thought that it doesn't do that and only threads through (assoc :secure true)
It won’t be threaded through secure?
it isn’t threaded through the predicate
(cond-> 1 ; we start with 1
true inc ; the condition is true so (inc 1) => 2
false (* 42) ; the condition is false so the operation is skipped
(= 2 2) (* 3)) ; (= 2 2) is true so (* 2 3) => 6
;;=> 6
;; notice that the threaded value gets used in
;; only the form and not the test part of the clause.
That’s the first example in clojuredocs
That above example should make it clear. It does not thread it through the predicate
if it did, you’d have an error (true 1)
makes no sense
What was your test?
user=> (macroexpand '(cond-> {:a {:b 3}} true? (get-in [:a :b]) false? (identity)))
(let* [G__5854 {:a {:b 3}}
G__5854 (if true? (clojure.core/-> G__5854 (get-in [:a :b])) G__5854)]
(if false? (clojure.core/-> G__5854 (identity)) G__5854))
As you can see here, it says (if true?
not (if (true? G__5854)
cond->
does not thread the expression through the test clauses, only through the forms
Thanks for the link 🙂 I think I'll just use nested ifs since I prefer not to have too many dependencies in the project
You could also make a macro writing exercise out of it and write a "pcond->" which also threads the predicates
Well yes but frankly I'm trying to keep everything as simple as possible. Macros are powerful but they can be a bit too magical for my taste. Thanks for the suggestion though 🙂
https://pastebin.com/QQHY0wPS I did my first clojure program. It's a dominoes game, still incomplete. What (functions, macros, theory) should I learn to make such script less clumsy?
In a more "C-like" language I could use "early returns" with conditionals, but separating local variables to the actual function body with let doesn't let me to do this comfortably, forcing me to use nil values in the let as this early-return thing
In a static functional language I could use Either[L,R] and pipe flatmap operations so I could at least forward the error reason, but I couldn't think easily on how to do this in Clojure
Let us start then
(def tiles
(apply concat
(for [x (range 7)]
(for [y (range (+ 1 x))]
[x y]))))
for
accepts multiple bindings and nests them.
As follows
(for [x (range 7)
y (range (inc x))]
[x y])
This removes the need for a concat
(defn- player [number n-players pieces]
{:hand (let
[n-pieces (quot 28 n-players)
i (* number n-pieces)
j (+ i n-pieces)]
(subvec pieces i j))
:name (format "Player %d" number)})
Do you really need to calculate n-pieces
repeatedly for each player? This also seems like it can easily be replaced by something like partition
. Consider using partition
hereIs this your first dynamically typed language? You are doing nil checks on every single piece of information you are using. While safe and noble, it is entirely redundant and useless. you’ve written all those functions, the only place you really need to guard is input from the user.
(defn move [game [player-pos piece-pos] position]
(let [
player (get (:players game) player-pos)
hand (if player (:hand player))
piece (if hand (get hand piece-pos))
m-piece (if piece (match game position piece)) ;; "matched piece"
new-hand (if m-piece (vec-remove hand piece-pos))
new-player (if new-hand (assoc player :hand new-hand))
new-board (if m-piece
(if (= :left position)
(into [] (concat [m-piece] (:board game)))
(conj (:board game) m-piece)))
]
(if m-piece
(assoc game
:board new-board
:players (assoc (:players game) player-pos new-player)
))))
game
is an entity of your creation player
is yours. All of these are yours
These are nested structures that you can easily get a value from by using get-in
(get-in game [:players player-pos :hand piece-pos])
(defn vec-remove
"remove elem in coll"
[coll pos]
(into []
(for [elem (map-indexed vector coll)
:let [i (elem 0)]
:when (not= i pos)]
(elem 1))))
There are many things you can fix here
First. You are assuming that you need to keep your entire hand as a vector
I can definitely think of a better way of representing your information. Consider using a hash-map instead of a vector
You can easily dissoc
from it
(elem 0)
is a bad practice
You can either considering destructuring
[[i j] (map-indexed vector coll)]
Or (first elem)
and (second elem)
Btw, I am not sure you’re reading this. @U035WLPF552
Overall, congrats on your first effort. My recommendation is to write something a bit more manageable first. Unlike C-like languages
there are a lot of important and subtle differences that you may find overwhelming
Also, your question on either
. Maybe not. https://www.youtube.com/watch?v=YR5WdGrpoug
I appreciate all effort and comments you made. Thanks a lot!
https://pastebin.com/FNNjFMKg this is the new version of the program, with most of the suggestions. Still incomplete, no error treatment for invalid moves. But I`m pleased with the new functions I learned. 😄
I think the best part of this exercise was to gain a bit of "muscle memory" with Emacs and Cider!