Fork me on GitHub
#clojure
<
2023-02-21
>
pez08:02:05

I am trying to articulate in what sense Clojure/ClojureScript allows for interactive programming in ways that JavaScript does not. Noticing that I can't explain it easily and thinking it is me lacking understanding. I know that JavaScript dev tools don't utilize the dynamic nature of JS, but I don't see clearly where the language flat out disallows interactive programming. Can I get some help understanding this better?

thheller08:02:10

I'd say a huge difference is that you can manipulate the whole "world" from the REPL, easily reach into different namespaces and such

thheller08:02:31

in JS everything is usually isolated into "modules" which you can barely look into or modify

2
thheller08:02:07

plus everything is data first, and not encapsulated into a billion different classes/types

2
thheller08:02:07

its not so much the language, you can do all that in JS too. just the structures commonly used make it difficult

magnars08:02:41

JavaScript is often comprised of opaque object hierarchies, and state hidden in function closures, leaving large parts of the system out of reach from the console.

magnars08:02:18

While ClojureScript processes will usually work with data, allowing you to inspect it on the run.

p-himik08:02:51

Another, albeit minor, point is that s-exps make it easy to evaluate arbitrary parts of your code in arbitrary contexts, just to experiment - pretty much all editors support it. With JS, the editor has to be quite advanced to allow you to easily select just the part you need without constant kb<-> mouse switching.

pez08:02:16

Thanks all for this help! 🙏 I've been watching some Smalltalk demos and seen how amazingly interactive that environment is, and also noticed that it is clunky to select which pieces to update, since the code is not structural.

pez09:02:45

Seems like if I structure my JS code for it, and center it around more plain data, I can give myself a quite interactive programming experience? But that the module encapsulation might still be hard to compensate for?

borkdude09:02:17

What also helps is that CLJS is expression/value oriented whereas JS is statement oriented

borkdude09:02:12

I think that's the point p-himik already made, sorry I missed that

pez09:02:09

I think those are two different aspects. @U2FRKM4TW's (and my Smalltalk comment) was about the text and how it's easy for the editor to figure out where the expressions are. Yours is more on wether it is interesting enough to evaluate a piece of code. 😃

pez09:02:23

Smalltalk is interesting here, I think. I haven't dabbled in it, but it sure looks very imperative and PLOP:y. Yet it is no doubt very much in the category of interactive programming, in'it?

kwladyka09:02:14

As I quite abstractive input I can say: A few years ago I was trying all kind of TypeScript, JS vue, reacts etc. tools and it was frustrating experience comparing to re-frame. It was because cljs has atom, REPL and code was just more simpler and readable. So far Single Page App I would choose CLJS. But for short simple functionality javascript, because web browser give enough good tooling to do the job and it is much easier vs preparing / maintenance / compiling / deploying CLJS to use in smaller use case. Disclaimer: I don’t code in CLJS / JS too often.

pez09:02:42

> vs preparing / maintenance / compiling / deploying CLJS to use in smaller use case. Here #C034FQN490E can help for some use cases, I think.

😮 2
p-himik10:02:22

Oh yeah, borkdude's point on everything being an expression is a good one. I often find myself becoming frustrated at most things being statements in JS. But I imagine a JS dev would invariably say "what's a big deal here".

phill10:02:48

Is Perlis' aphorism "It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures" also a factor?

👍 2
skylize05:02:51

