Fork me on GitHub
#clojure
<
2018-09-13
>
caleb.macdonaldblack06:09:41

I have some questions about coupling, abstractions & dependencies. It seems to make sense to me to make all my dependencies in a clojure application abstractions. Dependencies such as database access layers, logging, caching, remote calls/apis. For a database, I’d create a protocol to perform the action of persisting a resource. My implementation may be an SQL statement run with postgres or something completely different. But the code calling this function on the persist-resource protocol does not know that. The implementation for this protocol would be passed down to the code calling it. I’d build my whole app this way. For obvious reasons I would not make types for data. Only for abstractions for functionality. The benefits I hope to gain are decoupled, testable, layered/segmented, simpler code. I’m finding limited examples of this. I mainly find examples with concrete functions used. Does this sound like a good idea. Am I on the right track here? What do other people do in their apps?

danny16:09:27

i’ve found myself doing and wondering the same thing. i suppose the alternative to using protocols for the dynamic dispatch would be use plain old functions, e.g. make the “put-in-database” action a function that gets “injected” rather than something fulfilling a protocol (via reify or otherwise). then the responsibility to be compatible with, say, your database layer falls to the data rather than the insertion protocol layer and in that case maybe you have some sort of converter (maybe schema-backed) of one data representation to a representation the insertion action can use.

caleb.macdonaldblack22:09:12

Thanks. I ended up using protocols with component and I was quite happy with how modular and decoupled it ended up being.

dominicm06:09:47

@caleb.macdonaldblack this sounds a bit like hexagonal architecture?

caleb.macdonaldblack06:09:18

I’m not familiar with that. I’ll do a quick search

caleb.macdonaldblack06:09:44

@dominicm That’s an interesting term that almost describes what I’m on about. I don’t know that I’d go as far as naming things ports an adapters though. I believe what I’m talking about lies more with separation of concerns, dependency inversion principle and dependency injection

caleb.macdonaldblack06:09:27

It just seems like an all round good idea to build software this way and I’m curious to know if many other people are doing it. An example of not using this approach would be having an API handler for a route call a database query function with a query and pass in a connection. The http request handler would be coupled to the database. Changing the database would require finding and modifying all API handlers.

valtteri07:09:15

@caleb.macdonaldblack I think the most popular approach is to use something like stuartsierra/component (or its relatives) for DI. You basically define dependencies as “components” which you pass to other components when needed. And you can have different set of components for different needs (testing, prod or whatever).

caleb.macdonaldblack07:09:19

@valtteri The solution I’m imagining involves a combination of Stuart Sierra’s component library and protocols. The component DI library seems more concerned about the lifecycle of dependancies and their state than abstracting implementation. Life cycle and state management for dependencies is also something that’s required for medium to large scale applications IMO

valtteri07:09:20

Ok I think I get it. My gut feeling is that getting those abstractions ‘right’ may be difficult. Personally I’ve encountered myself creating abstractions that are a) too vague b) too elaborate c) unnecessary.

valtteri07:09:00

All though sometimes (rarely) I get them right. 😄

valtteri07:09:12

It of course depends on many things. With Java you need (or at least used to need) such “extensions points” all over the code because that’s pretty much the only (sensible) way to provide customizations. In Clojure we have more flexible ways such as first class functions.

billyr08:09:18

Got this problem where taking values from channels stops working for whatever reason. I'm assuming some state is causing that but I can't quite pin it down. All the channels stop working. Puts work but the takes never fire. And I mean absolutely all channels, including new ones, I can go ahead and try to run the most minimal sample and it won't work. As if I'm creating too many and some limit is causing them to stop working?

billyr11:09:10

I was doing the blocking alts!! instead of alts! inside a go block. So after enough blocks piled up all async-dispatch threads were blocked hence nothing would work. At least I got to learn a bit about async internals.

octahedrion08:09:02

why does the reader macro ' have a corresponding special form quote , but

