Fork me on GitHub
#clojure
<
2018-04-11
>
dpsutton00:04:41

@benzap I'd love to read the code

benzap00:04:57

@dpsutton i'll be making the repository public in a day or two, I have a lot of refactoring to do, and I still need to implement a primitive macro system

benzap00:04:56

But for a basic breakdown, it's a forth-like language, I'm going to be calling it fif

benzap00:04:12

fully interpreted, stack programming language in clojure

😄 4
dpsutton00:04:51

Awesome. I love fun experiments like this. We are a lisp after all. Not everything has to be ETL :)

😁 4
john01:04:31

interesting

brycecovert02:04:02

Are there any patterns for using spec and namespaced keywords to assist in catching bugs caused by renaming fields or typos in destructuring? A simple example is if I’m going to update my software to move {::invoice/vendor-id 1} -> {::invoice/vendor {::vendor/id 1}. One way to think about that refactoring is to change the specs those keywords point to, and try to find references to where the wrong values are pulled out.

brycecovert02:04:39

Essentially I’d like to get some kind of error / warning / linter going on to ensure I pull fields out correctly after I’ve changed specs

johannjohann02:04:57

as a sanity check, is anyone else getting 'error building classpath' message when they run clj -Sdeps '{:deps {yada {:mvn/version "1.2.11"}}}' ? https://i.imgur.com/iKTHBci.png

seancorfield02:04:03

@johannjohann That check has been removed in more recent versions of tools.deps.alpha. I don't know if clj has been updated yet to the new version.

Alex Miller (Clojure team)03:04:25

clj was never released with that check

seancorfield03:04:45

Interesting. I wonder how @johannjohann ran into it then?

johannjohann03:04:43

i am on nixos and installed clojure using its package management. you can see how it was installed here https://github.com/NixOS/nixpkgs/blob/b1ccedb6d2b1d92f1843fe4afc96f2ff87190eae/pkgs/development/interpreters/clojure/default.nix#L16

Alex Miller (Clojure team)03:04:43

1.9.0.329 was an interim, non-official release

Alex Miller (Clojure team)04:04:31

so it was released, just not in anything I announced publicly or considered official

seancorfield02:04:11

@johannjohann I'm using clj 1.9.0.358 and I don't get that error with just that dependency. Do you have a deps.edn file in that directory as well? Just checking whether that is your only dependency...

johannjohann02:04:12

@seancorfield nope, i dont. i will bump my clj version and give that a go 🙂 appreciate the help

seancorfield04:04:31

Did you get clj updated? Did it fix the problem?

johannjohann04:04:02

sure did 🙂

4
dominicm14:04:25

ran into this today also, how did you update @johannjohann? NixOS repo seemed to be an older one?

datran02:04:23

I'm trying to turn edn into transit-json, but all the documentation of transit that I can find talks about using transit/writer to write to a destination. I think I just want a value, how do I get that?

datran02:04:34

I can go from transit->edn by converting to a byte array and then reading it like so: (t/read (t/reader (ByteArrayInputStream. (.getBytes ["^ ",":q",":chat-msg","~:message","asdf"])) :json))

datran02:04:58

(which seems a bit convoluted to me, but ok)

datran02:04:28

;; => {:q :chat-msg, :message "asdf"}

datran02:04:40

^which produces this nice edn form

datran02:04:00

I'd like to do the reverse, but I'm beginning to wonder if I'm just using the whole thing incorrectly

metacritical05:04:24

Hi does anyone know how to pass jvm-opts to the clj from terminal? specifically how do i pass ‘--add-modules java.xml.bind’ ?

gklijs05:04:43

-O - JVM option aliases

Allowed keys in these aliases are:

:jvm-opts - a collection of string JVM options

If multiple -O alias maps are activated, :jvm-opts concatenate

If -J JVM options are also specified on the command line, they are concatenated after the alias options
from https://clojure.org/reference/deps_and_cli

gklijs05:04:29

or just

-Jopt           Pass opt through in java_opts, ex: -J-Xmx512m

seancorfield05:04:25

I think, because you want an option and its argument, you'll need to specify -J twice:

clj -J--add-modules -Jjava.xml.bind

metacritical05:04:31

@seancorfield Ahh thankyou, now i know what i was missing.

seancorfield05:04:53

This cropped up in one of the channels here a week or so ago. It's counter-intuitive.

sh4z11:04:44

(same problem if i use str/includes? instead)

rauh11:04:24

@jlindeberg88 Make sure to read the doc string of reduce. The fn is being called once with both args

sh4z11:04:48

Ooh I see, thank you.

Cynical3.1412:04:06

Hi, everyone! I am trying to use the malabarba/lazy-map library for a lazy implementation of maps inside an anglican query but I am having trouble. anybody could help?

Cynical3.1412:04:20

P.S. : is there an official/dedicated chat for anglican applications only?

Empperi12:04:26

hmm, I could swear I've read somewhere how to preserve lines in stacktraces when wrapped with a custom macro but my Google-Fú is failing me

Empperi12:04:48

it's getting annoying that in my tests with this one wrapper macro I do not know where the error occured

Empperi12:04:27

does someone know the answer and could help me at least into correct direction?

Empperi12:04:46

ah right. I think I already got it by looking at the source code of ->

Empperi12:04:05

one has to ensure argument form metadata is passed along

Empperi12:04:44

didn't work easily but it does make sense that one has to do that

leonoel12:04:20

I wish I would have known that earlier

Empperi12:04:18

it's not quite that straightforward if your macro is complex but in theory it should work

leonoel12:04:57

works like a charm, many thanks !

devurandom12:04:12

Hi! Any idea where http://planet.clojure.in/ moved? Their domain expired -- did they move to a new one?

michaellindon14:04:59

if I define a function in the repl (defn square [x] (* x x)) for example. Is there a way that I can replace later calls to (square a) with (* a a) without defining square as a macro?

michaellindon14:04:43

or even just a way to look up the body of the function?

troglotit14:04:15

(source square)

michaellindon14:04:03

user=> (defn square [x] (* x x))
#'user/square
user=> (source square)
Source not found
nil

michaellindon14:04:13

do I need to require anything?

troglotit14:04:43

I’m sorry I’m not sure why it doesn’t work.

michaellindon14:04:56

(source max) works for me

michaellindon14:04:07

user=> (source max)
(defn max
  "Returns the greatest of the nums."
  {:added "1.0"
   :inline-arities >1?
   :inline (nary-inline 'max)}
  ([x] x)
  ([x y] (. clojure.lang.Numbers (max x y)))
  ([x y & more]
   (reduce1 max (max x y) more)))
nil

troglotit14:04:21

also, it worked for me in lumo. I mean (source square)

joelsanchez14:04:33

Prints the source code for the given symbol, if it can find it.
  This requires that the symbol resolve to a Var defined in a
  namespace for which the .clj is in the classpath.

michaellindon14:04:41

Could you please explain the last sentence?

joelsanchez14:04:05

it only works for definitions from source files

joelsanchez14:04:38

lumo does black magic so I can't explain why it worked though

michaellindon14:04:05

im interested in doing symbolic differentiation of mathematical expressions

michaellindon14:04:40

i can differentiate an expression (* x x) for example, but if the user defines the function square, and i want to do symbolic differentiation of (square x)...

michaellindon14:04:10

i guess i just have them put all their code in a file and reload it in the repl

ghadi14:04:02

Anybody have experience with Sentry (error reporting) and don't mind me pestering with a couple questions?

mccraigmccraig14:04:05

we've been using it for a while @ghadi

ghadi14:04:55

cool! do you use sentry-java directly or sentry-clj? Do you do any custom exception roll-up for stuff like ex-info?

mccraigmccraig14:04:30

we're using sentry-clj and we aren't doing any custom exception roll-up

ghadi14:04:24

do you set release in the DSN or send it on every send-event?

ghadi14:04:33

Thanks for the answers btw. Super helpful.

mccraigmccraig14:04:40

looking now... we're only setting release for the clojurescript builds, and we are configuring it when we initialise the raven client, which presumably sets it with the DSN

mccraigmccraig14:04:56

we also upload source-maps for the clojurescript apps

mccraigmccraig14:04:07

that works well for web-deployed clojurescript apps

mccraigmccraig14:04:33

but we haven't been able to get it to work for our hybrid mobile (cordova) apps, for no obvious reason

mccraigmccraig14:04:17

we have a boot task for uploading the source-maps to sentry ... it's in a private repo atm for no particular reason, but if you want it you are welcome to it

ghadi15:04:05

Neat! we're doing only clj, no cljs, just trying to get it to work better for us

mfikes14:04:36

If you are curious. Planck and Lumo simply capture the source for REPL-entered forms so that the source function can display that source. The change is fairly straightforward. https://github.com/mfikes/planck/commit/f16e6187f09a0201b3b2cefbfd8e0c1d75fa053d?w=1 (Hopefully this could make it into the standard REPLs some day 🙂 )

😮 4
Carc15:04:43

Hey guys. I was directed here by a member of the Google+ Clojure community.

Carc15:04:03

I wrote up an interesting macro, and am looking to get it reviewed. The full explanation of it is on the review page, but basically it allows you to write expressions in linear problem-like notation to create fitness functions that can be used in a genetic algorithm. Example: (fit-func-for [:s :g] :maximize (+ ( :s -2) ( :g 5)) :subject-to (<= 100 :s 200) (<= 80 :g 170) (<= 200 (+ :s :g) 1e6)))) https://codereview.stackexchange.com/questions/191736/macro-that-allows-for-linear-problem-notation-to-create-genetic-algorithm-fitness The GitHub page for the genetic algorithm can be found here (although it's not well documented): https://github.com/carcigenicate/genetic-algorithm/tree/master/src/genetic_algorithm/test_fit_funcs

benzap16:04:33

I haven't reviewed the code, but a few quality of life changes that I would recommend for publishing your code * Make sure that you don't commit anything into the repository that isn't part of the codebase. I would put that /.idea folder into the corresponding ./.gitignorefile

benzap16:04:07

within your project.clj, you are pulling in [seesaw "1.4.5"]. Is this for development? Just make sure to put any libraries that are used for development in the separate pre-defined :dev profile, this ensures that people who pull in your library from a public repository don't get unnecessary dependencies

benzap16:04:54

Last and not least, you should deploy to a public repository! I would love to see this out and about 🙂

Carc16:04:17

Thanks regarding the gitignore recommendation. And I was going to make a nice UI Seesaw once I'm happy with the rest of the code. I'm not sure what you mean by using :dev. I've never used it before. I'll have to look that one up.

Carc16:04:30

And it is public isn't it?

Carc17:04:31

I should note that my main concern is the macro code. I arguably shouldn't have even posted the github link since it's an undocumented mess. There's a couple side projects in there that I didn't feel like starting a new project for (like the macro), and ya, currently unnecessary dependencies and the like. I also mainly use Git(hub) as more of a "enhanced undo" tool, not for sharing code. I only share it to github so I can peek at the code if I'm trying to remember how I did something previously, but don't have my computer on me.

benzap20:04:39

For the :dev stuff, i'm referring to lein profiles. You can separate anything and everything used in development from whatever is deployed

benzap20:04:36

Sorry, I wasn't very clear with the repository stuff. I meant to take your code, package it up, and deploy it on something like clojars, it makes it easier to mess around with

benzap20:04:22

Your github repo is your main tool for getting feedback, but all the power to you. I don't think there's any right way to use github haha.

theeternalpulse15:04:32

what was the library that turns the core.spec error messages into human readable text (and others, but specifically for runtime/compile errors)

joelsanchez16:04:41

expound?

👍 8
theeternalpulse17:04:18

Yes, I keep forgetting the name

jjttjj17:04:24

Has anyone ever found themselves in a situation where introducing mutation to clojure code actually helped make a cleaner solution? Is adding mutation to code ever justifiable in clojure as a means to make code more readable?

hlship17:04:19

It is sometimes acceptable to use a local Atom or Transient, as long as the mutation is hidden well within a function. You actually see this in some transducers, where a Transient is created by the transducer, but its activity is well hidden.

hlship17:04:13

I'm working on some code involving ZooKeeper, and just accepting an Atom at the center of all these callbacks kept things sane. I could have tortured the code to avoid it, but ZooKeeper data is expressly mutable, like most databases.

eraserhd18:04:27

My test-helpers namespace for a project has a half-dozen dynamic variables which get manipulated. I tried using some port of monads to clojure to compose effects, but it was much, much worse.

eraserhd18:04:26

The span of a test case is only a few lines, and there's a wrapping macro, so it's easy to reason about.

danielsz00:05:49

You want mutation when it helps decompose a problem. SICP explains this well in the chapter on assignment. When estimating Pi according to Cesaro, you get a side by side demonstration of a functional approach, and then the one that uses mutation, which is the elegant one. Mutation allows you to separate the random number generators as objects with internal state, which in turn allow you to write a general monte-carlo procedure, giving you separate pieces that work well together and can be reused (in contrast to the monolithic functional implementation).

jjttjj17:04:29

Long story short I need to parse a bunch of these messages from an outside source. Each message is parsed differently and additionally each message has many versions which will also dictate how they be parsed. The parsing also requires "memory", in that I'll hit a number 2 and then need to take two items from the raw input. Basically I wrote a hairy macro to aid in handling all the message types. But so far I'm finding it's still much cleaner to just treat the input as a mutable stack and pop things off as needed

tbaldridge17:04:17

@jjttjj yes, on a few occasions. There's a few cases where immutable data helps a lot. Two of them are 1) when an algorithm needs to backtrack. 2) where data will be shared between threads, or reused or saved by other parts of the program.

john17:04:17

If your local mutation is clearly a safe operation, it's all good IMO. Just makes the code less reusable though.

tbaldridge17:04:02

So in some things, like compilers and interpreters, both of those don't apply and therefore mutation can be cleaner.

jjttjj17:04:46

Ok good to know. I feel better much better now haha

tbaldridge17:04:17

I wouldn't do it as the norm, but used in the right cases it isn't bad.

tbaldridge17:04:52

There are aspects of parsers that need to backtrack, so look for situations to leverage immutability there.

jjttjj17:04:26

Yeah I've spent a month or so mulling over non mutable ways to handle this and have a few working ideas of how to do it, but then I just realized the code is much more readable with mutation

justinlee17:04:03

sometimes you end up passing around an object which is essentially a representation of global state that gets mutated by each function. it’s technically purely functional but in name only.

tbaldridge17:04:38

@lee.justin.m not really. If you're mutating the object it's not purely functional

justinlee17:04:01

i mean, you thread the “new” object through each functional call in a way that almost the same thing

jjttjj17:04:03

I don't think it's backtracking really. maybe "lookahead" though? I basically need to zipmap a list of values with my list of keys. But the keys will vary based on the version of the message itself (which it contains), it will also vary on outside factors (overall server version). and lastly some values will need to be grouped together before being assigned to a key

justinlee17:04:24

i get it’s not exactly the same

jjttjj17:04:59

and the grouping is based on a function of one of the values

tbaldridge17:04:00

yeah, I've heard that comment before, that threading immutable data through functions = the same thing as OOP, and I strongly disagree

john17:04:01

Yeah, immutability can be a contextual thing, with respect to a particular scope

justinlee17:04:03

to make an extreme example: the benefits of referential transparency are lost if every single function were to take a “global” object as a parameter and return a new “global” object that then gets passed to the next function. sometimes code starts to feel like that.

tbaldridge17:04:27

why? what is lost?

tbaldridge17:04:02

you now have a fully immutable, backtracking system.

justinlee17:04:38

the ability to look at a function and say, “this function only affects X and Y, not Z”. if the global object just gets passed around willy nilly, you can’t do that analysis just by looking at the signature.

ddellacosta17:04:08

how can you do that in Clojure anyways (without spec annotations or the like)?

tbaldridge17:04:12

That problem doesn't go away without the map though. It's even worse, it could be accessing global vars 5 levels down?

tbaldridge17:04:27

or mutating dynamic vars

john17:04:07

Yeah, if you're not dealing with one thing, you're dealing with many, right?

justinlee17:04:09

sorry you guys lost me 🙂

john17:04:38

by map, I think he was referring to the global val

tbaldridge17:04:07

you're claiming that (my-fn all-my-state arg1 arg2) is somehow more complex than (my-fn arg1 arg2), but my-fn could be still accessing global vars that could mutate.

ddellacosta17:04:08

I think the point is, simply removing the “global object” from the equation doesn’t necessarily start providing you with more insight into what a function is doing, per your example (“this function only affects X and Y, not Z”)

john17:04:00

@lee.justin.m What a lot of folks do instead is use map destructuring on the global object passed through, so that you can see clearly in the destructure sig what parameters are relevant, from a documentation perspective.

john17:04:44

that way you just pick out the pieces you need from the global state, as the vals pass through your fns

justinlee17:04:22

i’m saying if you have a purely applicative library, the huge benefit from my perspective is being able to look at call site and figure out what variables might change. obviously if the library is mutating stuff from under you, then that’s different.

ddellacosta17:04:30

what is a “purely applicative library?” More generally, how can what you’re talking about be guaranteed in Clojure?

ddellacosta17:04:00

there is no way in Clojure to determine whether a function is doing something side-effecting under the hood simply by examining the call site

justinlee17:04:33

there is if you wrote it. i’m talking about a style of programming.

ddellacosta17:04:34

and if anything, establishing a contract to pass global, stateful values around explicitly gets you closer to that

john17:04:02

So, IMO, you have trunk logic and leaf logic in Clojure. For trunk logic, you want to define fns such that they deal with the one global state. For leaf logic, you want to define fns such that they are applicable to only subsets of the data.

ddellacosta17:04:21

if we’re simply talking about conventions then…yeah, you can establish conventions for anything

john17:04:23

leaf logic should be usable across different trunks

john17:04:47

trunk logic will generally be specific to the system / shape of the tree

markw17:04:57

I think about this question a lot myself... just this morning I was thinking about how using something like webdriver to make requests would be different in a functional manner vs OO. In an OO language, webdriver updates internal state, on each request, subsequent requests will know what to do based on the current state of the object.

ddellacosta17:04:09

but we’re now off-track from the original discussion, which had something to do with how threading global values through a bunch of functions is bad

markw17:04:29

To do the same thing functionally would mean passing a webdriver map all over the program, and i might not always need every thing in the map

justinlee17:04:00

i never said it was bad. i was suggesting that sometimes you end up writing code that is really imperative code in a functional way and maybe that’s when its just better to write imperative code

markw17:04:01

I don't really have a problem with it, but I do agree it makes it non-obvious from the call-site what is happening

tbaldridge17:04:23

@lee.justin.m we have to be careful about introducing imperative into this

tbaldridge17:04:33

99% of Clojure code is imperative (vs declarative)

john17:04:47

If you're not threading one global value through your trunk, then you need to deal with multiple global values. And the way to deal with that is via ref types... unless you constrain your program to use one global state val. Which is why Clojurists have leaned towards using one global val. So we don't have to grab for the really big hammer of MVCC

justinlee17:04:15

but I think you know what i mean 🙂

ddellacosta17:04:55

I don’t actually. If anything the technique convention of passing a global, stateful value (or values) explicitly through a bunch of functions is far less imperative than the alternative

justinlee17:04:00

Are you guys saying that it is a clojure convention to pass most of the program state around in a single global map?

justinlee17:04:17

Doesn’t that bind every single function together in the most brittle way imaginable?

tbaldridge17:04:20

Datomic encourages this as does Pedestal, and even Ring

ddellacosta17:04:04

@lee.justin.m it’s less brittle than the alternative, which is side-causing/side-effecting values hidden in functions

tbaldridge17:04:21

You have that binding anyways, change the signature of a function and everyting will break

justinlee17:04:26

What about just passing what you need?

justinlee17:04:32

I think I’m missing something here. 🙂

tbaldridge17:04:38

absolutely, that's even better, and that's encouraged by Component

markw17:04:46

doesn't passing the entire state kind of resemble the "god object" antipattern

markw17:04:07

I do it all the time, just curious to hear opinions

tbaldridge17:04:15

Sure, if every component expects the entire map.

john17:04:15

I've never heard of that pattern, but it makes sense

tbaldridge17:04:55

That's the whole point of Component, you create trees of components, each component receives only it's portion of the tree. If you need something else in your part of the tree, you declare a dependency and it gets injected into your branch of the tree.

tbaldridge17:04:22

But the root of the tree contains all the state for all the components.

john17:04:36

@lee.justin.m (defn do-specific-thing-with-global [{:keys [thing-one thing-two]}] (thus-and-such thing-one ... how is that brittle?

markw17:04:54

it's not... but he did mention a valid point about the callsite

markw17:04:02

the signature makes it obvious

markw17:04:07

but it's all just the same map at the call site

ddellacosta17:04:20

I’m still not following how we can learn much of anything about what a function does from examining the call site…especially if we are removing arguments

ddellacosta17:04:31

but maybe we’re talking about two different things

ddellacosta17:04:44

I need to go eat. Enjoy the convo folks 👋

justinlee18:04:03

@john it hides the dependencies of do-specific-thing-with-global at the call site and connects the specific structure of the global with the implementation of the called function

justinlee18:04:24

as opposed to if you destructured at the call site and passed

ddellacosta18:04:12

and then…something else needs to know about the structure of the global stateful thing

john18:04:13

Somewhere in your tree, you're going to have to divy your state out into your fns, though.

ddellacosta18:04:17

you’re just pushing the responsibility around

ddellacosta18:04:33

(okay leaving for reals)

justinlee18:04:03

yes you push responsibility around but you also decouple the called function from the global state

justinlee18:04:13

sometimes that makes sense and sometimes it doesn’t

john18:04:18

For some fns, that's exactly what you want to do

john18:04:36

A fn that works generically on a map, sure

john18:04:52

a fn that ties your state into those generic fns? Different story

john18:04:15

leaf logic and trunk logic

john18:04:56

Like Tim was saying, you can use things like component that limit the flow of global vals down into some of your branches... But, in general, that isn't necessary. Since the global map val is structurally shared, it's not like your loosing memory by passing that pointer around.

justinlee18:04:04

Interesting conversation folks. Thanks.

👍 12
noisesmith18:04:26

another aspect here is that you can use select-keys with merge to limit the changes that can be done to the map you pass in

noisesmith18:04:38

or ignore the return value entirely to ensure they can't alter any of it

justinlee18:04:45

@U051SS2EU and I think the point I was inelegantly trying to make is that if you ever feel like you need to do that it means you have managed to write code that has some of the same drawbacks of stateful oopy code

noisesmith19:04:02

Right, my point is that it's still granular in a way that stateful code cannot be, both in visibility and in scope of changes

noisesmith19:04:50

That is, you're still better off than you were with global mutation (though the benefit is smaller in cljs since you don't have parallelism there, the biggest benefit is correctness in parallel code without locking)

john19:04:22

If your fn graph does not form a DAG (which pushing a global val through all your fns allows you to do), then the access patterns of your fns over your state will need to be coordinated by some state management system like STM

john19:04:24

(or some lensing like functionality over your global state, like @U051SS2EU was referring to)

justinlee19:04:44

I still don’t entirely understand why people want to do this rather than pass more granular data to small functions.

john19:04:40

If you can define your fn generically, such that it is not aware of global state, you should. But any app that has to manage some amount of state, necessarily cannot in all cases.

justinlee19:04:44

This convo has made it crystal clear to me why nobody seems to want closed maps!

john19:04:13

let the values flow

justinlee19:04:22

… and the typos

ddellacosta19:04:22

what’s a “closed map?”

justinlee19:04:34

In type checkers like spec or whatever, a closed map is one that does not allow unexpected keys.

ddellacosta19:04:02

ah. In that case, I still want closed maps

ddellacosta19:04:17

and the topic seems orthogonal to talking about passing state around

john19:04:28

You can always define a deftype or define whatever closed thing you want. Rich is just trying to keep the default spec behavior for maps to be open by default, so people don't go building things that don't work wit other things, by design

ddellacosta19:04:58

let’s not go there. I’ll just say I disagree (re: restricting what keys can be present in a map as a default behavior of spec validation for e.g. keys) and we can leave it at that

john19:04:16

lol fair enough

justinlee19:04:50

@U0B6ER3U3 The reason why it seems relevant is that if you pass a big map around and all kind of different pieces of code will be messing with it, a closed map is going to break. At least I think that’s the thinking. @john It is not easy to do closed map checking in spec and it doesn’t seem like anybody is interested in making it easy.

ddellacosta19:04:24

as far as this thread, re-reading it now it seems like we were talking past each other--I was never arguing that you couldn’t pass smaller subsets of a large stateful thing to functions, simply that passing stuff around vs. side-effecting in the body of a function was a more functional/declarative approach. Seems like that was never in contention though.

ddellacosta19:04:44

@lee.justin.m how will it break? And why is breaking bad?

justinlee19:04:42

I don’t think it is. I really want closed maps in a type checker but I’m seeking to broaden my mind here and gain wisdom from those who are more experienced in closure to understand why there is resistance to it.

ddellacosta19:04:47

or maybe it’s better to ask: when will it break?

john19:04:11

@lee.justin.m you could define a custom map thing, similar to def records, which are closed by default. And then define spec behaviors around those -lookup and other calls in a closed way, I assume.

ddellacosta19:04:33

ah. Yeah, I just think it’s wrong…haha

ddellacosta19:04:18

I haven’t yet seen a reasonable explanation other than some combination of “it makes it hard to extend your stuff” and “this is how it is, suck it up.”

ddellacosta19:04:26

but, that ship has sailed

justinlee19:04:49

@john I’m intrigued by that idea… must think about it a bit

john19:04:44

Yeah, I don't have a lot of skin in that game. If I felt that pain, I would think I could just implement closed maps and build specs for that, but I could be wrong. I'm no spec ranger yet.

ddellacosta19:04:37

I’ve been bitten too many times in spec by adding a value in a map and having the validation pass but the code break because of a subtle misspelling in the key

john19:04:09

I rarely use spec atm... should I also be experiencing that same pain all the time?

ddellacosta19:04:25

if we want to talk about design mistakes we can also talk about maps and nil…or shit, just nil period 😂

john19:04:33

I guess when you don't lean on a spell-checker, you don't feel the pain as much, when it doesn't work?

ddellacosta19:04:14

a spell-checker is a pretty terrible analogy, IMHO

john19:04:18

A nilless lisp would be interesting to see 🙂

ddellacosta19:04:33

Clojure is a lisp?? 😛

justinlee19:04:43

You don’t need any of this stuff if you always write correct code. 🙂 The question is how to establish invariants in your code in smart places with minimal work to help to get from broken code to correct code.

john19:04:34

Well, using spec as a checker for typos in key names

john19:04:00

I mean, if a map is invariant wrt to it's keys... it's more like an object than a map

justinlee19:04:01

Doesn’t help if you misplace a key in a nested map or mess up an optional key. In UI programming my components often have a bunch of optional keys. If you typo one of them you can go mad trying to figure out why it’s not working.

john19:04:02

is my point

ddellacosta19:04:34

not running a spell-checker against your text doesn’t prevent you from “executing it” and getting correct results: I can generally figure out what you mean most of the time even if you misspell something. Whereas not catching misspellings in keywords and the like means my program may break disastrously

john19:04:36

if you typo any part of your program, you're liable to go mad

ddellacosta19:04:08

but, this is all quibbling I suppose

justinlee19:04:26

I don’t understand this philosophy. There’s an obvious way to help programmers from making a common type of mistake. Just because there are other ways to make mistakes doesn’t mean we should do something that helps.

justinlee19:04:53

If you take every opportunity you see, you end up with an awesome system that everyone wants to use.

ddellacosta19:04:00

yeah, I’m with you @lee.justin.m, and that’s how I see it too

ddellacosta19:04:38

“Programmers know the benefits of everything and the tradeoffs of nothing” - Rich Hickey

ddellacosta19:04:56

so sayeth our great leader

john19:04:01

But using a thing that ensures that a map acts like a map is not going to help you when you want to ensure a map is acting like an object who's getter and setter method names are statically named.

john19:04:39

That's not a map

ddellacosta19:04:42

I’m not sure I follow. There is a world of possibility between defining types of maps and Java-style objects

john19:04:14

Right, but those things aren't Clojure maps

ddellacosta19:04:42

well, sure, but I thought we were being speculative here

john19:04:49

If someone passes you a thing that you can't assoc a new key value pair onto, arbitrarily, it's not a map

ddellacosta19:04:12

so to be clear: I was talking about a limitation (as I see it) of a very specific part of clojure.spec

ddellacosta19:04:21

not necessarily a redefinition of Clojure maps

ddellacosta19:04:50

that said, I could imagine a type system on top of Clojure that enabled a far richer set of possibilities for what a valid map could be, encompassing both “closed” and “open” maps

ddellacosta19:04:57

these things are not mutually exclusive

ddellacosta19:04:33

but, I understand it’s unlikely Clojure will ever go there

john19:04:35

right, and once we start talking about behaviors of datastructures, closed-key behavior does not belong to the class of data structures we call maps in clojure.

ddellacosta19:04:05

well, it could, if we wanted it to, is all I’m suggesting, and sometimes it’s exactly what you want

ddellacosta19:04:36

and furthermore--in response to your points about setters and getters, making that choice does not beget objects in the Java sense

ddellacosta19:04:41

not necessarily at least

john19:04:51

right. but when someone else wants to use your lib, you are changing the behavior of the data structure as it passes through your system

john19:04:51

your validation behavior is constraining the behavior of the structure into a less-than-map-like thing

john19:04:12

Which can trip up your consumers

ddellacosta19:04:29

yeah I mean, if we’re just talking about sticking with Clojure semantics obviously the more speculative aspect of what I’m talking about (a more sophisticated type-system on top of Clojure) is not viable. But interpreting a map flowing into a function via spec as a “closed map” is totally viable within the context of a specific application. However, it’s not how spec is set up by default, and that’s clearly an opinionated choice on the part of the Clojure core team

john19:04:12

The more sophisticated type system is core.typed and it's fairly sophisticated. I believe you can do all these things.

justinlee19:04:33

you can do it, but not in a way that anybody would want to look at

ddellacosta19:04:52

yeah core.typed is cool, but it’s hard to retrofit a type system like that on a language like Clojure.

ddellacosta19:04:02

believe me I know I’m off in fantasy land when I talk about this stuff

justinlee19:04:03

and the resulting error is worse than not having an error at all

ddellacosta19:04:37

yeah…there’s a reason the circleci folks stopped using it

justinlee19:04:58

it does not make the simple thing easy. the simple thing would be “yo, what the hell is this key. i don’t want that key. you screwed up”

john19:04:06

my point is that, even with spec, I'm pretty sure you can define a custom closed-map like thing, and build closed req-key like semantics yourself, if you really, really want it

ddellacosta19:04:47

@john I don’t think we’re in disagreement there

ddellacosta19:04:58

and as a matter of fact I have done it

john19:04:32

did it help?

ddellacosta20:04:10

it solved that particular issue, sure

ddellacosta20:04:23

I mean, this is a really specific quibble about one aspect of spec

ddellacosta20:04:13

I also have issues with maps and Clojure in general, but I don’t expect those’ll ever be addressed

ddellacosta20:04:22

so when I talk about those, I’m just goofing off

john20:04:08

Yeah, it certainly has generated a lot of discussion - this open vs closed maps questions

john20:04:22

IMO, closed maps are not maps but are just objects by another name

ddellacosta20:04:31

if you can define “object” for me then maybe we can talk about it meaningfully

ddellacosta20:04:00

I just see it as a map of a different type

john20:04:04

A data structure where data can be put into and taken out of the structure using named accessors

john20:04:24

or something

ddellacosta20:04:32

that sounds like a map

ddellacosta20:04:40

or actually, anything

john20:04:48

maps are "associative datastructures"

ddellacosta20:04:01

so what’s an object?

john20:04:52

The accessor names are static in an object... it is part of the shape of the thing. A map, rather - its accessor names are not part of the inherent shape of the thing.. Instead, we are associating getter/setter names with vals

john20:04:01

the behavior is different

john20:04:06

If you want to model a dog and you want to make sure that your dog never has wings, turn your dog-map into a dog-object and lock it down. It turns out in Clojure though, we usually don't want or need to lock things down, because we usually push our "dogs don't have wings" constraints to the edges or we allow our downstream users to decide whether dogs should have wings or not.

ddellacosta20:04:43

sorry was off doing work 😂

ddellacosta21:04:01

in any case, I’m worried using the term “object” is muddying the waters here

ddellacosta21:04:03

and not really bringing a lot to the discussion. It sounds rather like you’re objecting to having maps have closed sets of keys, which I think is reasonable for a large number of maps

ddellacosta21:04:36

the reality is that we overload maps in Clojure to represent a lot of different types of values. The main point of contention, I think, is if we lose anything in Clojure by always insisting that maps should be open in every semantic context. I would argue yes, but that’s not the general take in the Clojure community, nor how Rich Hickey and the core team thinks.

john21:04:23

There's a reason clojure isn't an object oriented language... The static constraints on objects create unnecessary debt for our programs down the road. We could go down the road of letting our maps+spec turn into a mess of incompatible map-graphs, like we end up with in OO land...

ddellacosta21:04:30

whether not a “closed map” resembles an “object,” for some definition of “object,” I think can always be answered in the affirmative

ddellacosta21:04:38

yeah see you’re talking about OO again

ddellacosta21:04:07

and I don’t care about OO, because I’ve never encountered a coherent definition of it that made it possible for two people to carry on a meaningful conversation about it

john21:04:22

Because closed maps pretty much are objects... which bring in all the same problems of closed object graphs

ddellacosta21:04:37

yeah--we’re going in circles here. haha

john21:04:05

It's a question of building constraint systems that do not compose

ddellacosta21:04:22

what’s the question?

ddellacosta21:04:07

static typing != OOP

john21:04:26

close maps, or objects, bake into your system constraints, which make your code less composable with other code, if you're not careful

john21:04:20

You end up with a set of objects (or closed maps) that only work with each other and are brittle in other contexts

ddellacosta21:04:50

we don’t always want everything to be able to compose with everything. Sometimes constraining things is the right approach. And again I’m not understanding your equivalency of “closed maps” and “objects,” which you’ve only defined reflexively in terms of…closed maps

ddellacosta21:04:15

in any case, I think I’m at the end of this conversation--thanks for taking the time! 👋

john21:04:36

np, perhaps others can explain it better than me. Good luck!

dominicm18:04:32

How does one structure a business layer around transactions? Such that multiple business actions can fiall together, assuming they use the same store.

noisesmith18:04:57

@dominicm you can use refs for that, or if you can sanely put all the data in one atom, an atom (be sure to use swap! and not reset! etc. in that case)

johnj18:04:59

since you mentioned 'store'

hiredman18:04:41

you are talking about managing side effects right? x y and z have side effects and then all alter some transactional store, and you want their side effects to be delay until you know the transaction that they do the storage operations in has succeeded?

dominicm18:04:05

Oh, I'm in #clojure, not #off-topic. Oops. I apologise. I saw the above and thought I was in #off-topic. Essentially, yes. I was looking at http://graphql.org/learn/authorization/ and http://graphql.org/learn/thinking-in-graphs/#business-logic-layer which got me thinking about this.

dominicm18:04:34

It may well be that business logic should be written in transactional terms, and the mistake is in how I'm thinking about this.

grav19:04:35

Is it possible to get a nicer diff in the output of failing unittests? Something like how Cursive/IntelliJ does it, but monocrome-console-friendly

noisesmith19:04:07

there's the lein difftest plugin, as one example

eskos19:04:32

eftest produces nice diffs as well

eskos19:04:12

with colors and all

benzap20:04:07

I'm curious, what does the #=(+ 2 2) value mean to the clojure reader?

benzap20:04:25

I know that there are a few built-in tagged elements for the edn format, but I can't find anything for the #= tagged element

ghadi20:04:38

the first rule of sharp equals club is ....

😆 8
benzap20:04:37

From what I can tell, it returns the evaluated s-exp, i'm looking to maybe exploit this

benzap20:04:53

For science...

noisesmith20:04:55

it exists but I'd avoid it

schmee20:04:46

heads up:

user=> (read-string "#=(+ 1 1)")
2
user=> (binding [*read-eval* false] (read-string "#=(+ 1 1)"))
RuntimeException EvalReader not allowed when *read-eval* is false.  clojure.lang.Util.runtimeException (Util.java:221)

Alex Miller (Clojure team)20:04:26

#= is read-time eval and is not considered a public feature

Alex Miller (Clojure team)20:04:01

it is a sharp pointy tool :)

devn20:04:15

Suggestions on exception tracking/rollup services that have decent Clojure support?

Alex Miller (Clojure team)20:04:09

Yeller is written in Clojure, maybe it does

devn20:04:26

I think Yeller went the way of the dodo 😞

devn20:04:09

OverOps actually lists Clojure on their site.

benzap20:04:27

@schmee, I have a sandboxed macro, and i'm thinking of having two variations of evaluation (reval #=(+ 1 1)) ;; => '(2) (safe-reval #=(+ 1 1)) ;; ERROR Does this seem like a bad idea? The first would allow injecting clojure code into the sandbox

benzap20:04:18

It does seem kind of hacky though... it might make more sense to just read everything in through the edn reader, and just define a new tag reader for the unsafe case

dpsutton20:04:26

Would you prefer default and unsafe? Rather than default and safe

benzap20:04:47

ah, I agree 100%

benzap20:04:00

unsafe-reval

benzap20:04:07

and have reval be safe

dpsutton20:04:27

That should encourage nice behavior

👍 4
schmee20:04:25

I can’t tell you if it’s a good or a bad idea, that depends entirely on what it’s for and how it will be used 😉

Alex Miller (Clojure team)21:04:18

#=(java.lang.System/exit 1) buh-bye!

benzap21:04:54

From what i've gathered, I can't seem to prevent that tagged element from evaluating

roklenarcic21:04:05

What's #= again?

roklenarcic21:04:33

oh compile time eval

benzap21:04:44

(str '(1 2 #=(+ 1 2))) ;; => "(1 2 3)"

roklenarcic21:04:31

I was thinking, why was 32 elements chosen for vector level size

ghadi21:04:38

read-time, not compile-time -- and it's a bad idea.

roklenarcic21:04:46

Seems to me that 16 makes more sense

schmee21:04:57

why is that?

roklenarcic21:04:16

well, you save on space on a typical function argument vector

roklenarcic21:04:24

few functions have over 16 arguments

noisesmith21:04:26

@roklenarcic it's based on speed tests

noisesmith21:04:40

and argument vectors are a separate type

roklenarcic21:04:57

I know speed has been tested, but I thought about it more from a memory usage perspective

schmee21:04:15

vectors are compacted to the number of elements if they are smaller than 32

schmee21:04:18

so no memory is wasted

noisesmith21:04:11

user=> (#(type %&) 1 2 3) => clojure.lang.ArraySeq

schmee21:04:21

@roklenarcic here’s a good read about persistent vector performance if you are interested: http://www.hypirion.com/musings/persistent-vector-performance

benzap21:04:13

so based on what i've learnt and how i've implemented this, I can only prevent code injection when passing in strings. I think i'm fine with this.

benzap21:04:42

It's a feature!

roklenarcic21:04:08

@schmee I am reading the article and I love how he's surprised that System.arraycopy(arr, 0, newArr, 0, newArr.length); is twice as fast as System.arraycopy(arr, 0, newArr, 0, n);

roklenarcic21:04:23

I guess he's never heard of array index check optimizations

roklenarcic21:04:13

JVM's array bounds check removal optimization is pretty good, being too clever confuses it

markw22:04:12

I know libraries like Ring handle this typically - just curious about the overall design tradeoffs

noisesmith22:04:26

@markw isn't "driver" there a global singleton, enforcing that you can only serve one request at a time?

hiredman22:04:34

global singleton's are terrible, wrapping it in an atom doesn't magically make it not a global singleton

markw22:04:06

Just using it as an example of dealing with maintaining state through requests, not how to efficiently solve the problem of serving multiple concurrent requests

noisesmith22:04:51

that's not an efficiency question though, it's a correctness one - as soon as two requests are concurrent that breaks

hiredman22:04:39

as soon as you want to run two different sessions with different logins it breaks too

markw22:04:15

Maybe this was a bad example... I'm not trying to re-create logic to serve pages... this is just an example of what i've done in the past with simple one-off web scrapers

markw22:04:34

I'm more interested in the persisting of state part

markw22:04:46

needless to say, I assume approach 2 is the correct way

john22:04:57

I think a more explicit explanation of passing a single global (edit: atom) around is in frameworks like re-frame.

ddellacosta22:04:54

at least as far as what @tbaldridge and I were talking about earlier, it was about using something like component (https://github.com/stuartsierra/component) or integrant (https://github.com/weavejester/integrant)

john22:04:05

Your method two, you could instead not def that logged-in-driver and just put that in the threading at the bottom

john22:04:18

and in both situations, you're "passing the whole program state through the program"

john22:04:30

(but only because your examples are so small)

john22:04:26

oh, I misread the middle part of your second method

john22:04:58

my point is that you don't need to explicitly use an atom to structure your functions in such a way that they can deal with a global value. Oftentimes, that means destructuring maps using keys specific to the function, doing your business logic, then updating the passed in map and passing it along, not caring about the rest of the shape of that map.

john23:04:53

but, if you do happen to be using two global atoms to deal with state, and then you go and try passing those values through your fns - regardless of whether they are designed to be generic with respect to global state or not - you're going to run into issues coordinating the state between those two atoms.

john23:04:39

so it's often preferred to use one atom and design fns that are generic with respect to a global value passing through them

john23:04:38

That driver functionality is inherently stateful and stepwise, interacting with an external stateful system, so that whole situation is going to be fairly mutation heavy

john23:04:49

Sorry, I think I conflated two different patterns earlier. The one global atom thing is helpful for not having to coordinate state between different points. The "one global value, passed through" thing is more about structuring functions in a generic way. And really that has more to do with specific kinds of architectures. If you're writing a library for manipulating specific things - stuff at that doesn't deal with the flow of values through a program - your fn signatures will be more specific to those domains.

markw23:04:06

@john case by case i suppose then... I just have a dirty feeling whenever I use globals now, but sometimes it seems natural. Probably just old habits

john23:04:24

I don't think you're going to escape it with a useful webdriver lib

john23:04:35

That's actually repl useful

john23:04:32

Once you've worked out your driver logic in the repl, then you can construct your tests, where you will probably want to structure things more functionally and pass state through functions sequentially