Fork me on GitHub
#clojure
<
2017-08-18
>
dnolen00:08:30

Sorry on vacation. When I get back happy to chat but not really interested in private discussion about stuff like this. Against the point really. But feel free to reach out when I return.

dpsutton00:08:12

i think the best thing about clojurescript is that they actively use #cljs-dev and you can follow along with the enthusiasm that the developers have to their field. you can follow along with how issues are approached and you can see how priorities are set. i think this is drastically different from the way clj development happens

funkrider01:08:22

I'm a noob to clojure but am working through books and examples and I am trying out the whole proxy, gen-class, interface, defrecord, deftype, reify thing fight now and it appears to me that you can use proxy to override Java object methods OR implement interface methods on an anonymous object but NOT BOTH. Is this correct?

seancorfield02:08:44

@funkrider If you're new to Clojure, you'll probably find #beginners helpful for "getting started" with any particular feature. As for proxy, according to the docs, you can supply both a class name and interfaces -- but only one class (as many interfaces as you want).

seancorfield02:08:55

@funkrider As a silly example:

user=> (def p (proxy [java.io.InputStream clojure.lang.IFn] [] (read [] 123) (invoke [] "World!")))
#'user/p 
user=> (.read p)
123 
user=> (p)
"World!" 

seancorfield02:08:26

(the latter is just shorthand for (.invoke p))

funkrider03:08:48

thanks sean! I get your example but why wouldn't this work? (defprotocol prot (foo [this])) (def p (proxy [java.io.InputStream prot] [] (read [] 123) (foo [] "World!")))

seancorfield03:08:34

@funkrider My first reaction is "a protocol is not an interface"...

seancorfield03:08:56

(you get clojure.lang.Var cannot be cast to java.lang.Class, right?)

funkrider03:08:03

omg (palmface) interface, protocol not the same thing... Thanks sean šŸ˜‰ And yes thats the error I get

seancorfield03:08:44

You can reify protocols and interfaces (but not classes)

boot.user=> (def p (reify prot (foo [this] "Hello") clojure.lang.IFn (invoke [this] "World!")))
#'boot.user/p
boot.user=> (foo p)
"Hello"
boot.user=> (p)
"World!"

seancorfield03:08:40

BTW @funkrider you can use triple-backtick to format blocks of code (feel free to ask in #slack-help for hints & tips on using Slack).

funkrider04:08:24

OK so - is there a way to extend a java class, such that the class has some new private state and either option A) a new method on the class instance that has access to the state or option B) a new clojure protocol can be created and dispatched based on the extended class type that has access to this new private state within an instance of the class? Essentially I am working through a bunch of interview type questions in Clojure to compare use against say Java and the question goes like this: Create a new Stack class that implements push, pop and max methods and all operate in O(1) time. It is relatively easy in Java but in Clojure I am really having a hard time getting it done!

joshjones04:08:46

are you doing this in clojure just to see how to do it in clojure? iow, is there a reason for the java interop?

seancorfield06:08:48

The bottom line is really that "Java Class" is not the idiomatic way to do this in Clojure. So you wouldn't create a mutable object in the first place -- the question just doesn't make sense.

seancorfield06:08:36

But, if you're determined to create some sort of immutable data structure with push, pop, and max, that return a new version of the data structure for push and pop, then you're looking at a protocol for the functions and a record for the state...

seancorfield06:08:26

...sounds like you'd need a stack that contained pairs of max and item to retain O(1) performance?

seancorfield06:08:44

(defprotocol PMaxStack (push [this item]) (pop [this]) (max [this]))
(defrecord MaxStack [stack] PMaxStack (push [this item] (if stack (let [[m i] (first stack) m' (clojure.core/max m item)] (->MaxStack (cons [m' item] stack))) (->MaxStack [[item item]]))) (pop [this] (->MaxStack (rest stack))) (max [this] (ffirst stack)))
(untested, off the top of my head)

seancorfield06:08:41

Ah, you'd need to exclude push, pop, and max since they're in core -- or rename them.

seancorfield06:08:22

Also notice you can't have pop both return a new MaxStack and the first item so you'd need some sort of peek accessor for that...

seancorfield06:08:02

Or else say (second (first (:stack my-stack))) which is kind of opaque. Anyway, hope that helps @funkrider ?

seancorfield06:08:27

Here's a working example:

