Fork me on GitHub
#clojure
<
2020-05-25
>
valerauko04:05:22

is there any way to name protocol functions? app.core$eval831$fn__832$G__822__841 is not very helpful

seancorfield04:05:13

Are you sure that's a protocol function @vale?

seancorfield04:05:01

That looks more like something from an anonymous function...

seancorfield04:05:20

Oh wow, yeah, just repro'd.

valerauko04:05:23

it's from the error message from calling a protocol function with bad arity

seancorfield04:05:41

Hah, I've never tried to just look at a bare protocol function type before...

valerauko04:05:14

i've been staring at flame charts a lot recently and this can make pinpointing problems really hard

valerauko04:05:35

luckily i only have a few protocols only called from 1-2 points so i can do trial and error

valerauko04:05:10

it'd be great if i could name them somehow even if it had to be some manual meta map or something

seancorfield04:05:37

What version of Clojure are you using? I get a much more intelligible error for an arity error in the call

seancorfield04:05:48

I get something like No single method: bar of interface: user.Foo found for function: bar of protocol: Foo

seancorfield04:05:18

(for a call of (bar obj 1 2 3) when it should just be (bar obj)

seancorfield04:05:48

user=> (defprotocol Foo (bar [this]))
Foo
user=> (defrecord Fubar [] Foo (bar [this] (println "fubar!")))
user.Fubar
user=> (bar (->Fubar))
fubar!
nil
user=> (bar (->Fubar) 1 2 3)
Syntax error (IllegalArgumentException) compiling bar at (REPL:1:1).
No single method: bar of interface: user.Foo found for function: bar of protocol: Foo
user=>
That's on 1.10.1

seancorfield04:05:37

user=> (extend-protocol Foo String (bar [this] (println "string!")))
nil
user=> (bar "hi!")
string!
nil
user=> (bar "hi!" 1 2 3)
Syntax error (IllegalArgumentException) compiling bar at (REPL:1:1).
No single method: bar of interface: user.Foo found for function: bar of protocol: Foo
user=>

valerauko04:05:41

it shows up in error messages only if the implementation is wrong (for example arity missing)

valerauko04:05:52

but it's always like that in the stacktraces

slipset07:05:59

This was kind'a surprising to me:

user> (def bla (filter #(throw (Exception. %)) ["lol" "foo"]))
;; => #'user/bla
user> (prn bla)
Execution error at sun.reflect.NativeConstructorAccessorImpl/newInstance0 (NativeConstructorAccessorImpl.java:-2).
lol
(
user> (prn bla)
()
;; => nil
user> 
I think I would have expected the second prn to print nil (or something else than an empty list (which in someways is nil, so whatever) 🙃

Ben Sless08:05:47

Laziness. Since the first element wasn't realized yet, the exception wasn't thrown.

slipset08:05:47

The problem isn't that (def bla ...) doesn't throw, the problem is that the second prn doesn't do what I expected it to do.

Ben Sless08:05:05

check the type of blah. I think it's a LazySeq with 0 elements, which checks out, since filter returns a LazySeq and when you tried to materialize the first element (via prn) it threw, thus remaining with 0 elements

slipset08:05:01

I guess I would have expected bla to continue throwing...

p-himik08:05:25

Yeah, same. Just by looking at the LazySeq code I don't see why it would not do that.

p-himik08:05:07

There's a strange ^{:once true} metadata on the fn - I have no idea what it does.

Ben Sless08:05:42

The answer isn't in LazySeq's code, but in filter's

Ben Sless08:05:51

(defn filter
  "Returns a lazy sequence of the items in coll for which
  (pred item) returns logical true. pred must be free of side-effects.
  Returns a transducer when no collection is provided."
  {:added "1.0"
   :static true}
  ([pred coll]
   (lazy-seq
    (when-let [s (seq coll)]
      (let [f (first s) r (rest s)]
          (if (pred f)
            (cons f (filter pred r))
            (filter pred r)))))))

Ben Sless08:05:59

simply, it throws before it can recur, so it does not recur

p-himik08:05:08

Interesting - my filter is quite a bit more complex. But works the same way. > The answer isn't in LazySeq's code, but in filter's I disagree. If it was that simple, you'd see the exception being thrown each time you call bla.

Ben Sless08:05:53

I trimmed down the source code

Ben Sless08:05:33

blah isn't a function, it's a lazy sequence, once it's realized it isn't realized again (in that sense LazySeq's implementation does matter)

p-himik08:05:05

What does "realized" mean in the context where the body throws?

Ben Sless08:05:37

final synchronized Object sval(){
	if(fn != null)
		{
                sv = fn.invoke();
                fn = null;
		}
	if(sv != null)
		return sv;
	return s;
}

final synchronized public ISeq seq(){
	sval();
	if(sv != null)
		{
		Object ls = sv;
		sv = null;
		while(ls instanceof LazySeq)
			{
			ls = ((LazySeq)ls).sval();
			}
		s = RT.seq(ls);
		}
	return s;
}

Ben Sless08:05:49

it probably means that sval throws

p-himik08:05:11

"Realized" sounds like it has a state. "sval throws" doesn't sound like a state. I'm trying to play with it with a debugger.

Ben Sless08:05:55

Lazy seqs do have state

p-himik08:05:43

I mean that you're not being very clear. I know that they have state.

p-himik08:05:51

So something strange is happening. The first call to fn.invoke() when you eval bla the first time throws, just as expected. But the second call just... returns nil. No idea why.

Ben Sless08:05:21

I think I see your point, you're saying that because it throws it doesn't get to the line where fn is set to null?

p-himik09:05:50

Ah, got it. Because of :once, the vector gets garbage collected, so the body of (when-let [s (seq coll)] ...) is not executed.

p-himik09:05:47

Oh, wait, disregard that - I was looking at wrong things.

p-himik09:05:03

Oh maybe I was half right, given that "closed-over references should be cleared" (from the link above). this.coll of the fn definitely becomes null, so maybe that's that. But I don't know how references really work in Clojure, so that may be something else entirely.

slipset09:05:12

By removing the :once thingy, it behaves as expected.

slipset09:05:24

ie it throws all the time.

slipset09:05:24

One could of course argue that throwing an exception makes the anon-fn impure, which would make the observed original behaviour ok.

rmxm12:05:27

Just a general question, I am kinda new (I did clojure for fun, now I'm dealing with real codebase). How prevalent is the use of atoms in clojure, my codebase has so many atoms which are used more or less like global vars and the whole thing gives my headache so I think this is not the way to roll(sadly its over 50k loc, so I have to find a strategy to be effective with changes/refactors)? A problem I run into now there is this conf namespace that holds all the configuration in atoms, then later every function uses this atoms in its inner logic, so if I want to say push an item to queue I can define the item, but cannot reliably deliver different queue details, since the queue-push takes only item details and queue details are inferred internally in function from atom, if the client is not started it gets started etc. So returning to original is abuse of atoms as global-vars-look-alike common/desired?

dominicm12:05:26

Atoms should not be used like that. I've not heard of that in clojure. I see maybe one or two used in small areas of a codebase.

dominicm12:05:52

I would start adding an arity to functions which take an explicit value instead of the atom value. Then I'd work on slowly bubbling the conf namespace use as far outwards as possible. Once there you have minimized use of it, and you can more easily get rid of it. You have a long journey ahead.

rmxm12:05:11

the arity solution is something I was thinking, however I see how it helped them with development, since you do not have to pass around a lot of stuff and do bookkeeping, like with this queue example, you just simply have some config for the queue and the client will be created for you from config atom, then stored in client atom, and you just have to run one function in every other function to simply get it, instead of passing it around to every function

craftybones12:05:00

still far better to have a single atom and update parts of it using functions than have multiple atoms

craftybones12:05:21

Also, consider component. I discovered it a few days ago, and it certainly makes it easier to deal with stateful bits

Cameron13:05:50

I would argue having those explicit parameters is showing you all the systems a particular function is touching with its change, and is a case of positive pain; like the pain that protects you from keeping your hand on the hot stove, once your function starts requiring lots of parameters, it gets bulky and painful to use, but that may be a sign something should be changed. Imperative code doesn't tend to be without the injury inflicted, its just numb to it. Building up a program that way is like living a life on painkillers -- definitely a bit smoother at times, but unfortunately with the darker side of its tradeoffs as well.

💯 8
Cameron13:05:41

Don't program imperatively kids

rmxm13:05:04

Well this is beyond imperative/declarative/functional, its more about hiding certain important parts of the system. Cause next thing is that testing is suffering, because the client is implicitly created, rather than passed around, which makes it hard to mock the stateful parts. And you can do it in almost any language.

noisesmith16:05:54

I strongly disagree - declaritive literally means that there's nothing changing or changed by a form other than what you see in the text of the form and what it calls.

noisesmith16:05:21

if you are using atoms that aren't passed in as arguments and then returned to the caller on completion, it's not declarative

rmxm13:05:58

I can see why you would do it, I think they considered it less bloatey, to keep the client instance in the atom and refer to it when needed from within a function, as oposed to incrementing arity by the client and doing passing around. It's sort of like a Singleton I guess, at least pattern looks similar. Again the problem remains that you have 7 functions and just can toss data into them. So in the end, "interfaces" are cleaner, because there is only one way to instantiate client and only one client should be available. So I am still torn if that is okish code...

craftybones13:05:56

injection > single atom > multiple atoms

craftybones13:05:11

is that a fair hierarchy?

rmxm13:05:50

by injection you mean arity? or the implicit injection that is popular in java

craftybones14:05:03

injection like component

craftybones14:05:12

explicitly, each dependency to each function

Cameron13:05:39

Well, I'm referring specifically to the idea of using atoms over parameters, nothing more. That particular aspect is trading a pure function for an imperative effect

Cameron13:05:29

as for the Don't program imperatively kids , that was me trying / failing to make a drugs joke, since I compared it to basically doing heroin / painkillers and realized immediately the comparison was not perfect ahahaha

rmxm14:05:06

Dont worry I get it. Not recommending all drugs is an obvious joke 🙂 I was just claiming that it doesn't matter what "paradigm" you can do this type of stuff anywhere.

Cameron14:05:37

Ok good ahahah You can program imperatively in any language, that does not make it not imperative. Using atoms this way is expressing change imperatively, using side effects / statements instead of expressions, and this is the drawback of doing so in all languages -- your function draws in inputs implicitly to compute its change, through a side effect (reading from an atom in this case), and expresses that change by sending it out to another atom (or returning it -- imperative functions are seldom all imperative). And as I argued, writing it declaratively with a pure function makes all this explicit, and the pain it adds is positive

rmxm14:05:47

thanks for help, this were my intuitions as well, especially that I see a lot of stuff that is not stateless like s3 client, and they still shove it into atom... it just makes all the debugging very mystic... I have to track every atom, where it was set, how was it set etc. forget about repl, since the whole bootstraping is so complicated that you just cannot hookin and investigate values

Cameron14:05:25

Hehe I have to get off but I have more to write about it as well!

Daniel Tan16:05:47

reagent atom cursors could be interesting for managing atoms in applications

rmxm16:05:30

seems like a simplified lens, thanks

rmxm18:05:41

Ok, since today's morning advices were very helpful, I riddle you that(perhaps #beginners is better place? I dont know so shout if so): I think this is a common pattern for application that there is some configuration file and it gets loaded and some values get set. In the codebase I deal with people are kinda doing java abstractions(using packages/ns as quasi objects). They load config data into an atom (I guess that use case is legit?) then there is a bunch of packages that are built implicitly on this config atom. So there is package/ns that handles s3 and every function starts with a dance of creating another atom with s3 client(kind like a singleton pattern), that part seems crooked since s3 client is stateless so you can recreate him ad-hoc on every invokation and dont have to persist reference. But then I guess I have to append extra param to every function and so every package using/importing that s3 package would have to deal with passing the config and extracting (required part of) the config?

phronmophobic19:05:51

I think the key is that you can talk about S3 operations without any configuration (ie, separate what from how within your codebase). the only part of the system that needs the configuration is the part that performs the operations. So you can talk about gets, puts, deletes, etc and even compose them without requiring the caller to specify any configuration. you can then pass these sets of operations to a function that knows how to execute them. You can even have multiple implementations (eg. mocking) for dev/prod/test. example pseudo code:

(def operations (comp (s3/get "foo")
                      (s3/put "bar"))

(def test-result (mock-s3/execute! operations))
(def result (s3/execute! my-s3-config operations))

phronmophobic19:05:44

if you’re passing around config everywhere and each part of your program requires some sort of context, then you’re probably unnecessarily combining what and how

rmxm19:05:55

I see, looks legit, the problem is s3, is used sporadically "here and there". So its difficult since side effects are scattered, I agree that it would be best to build series of operations and just run them.

phronmophobic19:05:52

in the short term, if you’re truly only using one configuration within your application, then it’s okay, but not ideal, to have global state

phronmophobic19:05:47

for aws specifically, the java libraries (and some of the clojure libraries built on top of them) have decent solutions for this problem like using profiles. I’ve also used instance based authentication in the past with good results

rmxm20:05:36

thanks, no worries i roughly know how iam and sts operates - I was more suspicious of code patterns that I looked at

Cameron22:05:53

btw interestingly enough what @U7RJTCH6J wrote (at least at first, I've only read the first two posts so far) is a lot of what I was coming back to write anyways

Cameron22:05:39

because, going back to my imperative ramble, the difference in using imperative programming as your entire paradigm, and functional programming, is not the existence of imperative effects. All programs need to eventually cause an effect, they have to cease being a description, a seed, and begin living. The real difference is functional programming tends to put off committing to the how until its absolutely time to do so, at the edges of a program

didibus19:05:47

The use of atoms on its own could be considered an anti-pattern, especially for a backend service

rmxm19:05:43

there is over 140 in the entire codebase that is somewhat around 40k

didibus19:05:23

Kind of feel like too many for me. But like, nothing is black and white. So each use needs to be considered and might have good reasons.

didibus19:05:51

But in theory, an atom only stores values that changes over time. And 99% of the time they are used its to model that.

didibus19:05:17

Now the thing is, most modern backend does not maintain domain state in memory. It uses one or more database. Which is why you'll find plenty of code bases with zero atoms in them.

didibus19:05:58

And even if you were to keep domain state in memory. A common pattern in Clojure is to use an in-memory database. So you'd model your app the same as if it was using a real DB. Except the DB might just be a big map inside a single atom. And you'd query/write/uodate that single atom.

didibus19:05:26

And sometimes the in-memory DB can be something more complex, like Datascript or H2

didibus19:05:22

So with this common pattern, you rarely have many atoms.

didibus19:05:42

And definitely functions should be get as pure as possible, the bare minimum means they shouldn't be making us of global variables. If they need some resource it should be injected into them as an input.

kwladyka19:05:00

Is it possible to use with-redefs with Java? For example: (with-redefs [LocalDate/now #(LocalDate/of 2020 05 25)] something like that > Unable to resolve var: LocalDate/now in this context

kwladyka19:05:00

what is the best practice for such kind of tests?

kwladyka19:05:07

write clj wrapper?

👍 4
didibus19:05:24

I'd say that's the best is to wrap the interop in Clojure fns and vars and then you'd mock the wrapper instead.

didibus19:05:45

Otherwise, it is possible for you to use the same mocking libs as Java does, like Mockito

didibus19:05:15

That said, LocalDate/Now is a static, and statics are hard to mock in Java. So I really think you're better of wrapping it. But one could use PowerMockito, but it does all kind of bytecode wrangling I believe not sure it's REPL friendly

kwladyka19:05:45

well I am sure defn will be simpler then 😉

souenzzo19:05:20

@kwladyka in that particular case, I prefer to use (LocalDate/now clock) signature and pass the clock as argument, then you can write a MockClock for your tests (Clock is a abstract class, so you will use proxy for that)

kwladyka19:05:21

I wanted to use one of existed time library for clj, but I found it is too early

kwladyka19:05:48

I mean libraries are not enough mature

seancorfield19:05:23

@kwladyka Which time libraries do you think are not mature?

kwladyka19:05:37

tick has not updated documentation and in the newest versions things not work clj-time is deprecated, but mature for sure clojure.java-time is quite ok, but because of the not common way how it is coded editor can’t help me figure out which fn I can use when I am typing java-time/ and not common ns java-time luck of some fn which I need

kwladyka19:05:59

ok I used wrong word

kwladyka19:05:10

I didn’t want to be offensive here

kwladyka19:05:44

just I didn’t find this libs make my life easier. It is simpler for me to use Java interop, than this libraries

kwladyka19:05:53

but maybe I am not enough clever to use them

seancorfield20:05:14

Using Java interop is just fine -- Clojure is designed as a hosted language for good reasons.

seancorfield20:05:46

We use clojure.java-time heavily in production at work so it's definitely mature/solid, but it's a thin wrapper so it doesn't improve over bare interop except in two cases: 1) it tries to auto-convert between date/time types as needed which can help you avoid quite a bit of boilerplate when you need to go back and forth between different types and 2) complex date/time "arithmetic" looks much nicer that bare interop.

seancorfield20:05:45

Which editor are you using that won't give you code assist/completion with clojure.java-time? I get full auto-complete and inline docs for it in Atom/Chlorine (without even using Compliment)

Lennart Buit20:05:58

If its cursive: I have seen this, I think clojure.java-time uses potemkin’s import-vars, which cursives static analysis doesn’t understand.

seancorfield21:05:35

Ah, a time when dynamic code assist via a connected REPL does a better job than static analysis 🙂

kwladyka21:05:06

> Which editor Cursive

kwladyka21:05:45

oh I see you already discovered this 😉

kwladyka21:05:20

but yeah for me this is a good reason, but I regret

kwladyka21:05:43

this is first library when I have such an issue

kwladyka21:05:26

BTW What editor do you use? Is it Atom? Visual Studio Code?

kwladyka21:05:30

something else?

kwladyka21:05:43

emacs? 🙂

kwladyka21:05:56

I should start with last one

seancorfield22:05:09

I use Atom with Chlorine and Cognitect's REBL. I've done some YouTube videos of how I work with that -- and I have a atom-chlorine-setup GitHub repo if you're interested

seancorfield22:05:29

I used Emacs for years before I switched to Atom 🙂

kwladyka12:05:08

Can you give me a link to this video?

seancorfield16:05:17

I'm on my phone but I think this link should work https://youtube.com/watch?v=ZhzMoEz4j1k

👍 4
kwladyka10:05:49

rebl looks interesting, I didn’t know this tool

kwladyka10:05:08

How would you compare atom vs Visual Studio Code?

seancorfield18:05:10

@kwladyka They're fairly similar in many ways but VS Code is much harder to customize at startup because it lacks an "init" file that can contain code. Atom lets you write an init.coffee file (containing CoffeeScript) so you can extend the editor in interesting ways.

seancorfield18:05:08

Chlorine now has a config file, written in ClojureScript, that is processed via sci (Small Clojure Interpreter) so you can do a number of extensions to Chlorine by writing that chlorine-config.cljs file. There's a variant of Chlorine for VS Code, but I don't know if it has the cljs extension stuff added yet.

kwladyka19:05:18

What is the difference about this “init”? What I can’t do?

kwladyka19:05:11

I was trying VSC last year, but Cursive gives me much better experience. Why did you decide to use atom instead of VSC?

kwladyka19:05:08

I am using VSC as a second editor to Intellij which is lighter to other things, than Clojure

kwladyka19:05:41

Just thinking if make sense to try atom instead of VSC? Can it make any benefits for me?

seancorfield21:05:18

As I said above, Atom lets you write additional functionality in CoffeeScript that is loaded at startup -- adding new commands on the fly and extending the editor. You can't do that with VS Code. All of the REBL integration I use on a day-to-day basis was originally added that way.

seancorfield21:05:04

Recently, I moved all of my REBL integration into Chlorine's cljs config system -- which is still something you can't do with VS Code.

seancorfield21:05:07

If you've watched my videos, you'll see how much I use REBL. If Clover (the VS Code version of Chlorine) is ever able to add commands at startup with keys bound to those additional commands, I might switch to VS Code. But the author (of both Clover and Chlorine) says that it doesn't look possible with VS Code, only with Atom.

kwladyka21:05:35

hmm interesting, thanks

slipset19:05:59

Consider sending the value for now as a parameter. That way your function becomes purer and needs less mocking.

4
kwladyka19:05:58

yes, I will split the code. Just I don’t like when I have to make code more complex, because of tests but sometimes I need to 🙂

kwladyka19:05:27

anyway, simple thing. No reason to make it more hard, than it is 😉

slipset19:05:50

I’m sure someone would argue that pure functions are less complex than their impure counterparts.

kwladyka19:05:14

I am sure too 😉