Fork me on GitHub
#beginners
<
2024-07-09
>
James Amberger00:07:37

(defrecord A []
  P
  (x [_] nil))

;; vs

(defrecord A [])
(extend A
  P
  {:x (fn [_] nil)})
You wouldn’t write the latter without a reason, but other than that, is there any difference between these?

hiredman01:07:23

Yes, inline extensions (like the first) result in the class generated by the defrecord implementing an interface generated by the defprotocol, which at least in theory the jvm jit can optimize better

hiredman01:07:01

The second case, the extension is stored in a map and there is an inline cache thing to try and avoid the map lookup, in theory it exposes information so the jit can still generate nice code, but it is hard to imagine it will match the interface case

hiredman01:07:09

(also use extend-type or extend-protocol in preference to extend itself)

exitsandman06:07:21

The only benefit to separate extend I can think of is that it's friendlier at the REPL, because it doesn't force you to update everything that depends on the class just to redefine a protocol's behavior. However, it's a big perf loss. (and yes, you probably want to use extend-type or extend-protocol instead rather than extend . extend and extend-type/`extend-protocol` is however an excellent example of a good way to make macros: the core functionality is implemented within a function, and the only responsibilty given to the macro is to expose the behavior with a better syntax and augment it with information that is exclusively available at compile time (the type being extended, which is used to hint the first arg and avoid reflectve calls).

phill10:07:15

The record has no members. If it had members, they would be in the local environment of the functions implemented inside the defrecord.

James Amberger13:07:33

Thank you; very helpful discussion as usual. > (also use extend-type or extend-protocol in preference to extend itself) Except when the following applies, right? > • Function maps are maps of the keywordized method names to ordinary fns > • this facilitates easy reuse of existing fns and maps, for code reuse/mixins without derivation or composition And in that case, “However, it’s a big perf loss” is just the price you pay? > The record has no members. If it had members, they would be in the local environment of the functions implemented inside the defrecord. Whereas if you used extend with an fn map defined elsewhere, you would just have to (:member this) instead of member — is that important? Basically thinking out loud and hoping to be caught out in any error; hope this posting style is not annoying.

Nim Sadeh17:07:59

I am trying to replicate the following Node.js code:

const crypto = require('crypto');

const secret    = '[SIGNING_SECRET]';
const hmac      = crypto.createHmac('sha256', secret);
const digest    = Buffer.from(hmac.update(request.rawBody).digest('hex'), 'utf8');
const signature = Buffer.from(request.get('X-Signature') || '', 'utf8');

if (!crypto.timingSafeEqual(digest, signature)) {
    throw new Error('Invalid signature.');
}
Using buddy-core for cryptography, but can't seem to get it to work. I verified via logging that the various components are being read correctly. Here's my code:
;; request handler
(defn handle-webhook
  [{{:strs [X-Signature]} :headers
    :keys [body-params]}]
  (println ::payment-webhook
           :body body-params
           :signature X-Signature)
  (if (authentic?
       (cheshire/generate-string body-params)
       X-Signature)
    {:status 200}
    {:status 403
     :body "Failed authentication"}))

;; function to compare hashes

(defn authentic?
  "Given a request payload, ensure it was signed with the secret provided for authenticity.

   Params:
   - payload: the string payload that comes through the request
   - signature: the signature header as a byte array"
  [payload signature]
  (-> payload
      (mac/verify signature
                  {:key env/hash-secret
                   :alg :hmac+sha256})))
I tried all forms of converting stuff to hex or bytes. I also have an example payload as a JSON file with the signature it came with. I verified that the sender and my app share the same secret.

Mario Trost15:07:45

Have you tried:

(require '[buddy.core.codecs :as codecs])
...
 (-> payload
     (mac/verify (codecs/hex->bytes signature)
                 {:key env/hash-secret
                  :alg :hmac+sha256}))

Mario Trost05:07:27

@U05D3EAA6FM Did this solve your issue?

Nim Sadeh12:07:58

Thank you, yes, sorry that I did not return. The issue was not codecs but rather middleware that did things to the request body. We had to setup a separate router for this endpoint in reitit/ring that had less middleware applied to it. Thanks for your help

👍 1
growthesque18:07:33

since both lists and vectors are sequential, what's the main benefit of lists over vectors?

growthesque18:07:35

The Brave book says: "A good rule of thumb is that if you need to easily add items to the beginning of a sequence or if you’re writing a macro, you should use a list. Otherwise, you should use a vector."

Bob B18:07:04

literal lists are generally used for code, so if it needs to be framed as a 'benefit', then a list has the benefit of calling the first thing in the list with the rest of the list of arguments

growthesque18:07:53

I am asking about the list collection type, i.e. '(1 2 3) over [1 2 3]

Bob B18:07:48

there are also some use cases when using a data structure as a stack - pop and peek operate on the front a list and the end of a vector, so if you wanted to pass something into a function that's working on a stack, you could pass a list to have the stack consumed from the front or a vector to have it consumed from the end

daveliepmann18:07:03

I think I saw Rich or Alex somewhere say to ~never prefer lists over vectors except when creating sexps. (I'm sure other specific situations like making a stack would count too.) Let me see if I can find it

1
daveliepmann18:07:57

apparently Rich on irc: > when generating code, when generating back-to-front > not too often in Clojure

growthesque18:07:17

are lists actually seqs or just very similar? I mean seqs are actually a different type, no?

daveliepmann18:07:30

seqs are printed like lists but they are a distinct thing

exitsandman19:07:25

lists should be cheaper than vectors if all you need is a stack, but I never profiled and it should be very marginal so don't quote me on that.

phill21:07:28

Conceivably, "the rest" of a list is a little cheaper to compute than for a vector. But vectors offer random access & more structural sharing (the only structure two lists can share is "the rest" from some point)

Sam Ferrell22:07:56

its basically impossible to create a lisp or a functional language without a linked list

Godwin Ko23:07:18

List is actual data structure for sequential access, but seq is an abstraction interface for sequential access

didibus08:07:24

Clojure is a Lisp, and all Lisp use a singly linked list to represent code. So from that historical lineage it made sense to represent code as a list and therefore the need to have a list data-structure. Singly linked list are also great for structural sharing, adding an element shares everything except the new element. It's really easy to implement, you just have a new head pointing to elements within the rest of the list at where they differed. They also offer real O(1) inserts. They're fast to iterate over recursively, first is O(1), rest is O(1), ans you can build up a new one easily as you go. So since they are so simple to implement in an immutable way, and are good for recursion, they're a good first data structure to have, and good for representing and manipulating code. But vectors are so much more versatile, they're used more often in practice, They're super hard to implement though, so you'd imagine a few first alpha version of Clojure wouldn't have had them yet.

didibus08:07:09

Seq is an abstraction, think interface. It's has many implementation, one is PersistentList and one is LazySeq. So Seq can be the same as List, but often will be LazySeq. But it can also be vector$chunkedSeq and whatever else is seq?

exitsandman10:07:39

Clojure was built with structure-sharing maps and vectors at the core from day 1 iirc

didibus15:07:20

Day 1? Are you sure? I'd expect it to be something I had half way into the language first release. You'd want to get the data structure for representing code up first logically.

didibus15:07:53

If you mean "on first public release" sure. I'm talking from a language development point of view.