Fork me on GitHub
#beginners
<
2021-10-09
>
slipset10:10:31

I think I’d done it something like this:

(defn ->note [line]
  {:note/id (UUID/randomUUID)
   :note/text line
   :note/note-list {:note-list/id :singleton}})

(defn read-lines [file]
  (-> file slurp (string/split-lines)))

(defn persist-notes! [env notes]
  (run! (partial m/add-note! env) notes))

(defn do-it [env files]
  (let [notes (->> files first :tmpfile read-lines (map ->note))]
    (persist-notes! env notes)
    {:note-list/notes (set/project notes [:note/id])}))

sheluchin12:10:35

What is the point of adding persist-notes! there?

slipset18:10:10

Not sure, but it kind’a feels right. Maybe it’s a sign that the m/add-note should be a m/add-notes . Not knowing how you persist, but basically going to the database is one of the more expensive things your app does. So limiting the amount of times you do that is a quick performance win. Also, you might want to consider sticking a transaction around this, and then suddenly it might be worth its own fn.

slipset10:10:23

Depending on your taste, you might let persist-notes! return the notes it persisted, but then, again, you might not 🙂

sheluchin12:10:33

There's that old adage that a function should either create a side-effect or return a value, but not both. What do you think about that?

niveauverleih11:10:47

What is the difference between add-lib and pomegranate?

George Silva14:10:34

Hello friends, again me! I have a specific scenario here that I'm trying to understand what is better. This works, already - so it's more about what is more idiomatic. I have a collection of waypoints, they are sequential. I want to get the distance across all of them. So I calc the distance between A - B, then between B and C. I have implemented a recursive version that works well with my two argument function for distance. But, I find that using partition and map and reduce much more cleaner. However, this forces me to create a specific signature for a pair version, like below. So, what is the more idiomatic way to solve this? Distances will only be calculated in pairs. I can see a reason for having a standalone function that deals with loose points. Maybe here we can go with multi arity methods?

;; two arg version
(defn distance-meters [a b]
  (* 1000 (safe-haversine a b)))

;; single pair version
(defn distance-pairs [pair]
  (distance-meters (first pair) (second pair)))

;; map reduce
(defn sum-distance-partition [waypoints]
  (let [pairs (partition 2 1 waypoints)]
    (reduce + (map h/distance-pairs pairs))))

;; recursive version
(defn sum-distance [waypoints]
  (loop [waypoints waypoints
         total 0]
    (if (empty? waypoints)
      total
      (let [curr (first waypoints)
            lead (second waypoints)
            distance (h/distance-meters curr lead)]
        (recur (rest waypoints)
               (+ total distance))))))
Any thoughts?

George Silva14:10:47

Just a follow up! Multi arity is freaking nice! not sure if this is good clojure, but I have this now:

(defn safe-haversine [a b]
  (try
    (haversine a b)
    (catch AssertionError ex 0)))

(defn km->m [distance]
  (* 1000 distance))

(defn distance-meters
  ([a b] (km->m (safe-haversine a b)))
  ([pair] (km->m (safe-haversine (first pair) (second pair)))))
Which I can now use with the sum-distance-partition with ease. While it was good to write the loop version of sum-distance (for learning purposes), I'm going with map/reduce 🙂

dgb2314:10:52

I wouldn’t!

dgb2314:10:08

your previous solution was cleaner

dgb2314:10:45

you don’t want a function to handle different ways of passing in the parameters. A more extreme example would be accepting both a seq of maps or a map and then quasi dispatching on what you get so you can call reduce on the seq or whatever.

dgb2314:10:09

distance-pairs can be defined as apply of distance-meters to a pair

1
George Silva14:10:00

https://clojure.org/guides/learn/functions#_apply There is this in the docs, let me give it a go.

👍 1
dgb2314:10:02

But I agree that the version with partition would be more idiomatic. However, I don’t think you need map there.

George Silva14:10:00

@U01EFUL1A8M can you give me an example on this?

dgb2314:10:17

of what? using apply ?

👍 1
dgb2314:10:49

wait a second can’t you just skip the whole partition thing and reduce over the waypoints with distance-meters ?

dgb2314:10:40

as for the apply version I think this would work:

(defn distance-pairs [pair]
  (apply distance-meters pair))

dgb2314:10:43

havent tested it

George Silva14:10:17

Ahh, you mean still having a separate function. I was considering removing the multi arity signature and using apply on sum-distance with reduce, map and apply

dgb2315:10:11

consider this: is your algorithm still correct if you just reduce with distance-meters over waypoints ?

dgb2315:10:02

(defn sum-distance-reduce [waypoints]
  (reduce distance-meters waypoints))

George Silva15:10:21

I think you are correct. Let me try.

George Silva15:10:16

But, how would reduce understand what is the last input given individually, in order to calculate the distance between points? Imagine the following string of points: a --- b --- c The distance between a and c is the sum of the distance between ab and bc. When reduce gets first invoked, it will be fed a, but only that. On the second iteration, b, and only that. :thinking_face:

dgb2315:10:04

yeah you need to reduce over the pairs 🙂

dgb2315:10:46

(you don’t need map but if it makes your code clearer then keep it)

George Silva15:10:12

👍 let me give that a go.

dgb2315:10:44

btw just to be sure: you do know that you don’t need to define all functions at the top level right? You can pass an anonymous inline function fn or use letfn for functions that you only need in a specific context.

dgb2315:10:38

It’s a matter of style, if you like naming things and using defn then that is competely fine. Just wanted to make sure 🙂