Definitely seems like this conversation is more about bashing JS codebases than illuminating anything meaningful about the language itself. > I'd say a huge difference is that you can manipulate the whole "world" from the REPL, easily reach into different namespaces and such @U05224H0W Namespacing is a proactive choice, which the language has slowly added more support for. Remember that browsers spent 20-30 years with everything being global. One of the greatest lasting influences of JQuery was its popularization the IFFE so we could, at least sort-of, get away from that. (Not implying this is a good idea for anything more than scratch code.) If your code embraces the everything-is-global model, and you open up Chrome Dev Tools, you definitely have access to the whole world. > in JS everything is usually isolated into "modules" which you can barely look into or modify @U05224H0W See above. Plus, it's usually a good idea to pretend that namespace = module, and not go reaching into things that are not intentionally exposed. > plus everything is data first, and not encapsulated into a billion different classes/types @U05224H0W Thanks TypeScript, helped make OOP style JS even more popular at the very moment people started discovering you can write functional JavaScript. 😞 Personally I would never write a Class where a function would do. But OOP people will do OOP, no matter the language. Most people learn programming using an OOP language, then blindly carry that mindset wherever they go. JavaScript puts up no barriers to that. If you want to write data-first. There's also no barriers to that. Just put your data in a plain Object, instead of a bloated class and start transforming. > JavaScript is often comprised of opaque object hierarchies, and state hidden in function closures, leaving large parts of the system out of reach from the console. @U07FCNURX These concerns point to design decisions. JavaScript does not encourage specific hierarchies nor hiding of state. It simply allows such things if that's what you want. > While ClojureScript processes will usually work with data, allowing you to inspect it on the run. Again, a design decision. Just use the key-value pairs of a plain Object instead of closing over state or building fancy classes. @U07FCNURX > s-exps make it easy to evaluate arbitrary parts of your code in arbitrary contexts, just to experiment - pretty much all editors support it. @U2FRKM4TW Not clear to me what you are describing here. But on the surface, sounds like it could be simply a tooling issue that nobody has taken the time to solve? > What also helps is that CLJS is expression/value oriented whereas JS is statement oriented @U04V15CAJ JS has both expressions and statements. To some extent you can consciously choose to lean more on expressions when available. The clearest example of this is if/`else` vs the conditional operator.

// statement
if (pred) do-a
else do-b

// expression
pred ? a
     : b
You can also wrap a statement inside a function with a return value, so the rest of you code can use the function call as an expression. > A few years ago I was trying all kind of TypeScript, JS vue, reacts etc. tools and it was frustrating experience comparing to re-frame. @U0WL6FA77 Comparing frameworks sounds like a different question to me. > "It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures" JavaScript has exactly one data structure, an Object. (Even primitives inherit from Object) You can choose to extend on that Object to make 10 different data structures. Or you can choose to embrace the single data structure and write 100 functions to operate on it. --- Despite unfortunate choice to use C-like syntax, the primary inspiration for JavaScript was actually Scheme. So there really is a lot of lispiness available if you choose to use it. I think you could quickly forget you were writing in JS if you had an implementation of Clojure's immutable data structures, a Babel plugin to provide a literal syntax for those data structures, Ramda, and a commitment to writing data-driven functional code.

magnars07:02:55

You cannot separate a language from its culture, and expect to have a meaningful discussion. What is possible to do with a language is less interesting than what is easy, taught, used in examples, best practice, normal. While your points are wholly valid, I think that your frustration with the arguments presented here stems from you having a different discussion than the rest. In a vacuum, yes, you could do all those things with JavaScript. But in practice, that's not what happens, unless you and everyone on your team are very diligently trying to make it happen.

thheller07:02:17

second that. CLJS after all emits regular JS code, so you could write all of it in JS directly yourself. clo*s*ure js is also a much older style than commonjs or esm, but it has reasons it has never caught on and become any sort of standard. it is clunky and tedious to write, just like jquery IIFE-style was. so, all the standards went a different route, which in turn meant down locking down the everything-is-global to something much less dynamic/inspectable/moldable

pez08:02:19

There are immutable data structures on its way to JavaScript, named Records and Tuples, with a literal syntax of #{}, and #[] , respectively (iirc). There are babel configs to start using them today.

metal 2
pez08:02:58

A thing I often do, and I think many Clojurians do as well, is to redefine code in Clojure core and in libraries, just to inspect how they work, or what my data looks like when it reaches some place in there. Since the culture is what it is, this practice would be very hard to mimic in the JS ecosystem. (Addressing my original question on what makes Interactive Programming harder in JS than in Clojure.)

skylize15:02:59

> ... redefine code in Clojure core and in libraries, just to inspect how they work, or what my data looks like when it reaches some place in there. Since the culture is what it is, this practice would be very hard to mimic in the JS ecosystem. I don't follow. What would make it at all difficult to redefine things in JavaScript? There is nothing stopping you from redefining any non-`const` Object you can get your hands on, including built-in functions.

String(5)
// => '5'

String = null
String(5)
// => Uncaught TypeError: String is not a function
The addition of const adds a barrier to outright redefining from scratch of certain things. But even then, you can still mutate the object freely to your hearts content. Even constructing things from Classes would not slow you down. (Unless you use private fields, which I believe is still pretty uncommon, since people got accustomed to it not being there for so long.)
class Foo {
  constructor (){ this.foo = 'foo' }
  sayFoo () { return this.foo }
}
const foo =  new Foo()
foo.sayFoo()
// => 'foo'