` doesn't ?

awb9909:09:37

Is it possible to create a key from a number?

awb9909:09:11

I calculate different kind of averages on my data and add that to the list elements. So my function average can create :average-30 (for 30 days) or :average-90 (for 90 days). I habent found anywhere how I can create the keys :average-30 by passing it the number 30.

billyr09:09:20

(keyword (str "average-" 30)) ?

awb9909:09:06

@bill_rubin thanks so much! I really googled for hours and could find an answer.

sreenath.n11:09:22

I have Java 10.0.2 and Clojure 1.10.0-alpha7. (clojure.java.classpath/classpath) returns empty list. Any idea how to resolve this?

Alex Miller (Clojure team)12:09:06

which I believe was fixed in 0.3.0 of java.classpath

Alex Miller (Clojure team)12:09:17

what version are you using?

sreenath.n13:09:03

I was trying to setup a project using chestnut with garden. It uses 0.2.3 of the java.classpath

sreenath.n13:09:00

let me fork the garden-watcher repo and upgrade java.classpath. I’ll let you know if that resolves the issue

sreenath.n13:09:34

Thanks @alexmiller. Updating java.classpath fixed the issue 🙂

h.elmougy13:09:19

why (apply map + []) returns a function. why not the default value of apply (+)

mpenet13:09:52

it returns the xform for map +, equivalent to (map +), since you pass empty arguments

mpenet13:09:29

you prolly meant to get (apply map + [[]]), or maybe you're looking for partial, not sure

h.elmougy13:09:17

Yes exactly apply over nested structure (apply map + [[1 2] [3 4]])

h.elmougy13:09:35

alright, got it thanks

jeff.terrell14:09:26

Why are things like if and do special forms, when they could be implemented as macros? Are they faster as special forms? If so, why? https://clojure.org/reference/special_forms

greywolve14:09:27

Great question, would love to know the answer

rmprescott01:09:15

>Special forms have evaluation rules that differ from standard Clojure evaluation rules and are understood directly by the Clojure compiler. https://clojure.org/reference/special_forms (if is a pretty common special form in LISPS so that the alternative is not evaluated if not needed. I don't know about do

rmprescott01:09:43

Probably just a name for the S-exp of a bunch of other expressions evaluating to the last. Most forms have one of those implicitly. Peeking at the parser it calls it a "BodyExpr" and uses it in all the usual suspects: https://github.com/clojure/clojure/blob/28efe345d5e995dc152a0286fb0be81443a0d9ac/src/jvm/clojure/lang/Compiler.java

jeff.terrell12:09:41

@U09FPJ924 - I'm aware that the evaluation is different. Macros can control evaluation and would be a sufficiently powerful tool for implementing e.g. if, so my question was why make them more special than they perhaps need to be, if macros are sufficient.

rmprescott13:09:04

This is discussed better in the main thread

👍 4
Alex Miller (Clojure team)14:09:52

prob b/c you need them too early for bootstrapping and they get translated directly to JVM bytecode

jeff.terrell14:09:41

Ah, OK. That makes sense. Thanks, Alex.

Alex Miller (Clojure team)14:09:57

do at the top level is actually special-cased in the compiler too

mfikes14:09:16

If you implemented them as macros, what would they expand to?

leonoel15:09:19

(defmacro do [& body]
  `(let [] ~@body))
