Fork me on GitHub
#clojure
<
2021-03-06
>
simongray14:03:00

If I call (keys m) and (vals m) for the same m will the order of the keys match the order of the values?

dharrigan14:03:39

It does seem to imply that

dharrigan14:03:50

user=> (doc keys)
-------------------------
clojure.core/keys
([map])
  Returns a sequence of the map's keys, in the same order as (seq map).
nil
user=> (doc vals)
-------------------------
clojure.core/vals
([map])
  Returns a sequence of the map's values, in the same order as (seq map).
nil

🙏 3
simongray14:03:06

ah, stupid me, should have just looked at the docstring

simongray14:03:52

my eyes were fixated on the source code which led me into some decompiled Java bytecode…

andy.fingerhut14:03:10

That is promised for the identical map objects. It is not promised for two different map objects for which clojure.core/= returns true.

simongray14:03:52

good to know @andy.fingerhut, but thankfully it’s the same map instance

cassiel16:03:35

If in doubt, you could just do a seq on the map which would deliver a list of [k, v] pairs which you could then split.

âž• 3
Yehonathan Sharvit18:03:41

In “https://github.com/matthiasn/talk-transcripts/blob/master/Hickey_Rich/ClojureMadeSimple.md” Rich mentions the importance of “Loose coupling” Could someone explain what does he mean by “loose coupling” and how it applies in Clojure?

seancorfield18:03:09

@viebel He talks more about it further into the preso: 40-50 mins in.

seancorfield18:03:24

TL;DR: use generic, immutable data.

seancorfield18:03:13

And nearly an hour in, he uses polymorphism a la carte as opposed to inheritance as another example for loose coupling.

andy.fingerhut18:03:39

There are other basic examples I have heard, e.g. use queues to connect two parts of software, rather than having one make calls to the other by name to pass on pieces of work.

andy.fingerhut18:03:07

The ideas of loose vs. tight coupling are, I believe, fairly language independent, although the mechanisms for doing so can of course differ between languages.

andy.fingerhut18:03:33

I suppose it would be accurate to say that loose coupling is more difficult to achieve in some programming languages than other.

Yehonathan Sharvit19:03:57

@andy.fingerhut @seancorfield Would you agree with this example? In Clojure, a namespace that manipulates a map that represents customer data is loosely coupled with customer data. While in Java, a static method that manipulates a Customer record (or a immutable data class) is strongly coupled with Customer.

Yehonathan Sharvit19:03:23

One benefit of Clojure approach is that the namespace doesn’t have to “import” the class definition for Customer

Yehonathan Sharvit19:03:17

On the other hand, a Java developer might argue that the contract between code that manipulate Customer data and Customer data shape is not explicit

Yehonathan Sharvit19:03:46

For instance, if you want to rename a field in Customer data, how would you discover all the pieced of code that need to be updated

Yehonathan Sharvit19:03:26

> We should program the insides of our sytems like we progam the outsides

seancorfield19:03:24

I think those are pretty reasonable claims. You could use Spec, if you really wanted to specify the fields that your function(s) required to be present.

Yehonathan Sharvit19:03:20

When Rich gave his talk, there was no Spec

Yehonathan Sharvit19:03:47

I think we should find answers to those objections without involving Spec

seancorfield19:03:04

I suspect this discussion belongs in a different channel since it is more philosophical -- if you're comparing other languages -- than technical.

Yehonathan Sharvit19:03:35

What channel? #data-oriented-programming maybe?

seancorfield19:03:09

Sounds like a better place.

seancorfield19:03:19

Then folks can opt-in if they wish.

borkdude19:03:12

There is also #other-languages

Yehonathan Sharvit19:03:58

Moved the discussion to #other-languages

3
jjttjj22:03:32

Clauses are [accessor predicate & args] I need a structure that can store these clauses such that I can take an input value and get back a set of matching clauses, as in, If (apply (resolve predicate) (accessor input) args)is true, return the whole "clause"

(def clause-db
  '[[:a > 0]
    [:b < 5]
    [:c #{:a :b :c}]
    [:d = 22]])

(get-matching-clauses
  clause-db
  {:a 1
   :b 2
   :c :key})
;;=>
;;; returns matching clauses
#{[:a > 0]
  [:b < 5]
  [:c #{:a :b :c}]}
I want the get-matching-clauses function to be as efficient as possible, so for instance the same accessor shouldn't be called twice on the same input even if it is used in multiple clauses. I have a full working example/use case here: https://gist.github.com/jjttjj/ad9c6355d8b6741b43f23e90c7c77442 But I'm wondering if I'm reinventing some wheel. This is something like a rules engine however I don't need any working memory, inputs are only considered in isolation. And while the total number of relevant clauses will grow over time (by wrapping the clause db in an atom) only the set of clauses that exists when the input is compared is relevant. I just need function that can take a literal representation of some "matches", in a data structure that can be appended to, and an input, and check for the matches. Any thoughts on solutions to this?

em00:03:44

Cool problem! Your solution seems fairly compact, and as long as you're not looking for automatic clause combining (stuff like collapsing (< x 20) (< x 10) ) I'm not sure going all the way to rules engines is worth it. That said, if you're already using resolve and symbols, you could consider just using clojure's homoiconicity for your clauses. I'm thinking about the case where you might need to access more than one piece of data for a clause (like comparing (< :x :y) for x and y in the input value). What about an alternative representation like [accessor-map comparison]? E.g.

(def clauses '[[{:price price} (< price 2000)]
               [{:offer offer :ceiling ceiling} (= offer ceiling)}])
Advantages here is just using very readable code as the clause, and the implementation for this would be fairly trivial and would guarantee one-time access on all keys (you would just merge all the accessor-maps into a lookup, and bind/replace symbols for each clause). If the map notation is a little wordy, you could also have an implicit rule like :keys in destructuring, where if the accessor-map is a single keyword/symbol, then that's both the keyword/symbol (i.e. [:price (< price 2000)] instead.

jjttjj00:03:47

Very interesting suggestion, thanks!

Ben Sless14:03:52

Sounds like a rule engine in reverse - the rules are the messages while the facts are the clauses

jjttjj20:03:33

Hmm I'm starting to feel maybe https://github.com/noprompt/meander will be useful here but I haven't fully wrapped my head around it yet

Ben Sless14:03:02

You can remove some dynamism from your solution, but otherwise it makes sense

(defprotocol IClause
  (-applies? [clause msg]))

(defrecord Clause0 [accessor f]
  IClause
  (-applies? [_ msg]
    (f (accessor msg))))

(defrecord Clause1 [accessor f a]
  IClause
  (-applies? [_ msg]
    (f (accessor msg) a)))

(def clause-db
  '[[:a > 0]
    [:b < 5]
    [:c #{:a :b :c}]
    [:d = 22]])

(defn compile-clause
  [[accessor f & args]]
  (let [accessor (if (symbol? accessor) (resolve accessor) accessor)]
    (case (count args)
      0 (->Clause0 accessor f)
      1 (apply ->Clause1 accessor f args))))

(def compiled-db
  (mapv compile-clause clause-db))

(defn apply-clauses
  [m]
  (into
   []
   (filter #(-applies? % m))
   compiled-db))

(apply-clauses
 {:a 1
  :b 2
  :c :key})

jjttjj15:03:50

Thanks for this @UK0810AQ2 this looks good