foo.foo = 'bar'
foo.sayFoo()
// => 'bar'

p-himik16:02:35

There are a lot of instances where you cannot override some field because its descriptor prevents it. Built-in API also has such objects. > But even then, you can still mutate the object freely to your hearts content.

const doStuff = () => {...}
Here, I'm already screwed, aren't I? Especially given that a defn is accessible from outside and such const at the top level of a module is not. > which I believe is still pretty uncommon Oh it is common, but in TypeScript. And quite a few JS libraries do rely on descriptors to actively prevent a user from overriding something. As mentioned - cultures are different. There's no point in denying that. Just because you can do something in JS in principle doesn't mean that it's a viable approach, unless you never touch the surrounding ecosystem.

skylize20:02:24

Yes. There are definitely huge differences in culture (Hell, JS itself has numerous subgroups with their own vastly different cultures). But talking about cultural differences as if they are the same as something inherent to the language twists the conversation in an unhelpful way. In the context of the original question, it leads to arguments for promoting Clojure that are easy to dismiss, because they ignore the viewpoint of the JS dev, who might think something like "I can already do that. So what?" I am not denying there are clear differences between the languages either. It just seems like most of the points raised were largely about style and custom. (At least until I raised a complaint, and everyone felt immediate need to debate my discussion of actual language & environment features.) Perhaps if we could all do better at looking past the Algol facade of JS to the Scheme underneath it, and what it can do instead of what people do with it now, then we could meet people where they are. Teach people to start writing functional data-driven code in JavaScript. Then they have a good reason to want to switch to Closure, since it offers stronger support for that and just the right amount of hindrance to the old ways. That's how I got here anyway.

phronmophobic20:02:57

