Fork me on GitHub
#beginners
<
2022-03-13
>
Danilo Oliveira08:03:35

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

Mike Rocke13:03:38

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!

andy.fingerhut16:03:50

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.

andy.fingerhut16:03:29

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.

andy.fingerhut16:03:49

For Clojure, that idiomatic style is much different than it is for most other programming languages.

Mike Rocke17:03:46

Great points Andy. Thanks for the detail

Danilo Oliveira21:03:31

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

😂 1
andy.fingerhut21:03:57

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.

noisesmith23:03:03

@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.

aratare08:03:26

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.

aratare08:03:42

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 🙂

craftybones08:03:56

Do you want as-> ?

craftybones08:03:39

It isn’t the exact same as what you want

craftybones08:03:44

But it does do the rebinding for you

craftybones08:03:03

It isn’t conditional however

aratare08:03:36

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.

craftybones08:03:10

Do you stop at the first false clause?

aratare08:03:57

I don't really mind short circuiting but it'd be nice to have it.

craftybones08:03:02

Somehow this seems like an anti-pattern.

craftybones08:03:33

(when-let [auth-header (get-in req [:headers "authorization"])] 
  (-> auth-header (string/split #" ") second))

craftybones08:03:30

What check do you need if an authorization header is found?

aratare08:03:26

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.

craftybones08:03:17

What about some-> ?

craftybones08:03:07

With some-> you’d have to complement your predicate checks

aratare08:03:19

Yeah. I think I'm going about this the wrong way. Perhaps I'll just stick to nested ifs.

craftybones08:03:30

can’t you do the following?

dharrigan08:03:03

I do that, i.e.,

craftybones08:03:25

(and (has-bearer-token? req) (valid-jwt-token? req) (valid-user? (get-user req))))

dharrigan08:03:27

(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))))

dharrigan08:03:41

that will thread the map all the way through the predicates...

craftybones08:03:07

If its just a validation, then a bunch of ifs should do it

craftybones08:03:16

But if you want to associate data as you go along, then you’d have to use cond->

craftybones08:03:32

or go down to macro land

aratare08:03:32

Oh I see. I thought that cond-> only threads through the forms themselves, not the predicates

dharrigan08:03:47

err, yes, thta's what I meant 🙂

aratare08:03:53

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)

craftybones08:03:34

It won’t be threaded through secure?

craftybones08:03:48

it isn’t threaded through the predicate

craftybones08:03:14

(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.

craftybones08:03:19

That’s the first example in clojuredocs

aratare08:03:46

> that will thread the map all the way through the predicates...

aratare08:03:54

I'm a bit confused sorry

craftybones08:03:13

That above example should make it clear. It does not thread it through the predicate

craftybones08:03:30

if it did, you’d have an error (true 1) makes no sense

aratare08:03:06

well I just tested it

aratare08:03:12

it did thread through the predicates

craftybones08:03:43

What was your test?

aratare08:03:24

(cond-> {:a {:b 3}} 
  true? (get-in [:a :b]) 
  false? (identity))

craftybones09:03:42

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))

craftybones09:03:31

As you can see here, it says (if true? not (if (true? G__5854)

aratare09:03:03

Ah my mistake then. Guess I'm back to nested ifs 😅

craftybones09:03:09

cond-> does not thread the expression through the test clauses, only through the forms

aratare11:03:22

Thanks for the link 🙂 I think I'll just use nested ifs since I prefer not to have too many dependencies in the project

craftybones11:03:23

Great. Explicit does trump implicit more often than not

1
Ben Sless05:03:29

You could also make a macro writing exercise out of it and write a "pcond->" which also threads the predicates

aratare11:03:44

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 🙂

Ben Sless11:03:10

BTW I think some-> might suit your needs

Danilo Oliveira21:03:33

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?

Danilo Oliveira21:03:04

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

Danilo Oliveira21:03:46

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

craftybones01:03:31

Let us start then

craftybones01:03:40

(def tiles
  (apply concat
         (for [x (range 7)]
           (for [y (range (+ 1 x))]
             [x y]))))

craftybones01:03:02

for accepts multiple bindings and nests them.

craftybones01:03:12

(for [x (range 7)
      y (range (inc x))]
  [x y])

craftybones01:03:00

This removes the need for a concat

craftybones01:03:54

(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 here

craftybones01:03:41

Is 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.

craftybones01:03:07

(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)
             ))))

craftybones01:03:39

game is an entity of your creation player is yours. All of these are yours

craftybones01:03:55

These are nested structures that you can easily get a value from by using get-in

craftybones01:03:40

(get-in game [:players player-pos :hand piece-pos])

craftybones01:03:45

(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))))

craftybones01:03:13

There are many things you can fix here

craftybones01:03:57

First. You are assuming that you need to keep your entire hand as a vector

craftybones01:03:36

I can definitely think of a better way of representing your information. Consider using a hash-map instead of a vector

craftybones01:03:45

You can easily dissoc from it

craftybones01:03:33

(elem 0) is a bad practice

craftybones01:03:42

You can either considering destructuring

craftybones01:03:12

[[i j] (map-indexed vector coll)]

craftybones01:03:22

Or (first elem) and (second elem)

craftybones01:03:48

Btw, I am not sure you’re reading this. @U035WLPF552

craftybones01:03:14

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

craftybones01:03:57

Also, your question on either. Maybe not. https://www.youtube.com/watch?v=YR5WdGrpoug

Danilo Oliveira06:03:44

I appreciate all effort and comments you made. Thanks a lot!

Danilo Oliveira08:03:57

Thanks for teaching me how to write more idiomatic code!

clojure-spin 4
Danilo Oliveira08:03:02

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. 😄

Danilo Oliveira09:03:07

I think the best part of this exercise was to gain a bit of "muscle memory" with Emacs and Cider!