(defmacro if [test then else]
  `(case ~test
     (nil false) ~else
     ~then))

jsa-aerial14:09:31

Then you need case to be a special form. This issue typically boils down to what you want at the 'bottom'.

leonoel15:09:35

case is already a special form, and it is more general than if, hence the question : do we really need both ?

jsa-aerial15:09:07

nope - it is a macro case* is special. I see now this has been discussed to death...

john14:09:12

Some lower level conditional apply thing, I suppose. Could probably do it with or

john14:09:42

But yeah, you'd need something pre-defmacro definition, right?

☝️ 4
mfikes14:09:51

Yeah, I can imagine (at least in the ClojureScript case) macros that expand to JavaScript.

rutledgepaulv14:09:04

user=> (macroexpand '(or 1 2 3))
(let* [or__4469__auto__ 1] (if or__4469__auto__ or__4469__auto__ (clojure.core/or 2 3)))

rutledgepaulv14:09:10

or is implemented with if

mfikes14:09:05

Here is an example in ClojureScript

cljs.user=> (macroexpand '(or true false))
(js* "((~{}) || (~{}))" true false)

mfikes14:09:21

So, you at least need a special form to emit bytecode or JavaScript

jaawerth14:09:45

That or you'd have to do a lookup of functions on a data structure - 2-element vector, for example

Alex Miller (Clojure team)14:09:05

re top-level do, relevant search is “Gilardi scenario” https://technomancy.us/143

👍 8
jaawerth14:09:28

which is kinda the high-level equivalent of how you implement it on the low level via goto or MOV or whatever

bronsa14:09:07

how would you implement do as a macro tho

bronsa14:09:37

all the special forms that take multiple statements end up doing it via an implicit do

jaawerth14:09:46

...very carefully 😄

bronsa14:09:37

you always need to bottom out somewhere, you can't just pile macros on top of nothing

bronsa14:09:08

that's what the special forms are for, unless you take the cljs route and have the uber-special form that lets you directly talk to the underlying VM

jaawerth14:09:15

For sure. Clojure is also pretty good, even beyond that, about compromising between ideological purity (like in this example, the "everything stems from these key concepts/forms" aspect of Lisp) and practical application, which I think includes things like this. Or, for example, the decision not to support custom reader macros

mfikes14:09:23

Even in ClojureScript, if and do are special forms. The js* special form is used as an optimization in places: Normally the or macro expands to if constructs, but if type inference allows, it can expand directly to something that is more optimal by reaching for js*.

hiredman15:09:15

macros expanding to js is so terrible

8
danielglauser15:09:24

How come? Are there examples that you are thinking of?

hiredman15:09:42

yes, the core.async jira has a bunch of issues related to or and and and come down to the fact that in cljs those macros can expand to blobs of javascript that are opaque to the clojure code analysis that the go macro does

😱 4
hiredman15:09:54

and neither of those is the issue that first brought it to my attention, but I can't seem to find that one now

danielglauser15:09:25

That does look like an unfortunate situation. 😞

richiardiandrea15:09:26

Wow that is new and scary to me 😱

john15:09:16

Yeah, that's a salient point

leonoel15:09:19

(defmacro do [& body]
  `(let [] ~@body))