> What matters is not just what a programming language makes possible, but what it makes practical and idiomatic. (<https://www.youtube.com/watch?v=VSdnJDO-xdg > |source>) Both js and clj(s) are turing complete. cljs is compiled to javascript. By definition, there's nothing you could do in clj(s) that you couldn't do in js (ignoring some minor technicalities). What makes any language better for interactive programming is the tooling, community, and ecosystem.

phronmophobic20:02:46

> It just seems like most of the points raised were largely about style and custom. Style and culture make big difference. Those aren't the only factors, but they are probably the most important.

skylize20:02:17

From the original question, emphasis added: > I am trying to articulate in what sense Clojure/ClojureScript allows for interactive programming in ways that JavaScript does not. > Certainly culture and style play a role here, e.g. what libraries might already exist, or what APIs will those libraries present. But the premise of comparing what the languages allow for strongly implies culture and style should be treated as secondary to available features of the language and the environments it is run in.

phronmophobic21:02:27

The answer to "what can you do in clj(s) that you can't do is js?" is nothing, which is not very interesting. I think that's why most of the discussion was around "what in the clj(s) ecosystem makes interactive programming more practical and idiomatic?"

skylize21:02:05

> What makes any language better for interactive programming is the tooling, community, and ecosystem. > I agree that tooling is a huge factor. It's the one place where my claim that culture is tangential to the question completely breaks down. How much more productive would the REPL really make us if we didn't have Calva and Cider and Cursive? If there was an editor plugin that could inject itself into a browser runtime and run arbitrary bits of JS from your source code, how much would that close the gap?

phronmophobic21:02:21

Many of the comments above give good answers to these questions.

pez22:02:26

I took the first batch of answers as ”There is not much with JS as a language that makes it unsuitable for interactive programming, but rather the way it is used.” For me that was what I was looking for, so that I don't go around claiming that there is some fundamental blocker there at the language level.

👍 2
thumbnail09:02:53

I’m getting a reflection warning when calling a java method which expects a Map<String, ?> as it’s only argument. I’m calling it like this:

(when-let [span (.activeSpan (GlobalTracer/get))]
  (doto span
    (.setTag Tags/ERROR true)
    (.log {Fields/ERROR_OBJECT ex})))
The error I get is call to method log on io.opentracing.Span can't be resolved (argument types: clojure.lang.IPersistentMap) 🧵

thumbnail09:02:51

I figured that the compiler ‘promises’ that it’ll read { … } as a IPersistentMap (which is not a j.u.Map, but on runtime it’s a PersistentArrayMap (which is a APersistentMap, which is actually a j.u.Map).

thumbnail09:02:41

I tried typehinting the map with java.util.Map, clojure.lang.APersistentMap and clojure.lang.PersistentArrayMap`), to no avail

delaguardo09:02:00

(when-let [span (.activeSpan (GlobalTracer/get))]
  (let [^java.util.Map ex-map {Fields/ERROR_OBJECT ex}]
    (doto span
      (.setTag Tags/ERROR true)
      (.log ex-map))))
try this. the argument for log method should have type hint in the let block

thumbnail09:02:26

Ah, that seemed to work. For my understanding; is this because in my previous attempt (`^java.util.Map {Fields/ERROR_OBJECT ex}`) the meta-data would live on the map instead on the var ?

delaguardo09:02:03

I don't know for sure, but the documentation says "Type hints are https://clojure.org/reference/metadata#_metadata_reader_macros placed on symbols or expressions that are consumed by the compiler. They can be placed on function parameters, let-bound names, var names (when defined), and expressions" In the case of ^typeHint {,,,} the tag is added to the value because {,,,} is a literal, not expression. https://clojure.org/reference/java_interop#typehints

2
thumbnail10:02:11

Thanks! that does explain the behaviour i’m seeing 🙂.

Alex Miller (Clojure team)12:02:59

There is a ticket somewhere about this area and the inability to type hint literals here

thumbnail12:02:07

🙌:skin-tone-2: that’s helpful to know, thanks!

Panel10:02:18

(let [{a :a} '({:a 1})
      {c :c} '({:c 1} {:c 1})
      {b :b} [{:b 1}]]
  [a b c]);; => [1 nil nil]
I did not expect that, anyone know the reason for de-structuring to work this way for single element list ?

Panel10:02:54

(let [{a :a} (list {:a 1})]
  a)
This will fail bellow clj 1.11 so yes it looks related.

Rachel Westmacott16:02:48

I'm hitting an issue trying to proxy an java.io.OutputStream - the abstract write method I'm trying to interfere with has three overloaded versions in the java, two with the same arity. I think that's what's confusing it. I have a proxy with the correct arity overload, but I keep getting an UnsupportedOperationException.

Rachel Westmacott16:02:04

worked it out! I was trying to chain calls through proxy-super - so my proxy-mappings weren't there.

Lucas Jordan19:02:18

Good afternoon. Does anyone know a way to stop clj-http from adding a Content-Length to the header for a PUT request?

hiredman19:02:10

http spec says

A user agent SHOULD send Content-Length in a request when the method defines a meaning for enclosed content and it is not sending Transfer-Encoding. For example, a user agent normally sends Content-Length in a POST request even when the value is 0 (indicating empty content). A user agent SHOULD NOT send a Content-Length header field when the request message does not contain content and the method semantics do not anticipate such data.
so you should send Content-Length. My guess is you don't want to send content length because you have a stream and you don't want to count the whole thing. In which case you should look at Transfer-Encoding

hiredman19:02:20

(I don't know to what degree clj-http supports Transfer-Encoding for client send bodies)

Lucas Jordan19:02:10

Thank you for responding. Nothing to do with streams. I am trying to debug aws-sig4 signing with clj-http, The header is added after the “middleware” code is called, so it is not being included in the signature. Im not even sure it should be included, but I am trying to remove variables

hiredman19:02:12

we have some code that makes a presigned url for a GET, and I think the only header it includes in the signature is Host, but maybe PUT is different

Lucas Jordan19:02:29

it must be. The github projects about this work out of the box for GET /_cat/indices, but don’t for a PUT

hiredman19:02:13

ah, the signature generated says that host is the only signed header

hiredman19:02:11

that ends up as part of the query string on the signed url

Lucas Jordan19:02:38

I turned the parts and pieces of the existing clojure implementations into something that works.

Lucas Jordan19:02:46

Actually, let me ask a much better question (for me)…. does anyone know how to enable aws-sig4 signing with clj-http. The github project ‘org.zarkone/aws-sig4’ works sometimes, but not all times (having trouble with a PUT)

lukasz19:02:09

You can pull in the Java SDK from AWS, IIRC it has a signer class... but you might better off just using the Java SDK directly (prepare to .build a lot) or cognitect/aws-api if it's not IO intensive stuff (e.g. uploading big files to S3)

Lucas Jordan19:02:11

I happily use the cognitect stuff, great library. But I am trying to access OpenSearch, which requires the same signing as AWS. AWS provides an SDK for open search but it is super Javay, and not compatible with my existing clj-http bases ElasticSearch code.

lukasz19:02:11

Ah yeah, that, I solved it differently - ES (ahem, OpenSearch) in a private VPC subnet + allowing traffic only from another semi-private subnet.

lukasz19:02:58

I couldn't make the signature stuff work because it signs headers, query parameters and body - and that's just very hard to wrangle if you're using a different HTTP client

Lucas Jordan19:02:07

You know, I might need to do that… but the Serverless flavor requires authentication…. so I would have to switch to the ‘hosted’ version

lukasz19:02:39

Right, I wasn't aware of that. Then again, you might as well use the OpenSearch client, it looks like it shipped with AWS auth not long ago: https://opensearch.org/blog/aws-sigv4-support-for-clients/

Lucas Jordan19:02:19

I might be willing to do that, if I can get the SDK to talk to a local docker container running OpenSearch (is there such a thing?).

lukasz19:02:39

Yes, AWS provides opensearch images for local use

Lucas Jordan19:02:41

maybe I just look their source code and see what they are doing 🙂

Vipin05:02:26

Hi Lucas, we have been using https://github.com/joseferben/clj-aws-sign to sign our opensearch requests.

lukasz15:02:32

TIL, I have no idea how I missed this O_O

Lucas Jordan20:02:39

Totally missed that one too 🙂 Thanks for sharing, I just put my solution on github, and it looks a lot like this one…. well at least I learned something 🙂

Nundrum21:02:20

Is there a good way to think about parsing nested data? In this case, iCal. I have something working, but it looks very imperative and sloppy.

escherize21:02:18

There are a couple approaches to functional parsing. There’s the popular instaparse which will generate a parser from an ebnf.

Nundrum21:02:39

It looks something like

(let [vcal (atom {})
      vevent (atom {})
      valarm (atom {})]
  (doseq [line lines]
    ....a bunch of imperative looking code that stuffs data into those atoms ...)

escherize21:02:00

I have been toying with porting a rust parser combinator library called nom to clojure.

escherize21:02:17

but if you want to get into parser combinators now, there is the/parseatron.

👍 2
Nundrum21:02:25

Instaparse might work. I can't find a good description of the iCal format though. The RFC is overwhleming for what I'm trying to do.

Alex Miller (Clojure team)21:02:45

there's a Java lib to parse this stuff and a Clojure wrapper

Alex Miller (Clojure team)21:02:34

I just hacked some stuff up with it recently, looks like I just used the Java lib directly - org.mnode.ical4j/ical4j

2
Nundrum21:02:40

I looked for Clojure libs, but didn't find any that parsed.

Nundrum21:02:12

That one only seems to create iCal?

Alex Miller (Clojure team)21:02:40

yeah, guess not. I just used the Java lib directly and went from there

Nundrum21:02:03

Thanks, I'll check those things out!

Nundrum02:02:56

I thought all was going well with ical4j until I try to get actual start/end times. Here's the same date object raw and after calling .toString

:dtstart #inst "2023-02-22T20:00:00.000-00:00", :tz "20230222T150000"
You can see the instant is UTC, but the string has been converted to my TZ. :man-facepalming:

Alex Miller (Clojure team)02:02:26

Well you have to be somewhat careful with this - it is the correct instant in time. How you view it is up to how it is formatted for printing. .toString is doing that formatting and probably taking your time zone into account

Alex Miller (Clojure team)02:02:18

The JDK has a whole library (actually several) for formatting instants to strings

Nundrum03:02:38

I can't find any way to get ical4j to give me the timezone-adjusted version.

Alex Miller (Clojure team)04:02:57

I don’t think you will, you have the right data - you just need to format it for wherever it’s going

Nundrum00:02:11

Finally, an explanation: > The java.util.Date class has no time zone assigned†, yet it's toString implementation confusingly applies the JVM's current default time zone.

Jakub Šťastný23:02:48

Hey guys, I was wondering what's the situation with the spec 1 vs. spec 2 nowadays? Could someone sum it up what's the status of these two, so I can decide which one to use? Cheers.

seancorfield23:02:51

Spec 1 is production quality (even tho' it's marked "alpha"). We've been using it heavily for years. Spec 2 is still experimental and a work-in-progress and should not be used in production code.

seancorfield23:02:59

If you just want to experiment with the concepts in Spec 2, feel free. At some point, it will evolve into a non-alpha version but it has a way to go yet...

escherize00:02:01

If you are looking to pick between data contract libraries, you can also look into https://github.com/metosin/malli.

2
👍 4