Fork me on GitHub
#clojure
<
2018-10-19
>
tristefigure10:10:40

Is there a way to redefine function calls ?

(with-redefs [+ -] (apply + [1 1]))
=> 0

(with-redefs [+ -] (+ 1 1))
=> 2

bronsa10:10:40

you need to disable direct linking

🙏 4
darwin14:10:35

@timo.freiberg Writing clojure.spec for kwargs-api is more difficult than for explicit maps, IMO

isak14:10:48

@timo.freiberg there are also some arguments against kwargs in Elements of Clojure

Alex Miller (Clojure team)14:10:12

you can use s/keys* in spec for this - it’s quite easy

Timo Freiberg14:10:54

Ah, there's one argument gone ;)

Alex Miller (Clojure team)14:10:25

kwargs are pretty useful at the human edges

Alex Miller (Clojure team)14:10:40

often more trouble than value in the internals

Timo Freiberg14:10:42

I'm gonna look into the elements of clojure reference

darwin14:10:00

ah, thanks, didn’t know s/keys* existed and was wondering how would I do that 🙂

Timo Freiberg14:10:29

@alexmiller the function is currently the only public function of a new library

Timo Freiberg15:10:04

ok i'm not going to buy elements of clojure to read the argument agains kwargs 😕

Timo Freiberg15:10:55

one thing i quite like about using kwargs with destructuring in the signature is that the function documentation immediately shows you the available keywords and their default values

Timo Freiberg15:10:21

when using an option map i would have to spec it or reference a map of defaults in the docstring

Timo Freiberg15:10:33

unless there are better options i haven't though of yet?

mpenet15:10:04

what's different with map destructured in the signature?

mpenet15:10:36

As long as you destructure it (so that it shows for the docs) kwargs or not is the same in that respect

dpsutton15:10:55

in emacs the function signature appears at the bottom so you see func-name [{:port :hostname ...}]

Timo Freiberg15:10:28

oh true, i can just destructure the option map as well and have the exact same docstring... woops :thinking_face:

Timo Freiberg15:10:00

so it seems to me like the pros of option maps win out slightly, although i like not having to type opening and closing braces

futuro15:10:08

Working on a codebase that has a lot of kwargs immediately wrapped in a map, they've been a huge ergonomic loss. I can't get nice alignment of the arguments from my editor (emacs), I can't easily pass the kwargs I got higher up the callstack to lower down, we use a lot of medley/mapply to unpack maps into kwargs.

futuro15:10:40

I've found them to be a greater pain than benefit, especially because it's somewhat random which functions will take kwargs and which will take a map.

futuro15:10:26

Pretty much every time I see (defn foo [x & {:as opts}]) it strikes me as a codesmell.

👍 4
Timo Freiberg16:10:38

thanks for the soapbox!

futuro16:10:34

Hehe, I hope it helps 😅

futuro15:10:29

</soapbox>

jsabeaudry15:10:03

(deftest underscore
  (let [a- "foo"
        a_ "foo"]
    (is (= a_ a-))
    (is true)))

jsabeaudry15:10:19

This does not compile on clojure 1.9 ?!?!

andy.fingerhut15:10:08

At least in namespace names, and also in defn names and apparently let symbols, dashes are changed to underscores to make them legal class/package names in Java.

Alex Miller (Clojure team)15:10:10

kind of interesting - any time you get both a- and a_ closed over, those create fields in the closed over function with the same munged name

Alex Miller (Clojure team)15:10:37

I think that can be reasonably called a bug

jeff.terrell15:10:45

(let [a- "foo" a_ "bar"] (= a_ a-)) yields false for me on Clojure v1.9.0.

Alex Miller (Clojure team)15:10:28

no closed over function here, so doesn’t trigger it

Alex Miller (Clojure team)15:10:18