(defmacro if [test then else]
  `(case ~test
     (nil false) ~else
     ~then))

bronsa16:09:00

@leonoel let internally uses do

leonoel16:09:44

this is compiler implementation detail

bronsa16:09:13

so are all the special forms

bronsa16:09:47

why would you want to have a special form complect lexical binding and statement execution when you can separate the two in two distinct special forms

leonoel16:09:58

aren't special forms part of the language specification ?

bronsa16:09:04

not really, it's just a coincidence that some special forms in clojure are also used directly (`if`/`do` etc)

leonoel16:09:53

ok but it's still valuable to have as few special forms as possible, when you need to perform deep code analysis

bronsa16:09:15

fewer special forms can make analysis much harder

bronsa16:09:08

imagine you didn't have if but you implemented it with combinators a-la pure lambda calculus

bronsa16:09:34

when everything is a function call or an invocation, do you really think it's easier to do branch analysis than tracking two branches of an if?

bronsa16:09:16

and using this concrete example: if you had if implemented in terms of case, any branch analysis on if would be massively more complex since case can do "more" in one special-form than if

leonoel16:09:25

I would be curious to see an example where having the separate if actually makes things simpler, since you need to handle the more complex case anyways

bronsa17:09:56

for one, implementing if in terms of case would make the compiler significantly more complex than it is now in order to apply all the intrinsic and peephole optimsations it

bronsa17:09:33

but ignoring that, think of other consumers for the compiler analysis: linters, deep walking xform macros etc

bronsa17:09:48

having just case would mean that every time a consumer needs to know whether that case expression is infact an if that got compiled down to case, it would need to do this (admittedly trivial in this particular example of if/`case`) analysis to distinguish the two constructs

bronsa17:09:26

IOW the fewer special forms you have, the less information you retain about the original intent

bronsa17:09:48

and this issue, amplified, can be seen in the clojurescript compiler: by bottoming out certain constructs to an opaque js* special form, you do indeed reduce the surface area of the compiler, but then it makes libraries like eastwood/core.async/core.typed significantly harder if not not completely possible

bronsa17:09:55

in fact the cljs compiler is going in the opposite direction (stuff that ambrose is doing based off my ast format for tools.analyzer): pushing away from desugaring into js* and towards more semantically significant AST nodes

bronsa17:09:31

it is definitely not true that more special forms == better but the inverse is not true either

bronsa17:09:43

balancing number of special cases vs expressivity is not easy and there's definitely benefits and drawbacks in both cases (loss of semantic expressivity vs increased surface area/complexity)

bronsa17:09:44

but thinking that the fewer special forms a language has the "better"/"more elegant" it is is just an ideological position, not neecssarily a pragmatic one (I share it BTW! I wish we could all live happily with as few special constructs as possible, but sometimes compromising is for the best :) )

leonoel17:09:13

We're on the same line. I'm not advocating for a overly pure model a la lambda calculus, I understand the host platform requires pragmatic choices, and I definitely think the js* trick should not leak outside of the compiler.

mfikes18:09:15

Right, js* is an internal implementation detail

bronsa16:09:39

if in terms of case would work but it would be a performance hit, case itself is just a performance optimisation over if

leonoel16:09:56

I don't get how this is an argument for having if as a special form. The trivial case optimisation can (and should, IMO) be handled by the compiler.

bronsa16:09:24

same reason as from the other thread: if is a more "primitive" special form than case/`cond`, so it makes sense to have it at the bottom

bronsa16:09:38

case OTOH exists just as a performance optimization and you could do without it

bronsa16:09:06

but I mean, some lisps implement if in terms of cond, some vice versa, it doesn't really make any difference

bronsa16:09:31

clojure just decided to implement the more complex versions as macros over the simples special forms, which IMO makes a lot of sense and kept the compiler relatively simple

leonoel16:09:26

I understand how performance justifies the case special form, I just don't get what is lost implementing if with case

leonoel16:09:31

let's switch to the other thread

bronsa16:09:06

so no matter how you rejiggle it, you still need a special form to handle conditionals and one to handle statements, if and do being the simplest ones you can have

☝️ 4
💯 4
martinklepsch16:09:20

I'm looking into some issue where Clojure can't seem to find a class (type) from Midje despite that type clearly being present in the jar: https://gist.githubusercontent.com/martinklepsch/658d29331e4ec098c4de82d704bff3b0/raw/74b753f84da73d948765b8e9fef1f52e667d3f99/gistfile1.txt

martinklepsch16:09:41

the gist is running clj with -verbose:class

bronsa16:09:38

oh god midje

☝️ 4
bronsa16:09:21

I had to add a nasty hack into tools.analyzer.jvm just to support the insane side-effecting stuff midje does at macroexpansion time

martinklepsch16:09:41

the weird thing is that a library is using that type (using the same import) is working perfectly fine

hiredman16:09:45

assuming that the type is defined in a clojure file, are you doing something to ensure that clojure file is loaded before trying to use the type defined in it?

bronsa16:09:51

they're probably going through different classloaders

martinklepsch16:09:35

@hiredman I'm using (import (midje.data.metaconstant Metaconstant))

martinklepsch16:09:57

ugh, I found... something. Requiring midje.sweet upfront fixes the issue

hiredman16:09:17

that is defined in midje.data.metaconstant

hiredman16:09:35

so you must load midje.data.metaconstant before you can use it

martinklepsch16:09:04

@hiredman I'm not sure I understand — what would be the proper way to load it before using it? I thought import is doing that or does it simply assume there already is a java class of that name?

hiredman16:09:36

import definitely does not do that

hiredman16:09:21

you load namespaces all the time right? there are lots of ways to do that, by far the most common is using require

hiredman16:09:29

if you define a type in a namespace, you must make sure that namespace is loaded before you can use(safely) that type, just like if you define a function in a namespace, you must make sure that namespace is loaded before you can use(safely) that function

martinklepsch16:09:59

thanks a lot for that explanation @hiredman

awb9917:09:10

@hiredman When I use the repl, and I require other namespaces from my own project, am I then guaranteed that all functions that I require are available? Or in other words does the repl parse the compelte file, so that I as user do not need to manually execute all functions in the repl, so that they are available ?

awb9917:09:47

ahh thanks.

hiredman17:09:57

the "project" boundary doesn't exist in clojure, that is something built on top of clojure in different tooling

hiredman17:09:24

at the level of just the clojure language it is just a bunch of namespaces

awb9917:09:11

that is very different to other languages indeed.

awb9917:09:27

I am fairly new to clojure, and I still am a little confused as to how the binding works.

awb9917:09:55

I try to develop all in the repl, and then once I have a few functions that work that belong together, I put them into their own namespace.

awb9917:09:07

so this way I can continue to experiment in another file,

awb9917:09:13

and just require the functions once.

awb9917:09:50

what confuses me, is when I do change several functions in several namespaces,

awb9917:09:02

then somehow some of the functions seem to stay at the old version.

awb9917:09:13

I assume as soon as I do the require,

awb9917:09:16

this freeses the functions?

awb9917:09:30

or if I then go in the repl to the other namespace, and evaluate one function,

awb9917:09:42

does it then chagne this functions even though I do not re-require them?

awb9917:09:59

the other thing I have is when I forgot to save a file,

awb9917:09:06

which when I work with proto repl does not matter,

awb9917:09:15

as only the text in the editor gets evalulate,d

awb9917:09:24

but when I require, it will take the saved file I guess.

manutter5117:09:21

You may be missing the :reload key — (require :reload '[my.updated.ns as mine])

awb9917:09:41

so I put the :reload for each reference?

manutter5117:09:00

No, you don’t put it in your source, you just use it in the repl

manutter5117:09:22

(require :reload '[test.seasonal.trading :as nst])

awb9917:09:46

I have setup proto repl

awb9917:09:56

and I work in different source files,

awb9917:09:02

and then with CTRL , B

awb9917:09:11

I send the block to the repl.

awb9917:09:15

this is how I do it currently.

awb9917:09:34

then once it works, I already have most in the right source file.

manutter5117:09:01

I’m not familiar with proto repl, sounds like CTRL-B just loads a single function at a time?

awb9917:09:40

yes only one function.

manutter5117:09:44

I think that “should” work, but I don’t know if it’s loading your functions into the right namespace or if it’s putting everything into the user namespace

awb9917:09:33

most of the time the namespace works,

awb9917:09:43

somethimes it puts stuff in user namespace.

awb9917:09:07

but I guess this happens if I forget to evaluate the ns before I work in a file.

awb9917:09:20

is there any other benefit of using only the repl?

manutter5117:09:40

It’s probably better to work in an file and send it to the repl

awb9917:09:42

or in other words, am I doing things not the losure way?

manutter5117:09:08

but if the repl starts behaving oddly, I think I’d try the (require :reload ...) trick just to sync everything back up.

awb9917:09:11

I am still very slow in writng clojure code, sometimes it takes me 4 hours to write 5 lines of code.

awb9917:09:22

ok I will try that.. thanks manutter!

awb9917:09:26

the ' means delayed execution?

manutter5117:09:00

well “delayed evaluation” technically

awb9917:09:42

so basically the require function loads the function tree into test.seasonal.trading,

awb9917:09:50

and if it is evaluated immediately,

awb9917:09:59

then the whole :reload would not work.

manutter5117:09:18

I’m not sure the technical details, but if you leave out the ', I know require gets upset.

noisesmith17:09:38

' means "no evaluation"

noisesmith17:09:53

it means that 'foo is just the symbol foo, and isn't looked up

☝️ 4
👍 4
noisesmith17:09:30

one confusing thing is that most macros involve some sort of implicit quoting of args - the pattern of quoting would be more noticible if that weren't the case

noisesmith17:09:06

you need to quote a symbol if it refers to something that hasn't been loaded yet (or a name for something that doesn't directly resolve, like a namespace)

roti19:09:07

I know I did this before but I can't remember how, so here it is: how can I deploy a jar to clojars?

seancorfield19:09:22

lein deploy or boot deploy

roti19:09:03

yeah, but I have a jar file, not a project

Garrett Hopper19:09:03

Is there a better way of utilizing these variadic map functions when I have an actual map?

(defn example [& {:as options}])
(apply example (apply interleave ((juxt keys vals) {:alpha 1})))

pbrown20:09:56

@U5JUDH2UE I use (apply example (mapcat identity {:alpha 1}))

Garrett Hopper20:09:10

Nice, thanks! 🙂

seancorfield19:09:31

@roti Then you probably shouldn't be deploying just a "random" JAR to Clojars...

dpsutton19:09:59

anyone have a good mnemonic to remember how to differentiate constantly repeat repeatedly?

dpsutton19:09:19

i always jumble them in my head and have to repl it

andy.fingerhut19:09:52

d/dt constantly = 0 (sorry, bad math joke)

dpsutton20:09:10

works for me 🙂

jaide20:09:35

@dpsutton I just try to keep (= (repeatedly 1 (constantly true)) '(true)) in my head

noisesmith20:09:50

(= (repeatedly 1 (constantly true)) (repeat 1 true) [true]) - proposed alternate

jaide20:09:43

(when (repeatedly 5 (constantly :beer)) :drunk)

🍻 12
andy.fingerhut21:09:41

(when (repeatedly 5 (constantly :beer)) (repeat 3 '[I am drunk.]))

😀 4