boot.user=> (defprotocol PMaxStack (spush [this item]) (spop [this]) (smax [this]) (speek [this]))
PMaxStack
boot.user=> (defrecord MaxStack [stack] PMaxStack (spush [this item] (if stack (let [[m i] (first stack) m' (clojure.core/max m item)] (->MaxStack (cons [m' item] stack))) (->MaxStack [[item item]]))) (spop [this] (->MaxStack (rest stack))) (smax [this] (ffirst stack)) (speek [this] (second (first stack))))
boot.user.MaxStack
boot.user=> (->MaxStack nil)
#boot.user.MaxStack{:stack nil}
boot.user=> (spush *1 4)
#boot.user.MaxStack{:stack [[4 4]]}
boot.user=> (spush *1 2)
#boot.user.MaxStack{:stack ([4 2] [4 4])}
boot.user=> (spush *1 8)
#boot.user.MaxStack{:stack ([8 8] [4 2] [4 4])}
boot.user=> (smax *1)
8
boot.user=> (spop *2)
#boot.user.MaxStack{:stack ([4 2] [4 4])}
boot.user=> (speek *1)
2
boot.user=> (spop *2)
#boot.user.MaxStack{:stack ([4 4])}
boot.user=> (smax *1)
4
boot.user=> (speek *2)
4

funkrider04:08:16

and gen-class doesn't seem like the "Clojure" way especially as it ends up being tied to a specific clojure version?

funkrider04:08:48

Thanks sean for the tip on formatting too!

joshjones04:08:50

for testing purposes I'd like to use with-redefs to replace a function with one suitable for testing. however, the project uses direct linking, so I will have to define the function that can be redef'd with ^:redef -- is the best way to get this directive out for production to elide-meta when i compile? or is there a much better way to do this?

joshjones04:08:02

preferably, a way to avoid direct linking when i'm running tests so i can avoid ^:redef altogether?

qqq05:08:53

I'm writing a webapp where both server and client uses clj/cljs. I'm currently serializing/unserializing using str and edn/read-string. Is transit a drop in for this? Due to websockets, it wants / givbes me strings. However, transit seems to want to control it's own streams.

noisesmith17:08:20

ā€œtransit seems to want to control its own streamsā€ is, to me, a really odd way to put it

noisesmith17:08:10

itā€™s simple to make a string out of a stream, the stream is the simpler and more general thing, which is why itā€™s the level transit operates on (so people donā€™t need to go creating strings that would not need to exist, out of an InputStream they already have)

mattly05:08:19

itā€™s not a drop-in, and youā€™re right, transit wants streams

mattly05:08:23

but itā€™s not a pain in the ass

mattly05:08:49

Iā€™ve written this like 3 times, now, I should probably put it on clojars: https://gist.github.com/mattly/217eb6f26cb5d728a6cc88b4d6b926bb

pjones10:08:28

new to clojure here trying to make heads and tails of documentation and tutorials etc and a bit lost. Hoping to test clojure for data science purposes. Using the following projects: boot, huri, clojure-csv, semantic-csv, neaderthal, kixi, bayadera in a gorilla repl with emacs (spacemacs) as my editor. 1) I want to know if anyone can point me to good resources to get more comfortable with clojure in data science? 2) bayadera is struggling to install: I use [uncomplicate/bayadera "0.1.0-SNAPSHOT"] ,but get could not find artifact error, can anyone give me some advice/help with this?

martinklepsch12:08:26