(let [a- "foo" a_ "bar"] #(= a_ a-))

👍 8
Braden Shepherdson15:10:56

the compiler error is much better than silently proceeding as if both were called a_.

dpsutton16:10:14

and it's a good compiler error:

dpsutton16:10:22

>>> user=> (let [a- "foo" a_ "foo"] #(= a- a_)) CompilerException java.lang.ClassFormatError: Duplicate field name "a_" with signature "Ljava.lang.Object;" in class file user$eval155$fn__156, compiling:(NO_SOURCE_PATH:13:1)

isak16:10:46

for clojurescript, we don't seem to get the compiler warning. We get this js:

(function (){var a_ = "foo";
             var a_ = "bar";
             return ((function (a_,a_){
               return (function (){
                 return cljs.core._EQ_.call(null,a_,a_);
               });
               ;})(a_,a_))
            })().call(null);

noisesmith17:10:07

so it erroneously fails to error

✔️ 4
mfikes17:10:47

There is a ticket covering this sort of stuff: https://dev.clojure.org/jira/browse/CLJS-2461

👍 4
isak18:10:02

when creating stateful components in component system like integrant, am I wrong to want to only expose a subset (or an immutable view, even) to the rest of the system? Is this possible currently? Seems like no.

noisesmith18:10:46

this is trivial to do in stuartsierra/component - the start method is a closure, you can return what you like

isak18:10:07

Example: component that maintains a datascript DB, and a timer to incrementally update it, but then only exposes the immutable view of the DB to the rest

isak18:10:37

@noisesmith interesting. What if you need some of the state in order to be able to "stop" it later?

borkdude18:10:05

@isak I see it more like OO, it’s an object with state and you could expose function to mutate the internal state, like a setter would on an object

isak18:10:42

@borkdude I agree it is pretty OO type thinking. Though if I do the obvious thing (e.g., return a record), then I also expose maybe more would make sense to the rest of the system. Also make it more clunky to use.

borkdude18:10:11

Expose only the public API, let the component deal with the implementation details of managing the state.

borkdude18:10:47

Or create functions that given that component, do something with it and consider that your API.

isak18:10:27

Maybe with a record that implements deref to return the immutable view, it would be good enough, and just treat reaching into it otherwise from other components as a nono by convention.

borkdude18:10:58

Not sure what you mean by record here. a defrecord? a database record?

isak18:10:09

yea, defrecord

borkdude18:10:57

If you don’t want others to mutate the db, create a bunch of functions that provide read-only access and expose only those?

isak18:10:59

I can see your strategy working too, just worried I'd have to copy and paste APIs that are already fine

isak18:10:35

@borkdude fair enough, that makes sense

noisesmith18:10:43

@borkdude but as mentioned, the issue is exposing the db to both start and stop methods, since records don't close over construction scope

borkdude18:10:27

that’s fine I guess, the component itself can do whatever you want?

borkdude18:10:43

ah, but having it as a record field exposes it, right?

isak18:10:50

right, that is a downside

noisesmith18:10:00

right, records don't do closure or private fields

borkdude18:10:21

What I do in that case: create some functions in the same namespace which is the API of dealing with that component, and don’t use the component directly

mpenet18:10:56

Can't you just use reify and reify your proto + component lifecycle. Then expose whatever you want from your proto fns

noisesmith18:10:08

you would also need to implement hash-map

borkdude18:10:19

Clojure isn’t too obsessed over making things private, it’s usually by convention anyway

☝️ 4
borkdude18:10:06

E.g. I’m doing this:

(defcomponent SSE
  "Server sent events component"
  [channels &]
  (new [cfg] {})
  (start [component]
         (let [channels (atom {})
               sse {:channels channels}]
           (info "Starting SSE component")
           sse))
  (stop [component]
        (info "Stopping SSE component")
        (doseq [c (vals @channels)]
          (a/close! c))
        nil))

(defn send-message-to-user
  [sse user-id message]
  (let [c (channel-for-user sse user-id)]
    (a/put! c message)))

borkdude18:10:26

Nobody is expected to use the channels directly from this component, but to go through send-message-to-user only

noisesmith18:10:39

which is fine when data is immutable, but for necessarily mutable state you end up with something that's either overly complex or exposing things others shouldn't be touching and relying on convention

👍 4
borkdude18:10:59

(this component is defined using a macro, but you get the gist of it)

isak18:10:16

Here was what I was hoping for in integrant:

(defmethod ig/init-key ::service [_ {:keys [db-pool tmp-dir]}]
  (ig/map->State
    :private-state {:exec-service    nil
                    :datascript-conn nil}
    :public-state
    (fn [private-state]
      @(:datascript-conn private-state)))

  (defmethod ig/halt-key! ::service [_ private-state]
    (.stop (:exec-service private-state))
    private-state)

borkdude18:10:33

@isak if you really want private, make a Java class 😉

isak18:10:08

@borkdude yea i'm not to worried, but good to hear how other people are thinking about this

borkdude18:10:14

is this something that integrant supports, private-ish?

isak18:10:35

no, i just made it up as a possible API, for when you want your private state to be different from public

borkdude18:10:10

right. you could also use an underscored field or another convention to signal “internal” or “implementation”, like _datascript-conn

isak18:10:37

good idea

borkdude18:10:31

@isak just out of curiosity, is this a server side or client side app?

noisesmith18:10:58

use characters in the name that can't be typed on a normal keyboard? troll

simple_smile 4
borkdude18:10:17

@isak do you use DataScript with some kind of persistence?

borkdude18:10:31

haha, or use whitespace only keywords

borkdude18:10:44

(keyword " ")

isak18:10:55

@borkdude yea, i'm serializing it with fressian.

hiredman18:10:32

(datascript's btree sorted set is really great and can be used outside of the datomic like stuff)

borkdude18:10:55

{(keyword "   ") :my-secret :public :touch-me}
;;=> {:    :my-secret, :public :touch-me}
😛

isak18:10:19

if anyone is curious, for about 3 million datoms, it takes about 3 seconds to read it from a file via fressian, but about 30 seconds to initialize it from scratch with the data

borkdude18:10:25

@isak are you familiar with #datahike as well? it supports durability out of the box

borkdude18:10:57

(I don’t have any production experience with either, I do with Datomic)

isak18:10:58

@borkdude i just learned about it the other day. Looks interesting, but I haven't tried it yet.

hiredman18:10:40

I've been toying with a little library that wraps datascript's btree as an aorset delta crdt, with the idea that at some point I might use it in a chat server at work for presence information, it seems super promising

👏 4
borkdude18:10:01

I heard that Tonsky would be working on durability for datascript in his Clojurists Together funding

🎉 4
borkdude18:10:38

@hiredman cool idea. #datahike is trying to get funding for making it suitable for p2p, same kind of idea I guess?

borkdude19:10:29

exciting times ahead I guess when all of this stuff comes together

hiredman19:10:21

maybe? my little library is only about 200 lines (on top of datascript and minus and network communication), but it really is only the sorted set aspect. A lot of the datomic like bits are very hard to make work in a sane way on top of a crdt

hiredman19:10:10

there are things like, if you have a unique attribute, and concurrent modifications each add conflicting things, how do you merge them together? so I just left all that stuff out

borkdude19:10:48

@isak found the ultimate way to obfuscate your private state

{(keyword "x\u0008") :secret (keyword "y\u0008") :anothersecret}

simple_smile 4
💯 4
jeff.terrell19:10:10

Interesting: a CLI repl just shows :, but a cider repl shows :x^H. simple_smile

borkdude19:10:55

it’s not serious though.. 😛. my boot REPL shows it “correctly”

dpsutton19:10:03

what is correct?

borkdude19:10:18

{: :secret, : :anothersecret}

borkdude19:10:15

oh, {(keyword "x\u0008\u0008") :secret (keyword "y\u0008\u0008") :anothersecret} even doesn’t show the colon, hehe.

borkdude19:10:29

enough played… goodnight folks

👋 8
wilkerlucio20:10:24

hello people, is there someone here using pure deps.edn for deploying to clojars? if so, what are you using? I'm trying to figure if I can get rid of lein because currently I only use it to deploy to clojars, you know a solution for it?

weavejester23:10:51

Though in that guide it just swaps lein for maven, which might not be an improvement!

☝️ 4
dpsutton23:10:51

anyone ever register a multimethod specifically for testing? should we feel bad?

noisesmith23:10:36

I've added function args or defined new protocols just to make something testable, so

dpsutton23:10:37

(defn fake-effect-handler [f]
  (defmethod graph-proto/run-effect :rerender-components
    [gm effect]
    (swap! results conj effect))
  (f) ;; run tests
  (remove-method graph-proto/run-effect :rerender-components))

dpsutton23:10:47

yeah but this ...

noisesmith23:10:17

why remove the method afterward? if you care about the behavior for that dispatch outside the test, wouldn't you want to restore the previous method (if any)?