👍 1
George Silva15:10:03

I know about anonymous functions, yes. But In this example I have two namespaces. I just bundled everything together to make it easier for readers here. In general, the functions defined, I believe could be reused. of couse, this is just a silly exercise, the goal is to learn, so thanks for checking 🙂

👍 1
popeye15:10:20

I started to learn implementing design pattern in clojure and I started with command design pattern, Is this the way where we can write command design pattern in clojure ?

(defn execute [command args]
  (command args))

(defn switch-on [val]
  (println "Switching on for " val))

(defn switch-off [val]
  (println "Switching off for " val))

(def light-map
  {:livingRoomLight "Living Room Light"
   :bedRoomLight "Bed Room Light"})

(def fan-map
  {:livingRoomFan "Living Room Fan"
   :bedRoomFan "Bed Room Fan"})

(defn call-function []
  (execute switch-on (light-map :livingRoomLight ))
  (execute switch-on (light-map :bedRoomLight ))
  (execute switch-off (fan-map :livingRoomFan ))
  (execute switch-off (fan-map :bedRoomFan )))

(call-function )

dgb2315:10:24

Just a pointer for further investigation, I’m not an authority on this subject, especially not in terms of design patterns / OO: In functional programming people don’t typically use this pattern or the name for this pattern, but there is a very similar thing: “effect” systems. I think they are essentially the same thing. Somewhat controversial, haven’t seen it used except in UI contexts where user actions are quite literally commands.

popeye15:10:15

I have used it from few docs and try to write it

Ben Sless05:10:46

As an aside, it looks like you reimplemented double dispatch, i.e. multimethods. You might want to check them out

Ben Sless05:10:15

One approach:

(defmulti execute (fn [[cmd & args]] cmd))
(defmulti switch-off (fn [[location & args]] location))

(defmethod execute :switch-off [_ & args] (apply switch-off args))
(defmethod switch-off :living-room [& args] "Living Room Light")

Ben Sless05:10:16

Another:

(defmulti execute (fn [[cmd location & args]] [cmd location]))
(defmethod execute [:switch-off :living-room] [_ _ & args] "Living Room Light")

slipset15:10:29

One could argue that a lot of the patterns in the GoF book is there to solve problems that are caused by short comings of the language used. You’d need the command pattern in a language where functions are not first class, in languages where they are first class, you just use functions.

☝️ 1
slipset06:10:06

To further belabour this point. In Clojure, functions are implemented by extending the abstract class https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/AFn.java, which has for all intents and purposes, an execute method, called invoke. So one could argue that a function in Clojure is an implementation of the Command pattern.

👀 1
popeye15:10:49

Which are the design pattern we can practice in clojure?

dgb2315:10:10

I would look for “idioms” rather than “patterns”.

popeye15:10:08

Sorry, I don't get you

dgb2316:10:35

@U01J3DB39R6 For one, the classic OO design patterns that some of us learned a while ago, are in large parts obsolete. Even in say Java, because it, and other big OO languages, introduced closures, DTO’s records and so on. Many of these patterns were basically workarounds for not having first class data and function objects. To add, some of the patterns are controversial regardless. Secondly Clojure is basically built on first class constructs like data and function literals, but also macros, ad-hoc dispatch, multiple dispatch and so on, that further obsolete some of the patterns. The standard library then gives you a ton of stuff on top of that so you generally don’t have to reach for pattern abstractions, but rather just use what is there. That last part is important and might be the right way to go about this I think. Instead of looking for patterns, study and use what is already there: core especially, zippers, logic, match, async… Then, then are “idioms”, which emerge from using and composing the above. They are often explicit! Read the doc strings of a function and it often becomes clear what the idiom is (Clojure is very opinionated). Sometimes they emerge from practice. For example wrapping a protocol implementation with a defn or wrapping side effects with do and so on. The other thing that is helpful is to think in terms of functional algorithms and the underlying data structures you are using. What their characteristics are and when to use which one. How to them into efficient compositions and so on.

👍 4
popeye16:10:02

thanks for the write @U01EFUL1A8M 🙂 , yeah I have learned all of them, Now I thought let me do some exercise on design pattern so that, i get more confident 🙂

dgb2316:10:26

Ahh I see what you mean now 🙂 well in that case, what works for me: solve problems that you find interesting or build tools that you find useful!

dgb2316:10:51

Also recently there was an announcement of http://exercism.org having expanded their clojure exercises. I think most of these are algorithmic but there are also idioms in there. And typically you can find a lot of solutions that you can compare against, there is always one that’s very elegant.

🙌 2
Nik15:10:45

In clojurescript, I'm trying to use https://github.com/isomorphic-git/isomorphic-git library (ns app.main.vcs (:require ["isomorphic-git" :as git])) Usually I get functions that I can use, but here when typing git in repl gives #js {:Errors #object[Object], :STAGE #object[STAGE], :TREE #object[TREE], :WORKDIR #object[WORKDIR], :add #object[add], :addNote #object[addNote], :addRemote #object[addRemote],... These are all actually the functions defined in js source code eg https://github.com/isomorphic-git/isomorphic-git/blob/main/src/commands/WORKDIR.js so I'm confused why they show up as map kv pairs. How do I call these functions? I tried (goog.object/get git "WORKDIR") I got #object[WORKDIR]and doing ((goog.object/get git "WORKDIR")) gives #object[Object] Tried ((js/Symbol "GitWalkSymbol") (w))as well got

Execution error (TypeError) at (<cljs repl>:1).
Symbol(...).call is not a function