(->> [1 2 3 4 5 6 7 8 9 10]
     (map #(do (println %) %))
     (take-while #(> 3 %)))
whatā€™s the proper way to ensure that only 1 & 2 are printed?

jeff.terrell12:08:19

Swap the map and the take-while?

martinklepsch12:08:48

@jeff.terrell the result of the map is important for the take-while predicate

joshjones13:08:13

@martinklepsch the short answer is, don't do it. Mixing I/O with lazy sequences is not a good idea. What are you trying to actually do?

joshjones13:08:38

You can "de-chunk" the sequence, but that's all a workaround that usually indicates you don't need to have this in a lazy sequence to begin with

martinklepsch13:08:27

@rauh @joshjones thanks, interesting points. I want to execute a potentially side effecting function f for each element in a sequence and abort if (f x) returns an ā€œerrorā€ (error represented by a special keyword)

rauh13:08:06

@martinklepsch I'd use transducers instead of dechunking

joshjones13:08:11

reduce + reduced is a good choice

joshjones13:08:10

;; condition to terminate is when input is 3, in this example
(reduce (fn [_ x]
          (if (= 3 x)
           (reduced x)
           (do (println "value: " x) x)))
        (range 20))

martinklepsch13:08:35

@joshjones yeah, thats a good suggestion, thanks

seancorfield13:08:15

@pjones Welcome to Clojure! It lo ks like the author has not released that library yet -- I can't find it on http://clojars.org -- so you'd have to clone the repo and build it yourself. Since you're new, #beginners will be a helpful channel for you. Also #boot since you're using that.

chrisjd13:08:23

Thereā€™s also #data-science which might be a good channel to hang around in.

markbastian15:08:45

Is there a "best practice" for creating non-global core-async pipelines? As an example, suppose I have a trivial async computation pipeline that looks like this: `(def in-chan (chan)) (def out-chan (chan)) (pipeline 16 out-chan (map (juxt identity (partial * 2.0))) in-chan) (go-loop [] (prn (<! out-chan)) (recur)) (defn inject [query] (go (>! in-chan query)))`

markbastian15:08:22

it feels like a bad practice to pus put that right at the ns level.

markbastian15:08:17

(def in-chan (chan))
(def out-chan (chan))
(pipeline 16 out-chan (map (juxt identity (partial * 2.0))) in-chan)

(go-loop []
  (prn (<! out-chan))
  (recur))

(defn inject [query]
  (go (>! in-chan query)))

markbastian15:08:24

sorry, bad formatting first time

markbastian15:08:36

option B could be:

(def in-chan (chan))
(def out-chan (chan))
(pipeline 16 out-chan (map (juxt identity (partial * 2.0))) in-chan)

(defn launch []
  (go-loop []
    (prn (<! out-chan))
    (recur))
)
(defn inject [query]
  (go (>! in-chan query)))

markbastian15:08:06

you decide to initialize when ready, but the pipeline sits at the ns level. Seems better.

markbastian15:08:32

In order to make inject work in-chan needs to be in scope, so unless the whole thing is wrapped in a lambda and inject were returned as a fn I don't see how to encapsulate it all.

joshjones15:08:48

for long text you may consider the #core-async channel fyi you still have global stuff, unnecessarily i think -- no reason you can't create the chans in a function along with setting up the pipeline, and pass those channels to functions which do something with them

markbastian15:08:29

ah, didn't even know #core-async existed. I'll pop over there.

chrisjd15:08:19

I would opt for simple first; if your solution works as it is, I would stick with it. Otherwise youā€™re complecting things by creating ā€œcontextā€ objects for no real benefit.

chrisjd15:08:40

YAGNI or whatever. šŸ™‚

ghadi15:08:25

I would avoid globals and opt for arguments

ghadi15:08:08

launching the pipeline belongs in launch... as well as construction of the channels

ghadi15:08:59

you have an infinite loop in the consumption function @markbastian . Need to check (when-some [v (<! chan)] (prn v) (recur))

ghadi15:08:23

when the channel is closed it will return nil repeatedly and infinitely.

markbastian15:08:13

cool, thanks for the tip.

ghadi15:08:33

(defn sink [ch f]
  (go-loop []
    (when-some [v (<! ch)]
      (f v)
      (recur))))

(sink ch prn)

ghadi15:08:41

@tbaldridge knows that function ^

kwladyka15:08:14

hey i want to ask you about experience with grouping logs. For example in sentry there is fingerprint conception https://docs.sentry.io/learn/rollups/#customize-grouping-with-fingerprints . It works like ['foo' 'bar'] is grouping with others logs with the same values, but not with only ['foo']. Do you use it? What method do you have to group logs?

tbaldridge15:08:30

@markbastian I agree with @ghadi. Taking channels as arguments (instead of storing them in vars) is pretty essential to writing compose-able software.

markbastian15:08:17

Thanks for the tips. I figured that was the case, but I haven't seen any core-async resources online that explain the "right" way to use them. This is really helpful.

joshjones15:08:34

you're welcome mark -- generally you'll follow principles similar to what you'd use in any situation, i.e., prefer local state / params to global state

urzds16:08:11

How do I get all the methods that are part of a protocol?

urzds16:08:41

And hello everyone šŸ™‚

urzds16:08:43

I would like to create local bindings for all functions of a protocol against a certain instance of a record. So I want (to build) something that allows me to write (local-my-fn 123) instead of (protocol/my-fn impl/my-record-instance 123) for all my-fn in protocol/MyProtocol.

pjones16:08:08

@seancorfield thnx, will definitely check that out šŸ™‚

pjones16:08:43

@chrisjd thnx, will add

urzds16:08:03

On a different topic: I have a config.json, where I define which implementation shall be used for certain parts of my application. I start these parts using "mount", with (defstate my-part :start (condp = (:part config) "impl1" (impl1/->Part) "impl2" (impl2/->Part)). How would I do that without hardcoding all the implementations? Could I somehow make the different implementations for each Part register themselves? I played a bit with top-level forms in the different part's namespaces, which would update an atom somewhere, but that does not work, since the the code where the-atom-is-updated and where the-atom-is-used-for-lookup is not in the correct order.

Alex Miller (Clojure team)17:08:29

@urzds a protocol is a map that contains its implementation

Alex Miller (Clojure team)17:08:39

(defprotocol P (m1 [x y]) (m2 [a b]))
=> P
P
=>
{:on user.P,
 :on-interface user.P,
 :sigs {:m1 {:name m1, :arglists ([x y]), :doc nil}, :m2 {:name m2, :arglists ([a b]), :doc nil}},
 :var #'user/P,
 :method-map {:m2 :m2, :m1 :m1},
 :method-builders {#'user/m1 #object[user$eval2043$fn__2044 0x7087e497 "user$eval2043$fn__2044@7087e497"],
                   #'user/m2 #object[user$eval2043$fn__2057 0xbbcec2d "user$eval2043$fn__2057@bbcec2d"]}}

Alex Miller (Clojure team)17:08:05

none of this is ā€œpublicā€ api (you wonā€™t find it in any docs), but itā€™s also not something I would expect to change

urzds17:08:11

@alexmiller: So I could get all methods belonging to that interface by iterating over the :method-map? And with that list, I would write a macro, that generates the defines the local names / bindings?

mping18:08:11

Hi guys, whatā€™s the ā€œmodernā€ way of building http apis these days? Iā€™ve tried liberator and yada, but want to get more exposure

mping18:08:18

tried compojure also

ordnungswidrig18:08:21

Yada and liberator are both fine. pedestal is also a good option. It's all a matter of tradeoffs. Liberator and yada have a resource abstraction but the first binds you to the ring api, the later to Netty. Pedestal has solid async support but you need to implement a lot of the http resource semantics yourself.

mping18:08:38

Iā€™d like something with swagger, component/mount/etc and validation out of the box. nothing wrong with ring or netty imho, as long as I get an uberjar

csm18:08:21

Iā€™m a fan of compojure-api

csm18:08:30

the swagger and component integration has been great; we had to do a lot of finagling to support async responses, though

hiredman19:08:20

I would recommend looking for (or writing) a language designed to be sandboxed like that

hiredman19:08:40

clojure isn't, so it will take a lot of work to do it

hiredman19:08:40

basically you'll need what amounts to a custom compiler

hiredman19:08:59

the clojure compiler will emit code that uses reflection at runtime when the compiler doesn't know the classes involved, so not only do you need to control compilation, but you need to be able to do your checks at runtime

baritonehands19:08:49

I presented a REPL for our Java services, and the team was uncomfortable with exposing dynamic code execution even in our pre-prod environment

baritonehands19:08:06

I'm trying to find a good way to secure a REPL without requiring too much work

baritonehands19:08:53

it's already behind HTTP security using drawbridge

ghadi19:08:26

@baritonehands Consider copying the socket REPL code in clojure.core.server and adding a TLS socket listener to it, and then maybe wrap clojure.main/repl with a simple auth mechanism (PSK)

ghadi19:08:40

nREPL is a whole lot of baggage

mping19:08:22

if its for trusted users, I wouldnt care about sandboxing - not that different from having access to prod db, otherwise I wouldnā€™t expose a repl. too many possibilities šŸ˜„

urzds19:08:53

I built a system, where I can select which implementation of a Protocol to use based on a config file (using mount). Then I built multimethods to "register" these different implementations to certain config strings. Now the problem is that Clojure apparently never evaluates the namespaces where I define those multimethod implementations and hence I cannot load the Protocol implementations. Is there a better way to do such "component registration and selection at runtime" or is there a way to force Clojure to evaluate certain namespaces, even if they are not required from another namespace?

baritonehands19:08:37

@mping Unfortunately that argument didn't work

baritonehands19:08:59

@ghadi Has to be HTTP, so I don't have to expose any new ports

urzds19:08:25

The problem with requiring the namespaces from the "core" namespace is that there would be cyclic dependencies, since those require the "core" namespace again.

baritonehands19:08:13

@urzds Seems like you could require things dynamically, if you have the string of the full namespace

baritonehands19:08:26

that was for reloading a namespace of an existing symbol, like my-ns/foo

hiredman19:08:26

@urzds this is pretty common, the problem is your multimethod/protocol definitions shouldn't be in your core namespace

hiredman19:08:48

they go in some other namespace

hiredman19:08:46

you multimethods/protocol defnitions are like interface definitions

urzds19:08:58

@baritonehands That works:

(mount/defstate my-protocol
 :start (do
  (require (symbol (str "my-ns.my-protocol." (:my-protocol config))))
  (my-protocol/start config)))

hiredman19:08:27

do not do that, just structure your project well

urzds19:08:49

I will, once I figure out how.

hiredman19:08:38

you defprotocols and defmulti's don't belong in your core namespace

kwladyka19:08:31

hey, i am looking library like https://github.com/ptaoussanis/timbre but for business logs purpose. Unfortunately i canā€™t use timbre, because i use it for developer logs purpose. But i needs more or less the same logic for business logs purpose. Do you recommend something? Just i want push to something some data and add appenders to do something with this data.

hiredman19:08:23

@urzds create new namespace, typically ending with protocols or spi, and stick defmultis and defprotocls in there

hiredman19:08:04

then the implementation requires that, and core requires that

hiredman19:08:11

(and core requires the implementations)

urzds19:08:35

@hiredman: They are in my-ns.my-protocol. The defrecords and defmethods are in my-ns.my-protocol.my-impl namespaces. The problem appears when I try to create the state from the config using (mount/defstate ... :start ...). I would like the ...my-impls to register themselves, so I don't have to hardcode the list somewhere.

urzds19:08:56

"the problem" being what I described above: The implementations of the multimethod not being know, since the namespace was never required anywhere.

urzds19:08:51

@hiredman: Before I tried the multimethod approach to selecting implementations based on a config file, I had exactly the core -> impl -> protocol setup, but ran into some issues: my-ns.core requires my-ns.protocol1.impl1 so it can parse / execute the config file and create my-ns.core/protocol1-impl (using either my-ns.protocol1.impl1 or my-ns.protocol1.impl2). my-ns.protocol1.impl1 requires my-ns.protocol2 so it can use that, but it also requires my-ns.core, so it can access the selected implementation at my-ns.core/protocol2-impl. Thus there was a cyclic dependency. I could not break that cycle by moving (mount/defstate protocol2-impl ...) into my-ns.protocol2, because that would also have created a cycle between my-ns.protocol2 and my-ns.protocol2.implX. So I tried to break that cycle using the "everyone registers themselves" approach, which also breaks, because now my-ns.core requires nothing but my-ns.protocol1 and my-ns.protocol2, and hence no implementation is ever registered / defmethod is never called.

chrisjd20:08:06

@kwladyka Could you not just use a different config with timbre for business logging? See the GitHub page you linked under ā€œConfigurationā€; you could easily wrap the log* / logf* macros in a ā€œbusiness loggingā€ ns, for instance.

kwladyka10:08:43

thanks, looks promising, i have to find some examples

kwladyka11:08:49

oh i see, maybe it could be a solution

yonatanel20:08:54

@kwladyka I think it depends on what your data looks like, what you mean by doing something with it, and when you want it done. You could use different log levels and scrape the data later, or you might actually need to use message passing (core.async, manifold, even kafka).

kwladyka10:08:00

sure, kafka etc. are good, but i want simple solution. I donā€™t know manifold. I will look on it.

yonatanel20:08:28

@baritonehands What are the use-cases of that java services REPL?

baritonehands20:08:24

@yonatanel Just like a clojure REPL, I wanted to provide a programmatic interface to poke at anything, kinda like a remote debugger

baritonehands20:08:26

you can grab stuff out of the IoC container, query the DB, really anything

seancorfield21:08:47

@baritonehands We run a bare Socket REPL in one of our production web app servers. It starts up on an unusual-but-known-to-us port and it is only accessible via the loopback address via telnet directly on that server -- the port is not open, even within the server's DMZ. It's proven extremely useful for debugging over the years (it used to be an nREPL server but we switched to the built-in Socket Server a while back).

seancorfield21:08:39

Given that once you're physically on the server, you could start a REPL from the command line and run any code you wanted (from inside the JARs deployed there), having the process-embedded REPL accessible via telnet 127.0.0.1 <port> doesn't seem any more problematic. What sort of objections have your colleagues raised?

bfabry21:08:23

privilege escalation?

baritonehands21:08:28

@seancorfield Main objection was with devs running a REPL. It exposes more access than they currently have. SREs already have such access so it could be open to them. There was more concern over an accidental outage with good intentions than malicious use.

baritonehands21:08:50

@bfabry Exactly, and much more terse explanation

seancorfield21:08:04

Do devs have shell access to the production servers? I'm guessing not, in your case.

baritonehands21:08:42

nor preproduction

baritonehands21:08:03

The entire infrastructure was not designed with a REPL in mind. I think it would be possible to secure it, but may take some broad sweeping changes to the fundamental assumptions.

baritonehands21:08:33

Right now I'm trying to find a way to get most of the features without the priviledge escalation

seancorfield21:08:15

I agree that REPL access is pretty equivalent to full, unfettered shell access so if you don't have the latter, you shouldn't be given the former.

seancorfield21:08:51

Given the power of the REPL, I'm not sure how you could ensure restricted functionality via a REPL-like connection. You'd easily have to write your own DSL and parser and interpreter I think...

seancorfield21:08:30

...because restricting functionality would mean "not running Clojure code" -- it would have to be some DSL equivalent to a subset you wanted to allow.

noisesmith21:08:43

clojurebot instance with a replacement for the irc ui?

bfabry21:08:58

I think a repl running under a user with only the privileges you want users to have would work?

noisesmith21:08:02

we do have restricted access clojure sandboxes

seancorfield21:08:11

clojurebot still allows you to run a lot of raw code tho'...

noisesmith21:08:17

it does, yes

noisesmith21:08:25

but if strangers on irc can use itā€¦

seancorfield21:08:37

@bfabry Not sure what you mean?

noisesmith21:08:02

so like a separate clojure process that happens to be on the same machine?

bfabry21:08:44

yeah, and then regular unix permissions for access control

seancorfield21:08:06

@noisesmith To be useful for debugging production issues you'd want SELECT access to any DBs, I'm sure, so the scope is pretty large. A separate Clojure process configured with only readonly DB credentials might be an option I guess.

baritonehands21:08:10

@noisesmith I was thinking an actual REPL, pretty locked down in its own service, then a special interface for things it can do inside the other service

baritonehands21:08:38

so you'd get a repl, data manipulation, utility functions

baritonehands21:08:50

but only be allowed to do specific opt-in things in the other system

noisesmith21:08:59

if so, clojail could be a place to start looking

noisesmith21:08:08

but it sounds tricky to get right

seancorfield21:08:28

You'd have to restrict which Java classes it could create, and which clojure namespaces were off-limits (clojure.java.shell, http://clojure.java.io, etc).

baritonehands21:08:12

@seancorfield For restricting Java access, a simple @REPL annotation could do

ghadi21:08:17

i would love to hear a follow up on how this turns out a month after it's put into place

baritonehands21:08:31

I'd love to have a follow up

baritonehands21:08:09

hopefully I don't get fired over this šŸ˜“

hiredman21:08:12

the utility of a repl is its generallity, once you start restricting what you can in it, it becomes less general

ghadi21:08:20

i echo @seancorfield 's recommendation for using a local socket REPL if possible

hiredman21:08:46

once you need to explicitlly allow everything you might as well just slap an api on it and access from a local repl

baritonehands21:08:40

Alright, I'm off

baritonehands21:08:48

I will play around with these ideas

baritonehands21:08:55

thanks for the suggestions everyone

cddr22:08:31

I love the clojurebot idea for this. Then these production investigations happen in public and knowledge gets shared with the team. Is this something you do on your team @noisesmith?

noisesmith22:08:23

We have a socket repl, I thought of the bot as an example of solving, or nearly solving, the repl with restrictions issue.

lvh22:08:51

When I need/want a sub-ns for spec, is there any particular reason not to just do:

(create-ns 'headmarc.rua.dkim)
(alias 'dkim 'headmarc.rua.dkim)
(create-ns 'headmarc.rua.spf)
(alias 'dkim 'headmarc.rua.spf)
?

bfabry22:08:45

lots of people do that, the only downside I'm aware of is lack of cursive support

hiredman22:08:47

why create the namespaces at all?

hiredman22:08:15

create-ns creates a namespace for holding code

hiredman22:08:24

spec namespaces are keyword namespaces

bfabry22:08:18

boot.user=> (alias 'foo 'foo.bar)
java.lang.Exception: No namespace: foo.bar found
?

hiredman22:08:19

the only place where code and keyword namespaces intersect is the '::' syntax, which is a tempting short cut, but a bad idea outside of playing at the repl

lvh22:08:39

@hiredman why is it a bad idea? Itā€™s pretty convenient when you have a bunch of large maps to type in.

hiredman22:08:58

lvh: because the names will change whenever you move the code around

hiredman22:08:10

the whole idea is stable names

lvh22:08:12

(anyway yes thatā€™s why; I understand how kw namespaces differ from regular namespaces)

hiredman22:08:43

yeah, type :dkim/foo then use your editors find and replace to change it to the real thing once you have typed all the maps

hiredman22:08:21

I would go further and recommend not using keyword namespaces that match a code namespace's name

hiredman22:08:29

spec names are like table.column names in a sql database, you don't name those after the code that reads or writes it

bfabry22:08:41

disagree with this. lots of keyword namespaces are good fits to be the same as a code namespace, though obviously not all. in the cases where it's not I still don't see a downside to having a real ns object for it and getting an alias

seancorfield22:08:02

@lvh You can put the create call inline:

(alias 'dkim (create-ns 'headmarc.rua.dkim))
(alias 'spf (create-ns 'headmarc.rua.spf))
I think that's cleaner -- and will be less editing if/when alias is changed to auto-create the ns if it is missing (which is under discussion).

seancorfield22:08:26

As for specs, I think there are cases where having them per @hiredman's suggestion is the right way and other cases where having them tied to actual code namespaces is the right way. It depends what they're a spec of and also what their intended scope is (public API, internal subsystem, opaque within a namespace etc).

baritonehands22:08:22

@noisesmith how does the clojurebot solve the REPL with restrictions?

noisesmith22:08:55

@baritonehands it provides a sandboxed repl where users have restricted permission - but others have brought up the complications that come into play if you need to access data or apis (things like the db for example) that the clojurebot isolation does not address

cddr14:08:59

But you can add db/api access though? In the same way that plugins have been added for things like github links.

baritonehands22:08:04

It seems it doesn't use clojail, how does the isolation work?

seancorfield22:08:32

According to the Clojail readme, clojurebot does use it.

hiredman22:08:02

which clojurebot?

noisesmith22:08:10

@seancorfield I thought only lazybot used clojail

seancorfield22:08:26

Ah, maybe I misread the README then...

seancorfield22:08:06

,(clojure-version)

seancorfield22:08:18

(isn't that how to invoke the bot here?)

seancorfield22:08:23

I guess not...

hiredman22:08:34

the bot in slack, however it is invoked, is not that clojurebot

hiredman22:08:00

as far as I know, unless someone else is running and instance and wired it to slack

seancorfield22:08:46

Anyways, @baritonehands You'd have to write some sort of pseudo-REPL that used Clojail to do the actual evaluation part -- so you couldn't just leverage the standard Socket Server REPL and Clojail together easily I don't think?

seancorfield22:08:29

So it's /clj (some form)

seancorfield22:08:12

The integration seems to be owned by @samflores and points to so I'm not sure which bot that is.

seancorfield23:08:46

I'll take that elsewhere...

seancorfield23:08:59

OK, so whatever bot is attached to /clj does use Clojail...

seancorfield23:08:03

Having it respond "clojurebot" when it's not @hiredman "clojurebot" is confusing šŸ™‚

hiredman23:08:28

have to namespace the nicks

devn23:08:30

I miss IRC...

devn23:08:54

(Know that I will repeat this every few months)

spangler23:08:49

IRC is still there! @devn