Skip to content

Instantly share code, notes, and snippets.

@bsless
Created February 3, 2024 10:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bsless/8b2572df1f9ffe9dfc8465ca417c9e2e to your computer and use it in GitHub Desktop.
Save bsless/8b2572df1f9ffe9dfc8465ca417c9e2e to your computer and use it in GitHub Desktop.
Compare and contrast unclebob/functor ur example and different implementation tradeoffs
;; Baseline
(defn encrypt-if-direct-message [content tags]
(if (re-find #"^D \#\[\d+\]" content)
(let [reference-digits (re-find #"\d+" content)
reference-index (Integer/parseInt reference-digits)
p-tag (get tags reference-index)]
(if (nil? p-tag)
[content 1]
(let [recipient-key (hex-string->num (second p-tag))
private-key (get-mem [:keys :private-key])
sender-key (hex-string->num private-key)
shared-secret (SECP256K1/calculateKeyAgreement sender-key recipient-key)
encrypted-content (SECP256K1/encrypt shared-secret content)]
[encrypted-content 4])))
[content 1]))
;; Functor
(def encrypt-if-direct-message
(functor
([content tags]
(if (is-direct?)
(encrypt-if-properly-referenced)
(leave-unencrypted)))
(is-direct? [] (re-find #"^D \#\[\d+\]" content))
(get-p-tag-from-content
[]
(let [reference-digits (re-find #"\d+" content)
reference-index (Integer/parseInt reference-digits)]
(get tags reference-index)))
(encrypt-content
[]
(let [recipient-key (hex-string->num (second p-tag))
private-key (get-mem [:keys :private-key])
sender-key (hex-string->num private-key)
shared-secret (SECP256K1/calculateKeyAgreement sender-key recipient-key)]
(SECP256K1/encrypt shared-secret content)))
(encrypt-if-properly-referenced
[]
(<- p-tag (get-p-tag-from-content))
(if (nil? p-tag)
[content 1]
[(encrypt-content) 4]))
(leave-unencrypted [] [content 1])))
;; Factor to functions
(defn- direct? [content]
(re-find #"^D \#\[\d+\]" content))
(defn- get-p-tag-from-content
[content tags]
(let [reference-digits (re-find #"\d+" content)
reference-index (Integer/parseInt reference-digits)]
(get tags reference-index)))
(defn- encrypt-content
[content p-tag]
(let [recipient-key (hex-string->num (second p-tag))
private-key (get-mem [:keys :private-key])
sender-key (hex-string->num private-key)
shared-secret (SECP256K1/calculateKeyAgreement sender-key recipient-key)]
(SECP256K1/encrypt shared-secret content)))
(defn- encrypt-if-properly-referenced [content tags]
(let [p-tag (get-p-tag-from-content content tags)]
(if (nil? p-tag)
[content 1]
[(encrypt-content content p-tag) 4])))
(defn- leave-unencrypted [content] [content 1])
(defn encrypt-if-direct-message
[content tags]
(if (direct? content)
(encrypt-if-properly-referenced content tags)
(leave-unencrypted content)))
@unclebob
Copy link

unclebob commented Feb 3, 2024

Yes, of course. It just seems to me that the sub function approach is prettier; especially in situations where the arguments proliferate.

@bsless
Copy link
Author

bsless commented Feb 3, 2024

I'm not averse to the sub functions, like Scheme's nested defines, but a couple of things which trade off my approach are:

  • Mental overhead. In the end this converges into the approach of modularization which requires juggling / keeping track of lots of state and arguments, some of which are lazily initialized. Contrast with pure functions and no context.
  • Testability. I can write a test for those sub functions. They aren't testable with either functor or letfn.

Your point regarding arguments proliferation is important though. I think it indicates a need for refactor / clean up of the business logic. Some concerns could use separating.

@unclebob
Copy link

unclebob commented Feb 3, 2024

I'm not too concerned about the mental overhead for small functors. Large functors should be avoided, period.
The testability issue is more concerning. However, so long as functors are small tests should be conducted through the main functor rather than the individual subfunctions. This is similar to the argument that tests should only go through public functions not through private functions.

@bsless
Copy link
Author

bsless commented Feb 3, 2024

My inner pessimist just knows those functors won't remain small.
I also adopted a weird approach where I tend to avoid private functions. I prefer moving them to an impl namespace. This makes testing more convenient, and also shows some more trust in the user.

On another subject, for performance, you could replace the lazily initialized vars backing refs with an array of size 1. That would allow you to pass primitive args, too.

If you want to avoid the allocated functions in lets, you can use them as templates and inline them. But inlining is both risky and hard

@bsless
Copy link
Author

bsless commented Feb 5, 2024

Another point brought up on Slack by @alexander-yakushev: less REPL friendly, which is somewhat associated with it being less test friendly. I can imagine wanting to tinker with the regex and having a lot of unnecessary concerns mixed in while I figure out the right regex and edge cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment