This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2019-06-12
Channels
- # announcements (2)
- # aws-lambda (2)
- # beginners (402)
- # berlin (2)
- # calva (21)
- # cider (8)
- # clj-kondo (31)
- # cljdoc (1)
- # cljsrn (42)
- # clojure (43)
- # clojure-berlin (2)
- # clojure-dev (21)
- # clojure-europe (4)
- # clojure-hamburg (1)
- # clojure-italy (37)
- # clojure-nl (7)
- # clojure-spec (50)
- # clojure-uk (121)
- # clojurescript (46)
- # component (49)
- # data-science (1)
- # datomic (60)
- # emacs (29)
- # fulcro (43)
- # hoplon (5)
- # jackdaw (7)
- # joker (19)
- # luminus (5)
- # off-topic (28)
- # om (2)
- # re-frame (27)
- # reitit (7)
- # remote-jobs (15)
- # rewrite-clj (17)
- # shadow-cljs (95)
- # spacemacs (34)
- # sql (9)
- # tools-deps (17)
- # xtdb (70)
gotcha so no need for jenkins as well to deploy
oof, well, that is a whole other thing, if you are building a ci box, for whatever reason our "devops" team refused to take care of our ci box at the last job so it wasn't managed using chef, just a shell script
yea i'm in the process of putting our clojure app on a server so looking for best practices since its a new job and the previous place i worked we only used the jar method you said but the devops want to use jenkins and chef
nvm I was thinking it the wrong way just have jenkins do the lein test, uberjar and deploy the uberjar into the server
idk why I was thinking I would need lein in the server for the chef recipe to build since the chef recipe is being used just to automate building the server
my generator isn't working, I'm trying to run this line: https://github.com/cognitect-labs/aws-api/blob/master/examples/s3_examples.clj#L34
clj.dal=>
(s/gen (aws/response-spec-key secretsmanager :GetSecretValue))
Execution error (FileNotFoundException) at clj.dal/eval18601 (REPL:1).
Could not locate clojure/test/check/generators__init.class, clojure/test/check/generators.clj or clojure/test/check/generators.cljc on classpath.
class java.io.FileNotFoundException
am I missing an import?
ah, thanks!
@U0NCTKEV8: that worked!
... but the stuff generated isn't super helpful
thanks though
I have a map: '({:a 1 :b2})
collection .. I want to take the first map and get all the keys ... but what I'm tryin' ain't workin'
my uuid-string
function looks identical to this one ... does this mean I need to add the APACHE license to my project? https://github.com/cognitect-labs/aws-api/blob/master/src/cognitect/aws/util.clj#L250-L253
@johnjelinek I would expect everyone who needs a UUID string has written that exact same function independently (so, no, you only need to keep the license when you copy in code from an OSS project).
so ... it won't look like I copied uuid-string
if I rename the function and then I don't have to adopt the license?
You pretty much can't write that function any other way.
For example, I've never looked at that code and here's what's in our (proprietary) code at work
(defn uuid
"Return a standard Type 4 (random) UUID."
^java.util.UUID []
(UUID/randomUUID))
(defn uuid-as-string ^String []
(str (uuid)))
what gives? my :gen-class
from yesterday doesn't work today when I AOT?
clojure -A:aot
Exception in thread "main" Unexpected error macroexpanding clojure.core/gen-class at (clj/core.clj:1:1).
at clojure.lang.Compiler.macroexpand1(Compiler.java:7018)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:7092)
at clojure.lang.Compiler.analyze(Compiler.java:6789)
at clojure.lang.Compiler.analyze(Compiler.java:6745)
at clojure.lang.Compiler$BodyExpr$Parser.parse(Compiler.java:6118)
at clojure.lang.Compiler$TryExpr$Parser.parse(Compiler.java:2314)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:7106)
at clojure.lang.Compiler.analyze(Compiler.java:6789)
at clojure.lang.Compiler.analyze(Compiler.java:6745)
at clojure.lang.Compiler$BodyExpr$Parser.parse(Compiler.java:6120)
at clojure.lang.Compiler$FnMethod.parse(Compiler.java:5467)
at clojure.lang.Compiler$FnExpr.parse(Compiler.java:4029)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:7104)
at clojure.lang.Compiler.analyze(Compiler.java:6789)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:7094)
at clojure.lang.Compiler.analyze(Compiler.java:6789)
at clojure.lang.Compiler.analyze(Compiler.java:6745)
at clojure.lang.Compiler$InvokeExpr.parse(Compiler.java:3820)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:7108)
at clojure.lang.Compiler.analyze(Compiler.java:6789)
at clojure.lang.Compiler.analyze(Compiler.java:6745)
at clojure.lang.Compiler.compile1(Compiler.java:7725)
at ...
Caused by: java.lang.ClassNotFoundException: com.amazonaws.services.lambda.runtime.RequestStreamHandler
at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:466)
I've confirmed the dep is still there:
{:deps {com.amazonaws/aws-lambda-java-events {:mvn/version "2.2.6"}}}
and I see the classes that sit adjacent to the interface in the repl
code:
(ns clj.core
(:gen-class
:implements [com.amazonaws.services.lambda.runtime.RequestStreamHandler])
(:require [clojure.data.json :as json]
[clojure.string :as s]
[ :as io]
[clojure.pprint :refer [pprint]]))
(defn handle-event [event]
(pprint event)
{:who-done-it (get-in event [:records 0 :request-parameters :source-ip-address])
:bucket-owner (get-in event [:records 0 :s3 :bucket :owner-identity :principal-id])})
(defn- key->keyword [key-string]
(-> key-string
(s/replace #"([a-z])([A-Z])" "$1-$2")
(s/replace #"([A-Z]+)([A-Z])" "$1-$2")
(s/lower-case)
(keyword)))
(defn -handleRequest [this is os context]
(let [w (io/writer os)]
(-> (json/read (io/reader is) :key-fn key->keyword)
(handle-event)
(json/write w))
(.flush w)))
here's my deps.edn
:
{:deps {org.clojure/clojure {:mvn/version "1.10.0"}
org.clojure/data.json {:mvn/version "0.2.6"}
com.amazonaws/aws-lambda-java-events {:mvn/version "2.2.6"}
com.cognitect.aws/api {:mvn/version "0.8.305"}
com.cognitect.aws/endpoints {:mvn/version "1.1.11.565"}
com.cognitect.aws/secretsmanager {:mvn/version "707.2.405.0"}
http-kit {:mvn/version "2.3.0"}
; org.clojure/test.check {:mvn/version "0.10.0-alpha4"}
}
:aliases {:pack {:extra-deps {pack/pack.alpha {:git/url ""
:sha "81b9e47d992b17aa3e3af1a47aed1f0287ebe9b8"}}
:main-opts ["-m"]}
:aot {:extra-paths ["classes"]
:main-opts ["-e" "(compile,'clj.core)"]}}}
# tree src/
src/
āāā clj
āāā core.clj
āāā dal.clj
āāā util.clj
maybe it doesn't like the new files I introduced into the namespace
nope, took those files out and no change
You're missing at least one dep...
(! 513)-> clj -Sdeps '{:deps {com.amazonaws/aws-lambda-java-events {:mvn/version "2.2.6"} com.amazonaws/aws-lambda-java-core {:mvn/version "1.2.0"}}}'
Downloading: com/amazonaws/aws-lambda-java-core/1.2.0/aws-lambda-java-core-1.2.0.pom from
Downloading: com/amazonaws/aws-lambda-java-core/1.2.0/aws-lambda-java-core-1.2.0.jar from
Clojure 1.10.1
user=> (import 'com.amazonaws.services.lambda.runtime.RequestStreamHandler )
com.amazonaws.services.lambda.runtime.RequestStreamHandler
user=>
You need aws-lambda-java-core
for RequestStreamHandler
(I tried with just aws-lambda-java-events
and got the same error you saw)
doh! thanks
I'm not sure how I would've figured that out
@seancorfield: thanks for your help. Worked great
I searched for that full class name. Looked in the Github repo where it showed up. That listed for libraries to depend on. Events, Core, and two logging adapter libraries.
@deleted-user CSP -- Communicating Sequential Processes -- is the background to read for this.
Yeah, Go comes from CSP as well.
Yeah, I learned it via Occam in the early 80's.
A lot of solid production code is built on core.async
...
I wouldn't worry too much about futures, given what you can already do š
BEAM is an interesting environment. I took a workshop back at an FP conference years ago and was impressed by what Erlang can do. But I've been on the JVM for... 22 years at this point... so I both love it and hate it and I've learned to live with its limitations in order to take advantage of everything else.
I've never felt the loss of that -- in 22 years on the JVM. Make of that what you will.
But it later moved to OS threads, because they are better for true parallelism and thus performance
OK. I mean, CSP was the fundamental stuff I studied at uni in my postgrad days back in the very early 80's, so I don't think I was missing the point of it š
Ya, I meant Nate. Also, I had misunderstood the statement. So disregard. I thought we were wondering why the channel parks on put/take
Okay, fair enough. Context is everything š
Its how you synchronize producers and consumers and coordinate the concurrent processes
From my understanding, you have to go to great lengths of additional complexity in Actor models to perform such coordination
Ya, unfortunately, Clojure can't prevent the actual thread running the process from being accidentally blocked
If so, it would also block one of the threads in its pool. Meaning there is one less thread to run the processes on.
So unless Project Loom can magically rewrite calls to Blocking IO or other blocking operations, it would suffer the same fate.
Erlang has its own IO implementations so that you can do IO without blocking the underlying OS thread.
When using core.async go block, you just need to make sure you're only using non blocking IO
That, I donāt remember. But the videos that they have put out so far are really interesting and full of the nitty gritty.
> The implementation of the networking APIs in the http://java.net and java.nio.channels packages have as been updated so that fibers doing blocking I/O operations park, rather than block in a system call, when a socket is not ready for I/O. When a socket is not ready for I/O it is registered with a background multiplexer thread. The fiber is then unpacked when the socket is ready for I/O. These same blocking I/O are also updated to support cancellation. If a fiber is cancelled while in a blocking I/O operation then it will abort with an IOException.
But I guess ya, they can just update all IO code and have a if called by Fiber.. then park instead of blocking
Itās a huge undertaking but they seem to be chipping at it and making good progress.
Pardon?
Regarding blocking IO, isnāt it true that most file access IO is blocking at the OS level and itās hard to overcome that? Thatās what Iāve been hearing for years. So if you make socket calls parking you get huge practical value.
Yay for Windows? š
You can actually hand off a callback to the OS and register a user thread with it. And on interrupt from the peripheral, it'll switch to your registered user thread, hand of execution to your callback and pass you the IO result
Does this low-level stuff really matter tho' in the grand scale of things? (serious question)
But it provides an API called like Asyncio which abstract it away, but it's still multiplexed under the hood
I mean, we build production scale stuff on Windows, Linux... without worrying about this...?
But how many people need to do that?
I think the vast majority "don't care". Regular JVM stuff is "fast enough", right?
I worry that we go down rabbit holes in the name of (unnecessary) performance.
That's why I'm with you, for all practical purpose, core.async solves all the use cases I have. When I need it, I'm just careful not to block
Google, Facebook, Twitter... they care but most of us don't.
(and I'm not even sure they care really, in all of these cases)
I doubt they even care. All this is about scaling single host concurrency, and for most things, especially big tech, adding a few more hosts is way cheaper then the maintenance cost of more complex software
I see some people's "obsession" with async I/O and I'm like "really?". I get asked about it with clojure.java.jdbc
and next.jdbc
for example...
They often have the assumption it's faster.. I don't really get it, especially not with Java. You can easily do multiple things in parallel in different threads if you need to.
Yeah, and in the real world they'd be surprised how often it isn't faster to do stuff in parallel. Tuning is hard.
True.. recently did some benchmarks with rust where the same obsession lives. Because they decoupled getting data from Kafka, and processing it async, it was putting unnecessary stress on Kafka, making the performance worse..
Async JDBC is horribly non-portable. Why would anyone inflict that pain upon themselves?
I have no experience with it. Is that just an issue with async JDBC kinda just sucking? Or the async-ness makes it inherently less portable?
Only some DBs have drivers that support async.
(and each of them are different)
So when folks ask me to support async in clojure.java.jdbc
or next.jdbc
I'm like, yeah, send me a PR for portable async š
PostgreSQL has a fairly well-established async driver I believe... and some (many?) PG users tend to assume the world is like PG š
Hum... I mean. I can imagine how in 10 years, everything will just have moved to async IO, and it'll just be the new io standard
Yup, and in ten years, I'll be happy to release jdbc.async
š
And you could just therefore give it a Clojure fn which puts to the channel when called back
Could it park and like... block the return, hum. I guess it could. So you'd basically be doing a blocking call, but the thread is a lightweight one. Ya that's nice
But, really, these are implementation details in the grand scale of things...
async io just sounds like another horrible trap for hard to reproduce, non deterministic, bugs
Ya, I mean, Java can already scale. So all it could further do is save you a few extra hosts
If you look at this from a higher level, you can't tell how a lot of the low-level stuff actually operates, and that's a good thing.
I think that would be the defining factor for Loom as well. If its invisible, then great. You'll save some memory and shave of a few milliseconds in context switches without having to do anything. But if it's like a whole buy in, redesign everything, etc. Then meh
I wonder how many people use go blocks in Clojure (not ClojureScript). I've used thread, but never had a real need for go.
We have a few things that are very heavy on async and rely on core.async
But out of 80k+ lines it's not much
Cool! I guess I did maybe have a use case for it recently. Had to process a millions+ csv file and make a request to an API for each row.
And that's probably not a good use case for it
Ah OK
Frankly an executor is better
The fleet maybe handles a few hundreds max concurrent, of which other prod systems call into. That's the thing with real life, you always need throttling, back offs, circuit breakers, etc.
Seriously, Executors are great. There's one for every use case too. That with just futures take you really far already
I'm always sad when beginners just jump on core.async. And don't start with future, executor and pmap
Where do I start from while trying to learn these?
http://clojure-doc.org/articles/language/concurrency_and_parallelism.html#java-util-concurrent
I think you meant java.util.concurrent
stuff should be use prior to using core.async
.
I've always thought this was good: https://purelyfunctional.tv/guide/clojure-concurrency/
I mean that core.async is a pretty complicated tool, which shines only for particular advanced scenarios
And yes, learning future, promise with normal threads, agents, pmap and the java.uitl.concurrent stuff should happen first in my opinion
@U0K064KQV Thanks. What are examples of particular advanced cases?
There's two scenarios in my mind. The first one is when you want to wait on a number of things to complete, which could complete in any order, and want to process them as they complete.
But in either case, it can be possible to do it with futures and executors as well. So you need to first make sure that doing it with those would either not be as fast as you want, like limit the concurrency you actually need (very rare that you need a lot of concurrency), or because core.async would actually result in simpler code for the use case.
That's why some people say: "use core.async when you want to trade maintainability for extreme concurrency"
And does it shine in browser? There's Promesa
library for promises too. Which works with both Clojure and ClojureScript.
In browser and NodeJs, you have a lot less options for concurrency, so core.async is more helpful more often there
That said, I'm not an expert on ClojureScript and don't use it actively. So I'm the wrong person to ask for it
But I think in general core.async is more useful in it, and it's also less tricky to use, because of the single threaded nature of JS and the fact that most IO is non-blocking
Question arising in my mind is where one should choose Promesa vs core.async. Kind of best scenarios and tradeoffs in ClojureScript and browser for both libraries.
A promise is like a variable, its single element only. Where a channel is like a sequence, it's a stream of elements
And promise shines when you have a few one off tasks to run concurrently which only return 1 thing and are done
In Java though, executors can also be used for processing streams concurrently. And are often a simpler tool for that. But there is no such thing in ClojureScript. Similarly, core.async in ClojureScript has less gotchas.
Great. @U0K064KQV Thanks. Now, I think it's better to start from Promesa and upgrade to core.async if needed.
@deleted-user By the way, you should also have a look at https://clojureverse.org/t/cloroutine-v1/3300 At this point, you might know more about the whole space of coroutines vs fibers vs green threads vs cps, etc. But apparently core.async aren't full generalized coroutines and cloroutine implements a full genral coroutine in Clojure.
@deleted-user And for completeness since this is the beginner channel. In ClojureScript land, when targeting NodeJS, you don't have to worry about accidentally blocking the go-blocks, since Node only supports async non-blocking IO. Some people do prefer NodeJS over the JVM as their backend runtime and use ClojureScript for that reason instead.
Hi , My map is like {2004 [{:id 1, :name water, :attribute_name Color, :attribute_value blue} ,{:id 2, :name water, :attribute_name formula, :attribute_value H2O} ,{:id 3, :name water, :attribute_name State, :attribute_value liquid}] } i want to create following map like this {2004 [{:Color blue, :formula H2O ,:State liquid}]} Please guys help me
towards something like this?
(->>
[{:id 1, :name "water", :attribute_name "Color", :attribute_value "blue"}
,{:id 2, :name "water", :attribute_name "formula", :attribute_value "H2O"}
,{:id 3, :name "water", :attribute_name "State", :attribute_value "liquid"}]
(map #(hash-map (:attribute_name %) (:attribute_value %)))
(into {}))
=> {"Color" "blue", "formula" "H2O", "State" "liquid"}
It worked .... thanks in ton š
Hello, has anyone used Cucumber with Clojure? If so, which command do I have to use to run a tagged Test Case?
Hey, does anyone have any resources on how to include, import, and wrap homegrown Java classes in Clojure projects? I'm going to start working on wrapping an object model that a colleague wrote in Clojure. The classes are pretty involved, they're used to model CME market data for live feeds, so I'm looking for some resources on how to import the jar from the Java project in my Clojure project and how to properly implement a wrapper so that we can work with it more idiomatically on the Clojure side. Any resources would be a huge help! Thank you! Edit: I'm reading through the following things for a primer as well 1. https://clojure.org/reference/java_interop 2. http://clojure-doc.org/articles/language/interop.html 3. https://www.braveclojure.com/java/ 4. https://www.reddit.com/r/Clojure/comments/aqy19n/questions_about_wrapping_a_java_library/
If you can get the jar in the class path (differs based on your build tool, I guess?) then youāre off to the races, everything should work with the Java interop syntax.
As for more idiomatic, most Clojure apis would like to deal with maps, collections and seqs - so are if you can write some wrappers that do this kind of translation.
I actually prefer the nested one showed above but almost every clojure I see in the wild is obsessed with -> ->>
the straightforward translation via arrows is (-> x (bar y) (:bar) (->> (foo value)))
or alternately (->> y (bar x) (:bar) (foo value))
- it really depends on what x and y are and what they mean
Speaking for myself, ->
or ->>
in the context of some other threading operator gives me the heebie jeebies.
the other threading macros are designed to be nested under ->
it's specific to ->
, because all the other arrow macros take a value as the first arg
so any other arrow macro can be used as a child of ->
I think the fact that that doesn't work in reverse is part of the reason why I avoid doing it at all.
but it never needs to work in reverse - with ->
as the parent, all you need to do is fall out the end of another form, and you're back in a position where all the other threading types are available
all that's required is that ->
be used at the top with the other threading types inside it
because of how arrow macros turn nesting into sequential forms, there's never a need for -> to be inside ->>
(quux (bar baz (dog foo cat)))
;; becomes
(-> foo
(dog cat)
(->> (bar baz))
(quux))
it might be helpful to try to come up with a case where it looks like -> needs to be inside ->> - in every case there's a trivial way to just put ->> inside ->, and fall out the end of ->>, instead
that's why as-> takes a value first then a binding name for example
and the semantics are simple - if -> is the parent, you just transition from the -> rule to the threading of the child threading macro
line breaks help
x and y are single values. For small compositions stuff I guess its personal preference but seem handy for much bigger comps
@lockdown- everything is a value - what I mean is in (bar x y)
are you conceptually doing something to x that passes forward, or are you doing something to y that passes forward, or is bar something that combines x and y as "peers"
is this specific to ->
or applies to ->>
too?
So are threading macros akin to the pipe operator in Elixir and easy to overuse?
@lockdown- in that case (->> (bar x y) (:bar) (foo value))
is how I'd do it, since neither x or y is conceptually carried forward to the next step
@buddhamagnet they are not an operator, they are a syntax transform
now that I look at the elixir docs yes - they directly do what |> does in elixir, perhaps a difference being that they are strictly a syntactic transform that happens before compilation, so they can do things that don't make syntactic sense
user=> (->> (+ x y) (let [x 11 y 31]))
42
thanks for the clarification
x and y are being used "illegally" here because they are syntactically outside the block that defines them, but it works because the form is rearranged before evaluation to something that is valid
I don't think elixir allows this
(also don't use code like that, it's to demonstrate that it's a syntax transform :D)
they can be overused, but usually (when combined with good formatting) they make code clearer
I have a string iso8601 date, and wanna add or subtract from the year ending up with an otherwise identical string. What's the best path there? I'm worried parse/format could get caught in funny formatting problems. String manip makes me feel guilty. Is there already a function for that somewhere? I see clj-time.core and clj-time.format in a few places... so maybe I should go learn those?
if it is really iso8601, the format is standard and well supported, it should be pretty straightforward
I don't know if I've ever seen dates straightforward... -_- But yeah, ok, how would you do that?
I would unequivocally recommend clj-time
- itās great.
I would anti recommend clj-time because it's based on a deprecated library (Joda time)
I'll second that and I'm (one of) clj-time
's maintainer! š
user=> (str (.plusYears (java.time.ZonedDateTime/parse "2019-06-12T20:18:17.744-04:00" java.time.format.DateTimeFormatter/ISO_OFFSET_DATE_TIME) 5))
"2024-06-12T20:18:17.744-04:00"
user=>
I'll second that and I'm (one of) clj-time
's maintainer! š
@seancorfield have you considered re-implementing clj-time in terms of Javaās built-in time classes?
Requiring beginners to learn Java interop simply to handle basic data types (i.e. dates / times) seems a bit naff.
There was a very long discussion thread about that in an issue over several years -- and the consensus was that it would not be possible without breaking backward compatibility.
Makes sense.
clj-time
's readme suggests looking at clojure.java-time
if you want syntactic sugar.
There's also a new cljc-based wrapper for Java Time that has a compatible JS implementation inside it. I have not looked at that yet.
Nice - was not aware of that until now.
(the discussion took nearly two years!)
I wonder if clojars supports a deprecated flag, at the library level? That would be quite handy for situations like this.
It does not, unfortunately.
awww, dang
(although I guess updating the project's name/description which would end up in pom.xml
and therefore display on Clojars might be a good idea)
Yeah - shame there isnāt something that build tools (`lein`, tools.deps
, etc.) can consume.
Also, is the cljc library you mentioned https://github.com/henryw374/cljc.java-time ?
Yes, thank you! Launched recently at Clojure/North.
By the way, beginners should learn Java interop, and they should do so relatively early on.
Sure, but I donāt think itās controversial to state that being required to learn Java interop before learning about basic data types in Clojure is indeed too early.
Though perhaps one counter argument is that because the JVMās date/time data types have been so fscked for so long, beginners should go through that hazing ritual as early as possible⦠(Iām not sure I buy that argument mind you, given how much Clojure sanitises the JVMās behaviour in other areas)
Ya, I'm not saying that's the first thing you want to learn. But you should learn interop early on. Clojure's date data type for example is java.util.Date
(type #inst "2019-01-01")
;> java.util.Date
If you look at the Clojure standard library, you'll see that there are many omissions and holes for what would count as complete. These were not accidental. Whatever Java provided which was good enough and didn't need improving was left to Java interop, on purpose
If you look at the rationale for Clojure you'll see: > because I wanted A Lisp for Functional Programming *symbiotic with an established Platform*
If you reject it, and reject Java and the JVM platform, Clojure will just be extra painful and will seem odd, incomplete, not batteries included.
FWIW I no longer consider myself a beginner (7 years with Clojure) - just here to help other beginners out.
And Java for a bit over a decade before that, so well aware of the strengths and weaknesses of the underlying platform. I just donāt think that arguments defending Clojureās uneven sanitisation of the JVM hold much water (beyond the usual realities of finite time).
How do you interpret the rationale? I always interpreted it as not trying to abstract away the JVM, but be symbiotic with it, as an explicit design goal.
So, basically accepting that trying to not be leaky is too problematic. And accepting the leaky abstraction, but in a controlled and purposeful way was better
Specifically this part: Language built for platform vs language ported-to platform Many new languages still take 'Language as platform' approach When ported, have platform-on-platform issues Memory management, type-system, threading issues Library duplication If original language based on C, some extension libraries written in C donāt come over
If Clojure had it's own date time library in core, it seems it would fall prey to the "library duplication"
Weāre talking about fundamental data types here, and I donāt think itās appropriate to require beginners to detour via Java interop if theyāre at that basic level of learning about the language.
And itās not like Clojure doesnāt already sanitise other basic Java data types (e.g. numerics, data structures).
Java interop is not like an esoteric thing that you almost never reach for. It's one of the main component of Clojure
Wrappers over it should only be used when they add additional value. Like make it considerably easier to use, provide additional functionality on top, etc.
This is my first post here, so hello all. Talking of Java interop. I was looking at the examples from the Java Interop chapter of Programming Clojure and playing around with them. I came across something I don't understand. One example given is the following:
Hi š I'm trying to use the following lib https://github.com/mbuczko/embodie in a sample project. However, I got an error about:
1. Unhandled java.io.FileNotFoundException
Could not locate embodie/core__init.class, embodie/core.clj or
embodie/core.cljc on classpath.
My project.clj
contains the following info:
(defproject hello_world_embody "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url ""
:license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
:url " "}
:dependencies [[org.clojure/clojure "1.10.0"]
[defunkt/embodie "1.0.0"]]
:repl-options {:init-ns hello-world-embody.core}
:main ^:skip-aot hello-world-embody.core
:target-path "target/%s"
:profiles {:uberjar {:aot :all}})
Someone knows what my problem is?
Thanks!did you restart your repl after adding the dependency?
@noisesmith Yes. I did
I can reproduce the error in a fresh project, it seems like they broke their build(?)
I just looked in the 1.0.0 jar - it contains no clojure code or class files, and the pom file describing its deps pulls in no embodie projects
they need to fix their release, they built it wrong
lein caches your deps into $HOME/.m2/repository/
by default, and you can look up the jar files there. A good editor can open a jar file as if it were a directory - they are just zip files with some extra rules
(fixed trivial typos)
@noisesmith Thank you. Sincerely I didn't keep in mind to look up the jar content That's a good idea š
trying again, haven't got the hang of entering new lines in Slack... This is my first post here, so hello all. Talking of Java interop. I was looking at the examples from the Java Interop chapter of Programming Clojure and playing around with them. I came across something I don't understand. One example given is along the lines of the following: (defn sax_parse [source handler] (.. SAXParserFactory newInstance newSAXParser (parse (InputSource. (StringReader. source)) handler))) that works if called with an XML string as source and something extending org.xml.sax.helpers.DefaultHandler as handler. If I expand the .. member access threading macro I get: (defn sax_parse [source handler] (. (. (. SAXParserFactory newInstance) newSAXParser) (parse (InputSource. (StringReader. source)) handler))) which also works. But if I try to rewrite the last line using the -> thread-first macro then whether I try: (defn sax_parse [source handler] (.. SAXParserFactory newInstance newSAXParser (-> source StringReader. InputSource. (parse handler)))) or: (defn sax_parse [source handler] (. (. (. SAXParserFactory newInstance) newSAXParser) (-> source StringReader. InputSource. (parse handler)))) then it doesn't work. The defn can't be evaluated, I get Syntax error (ClassNotFoundException) compiling at (test.clj:16:3). StringReader. It seems to be taking StringReader. as a class name. Can anyone tell me why (please)?
yeah, StringReader. without parens will not read properly
OK, thanks, but if I try: (defn sax_parse [source handler] (. (. (. SAXParserFactory newInstance) newSAXParser) (-> source (StringReader.) InputSource. (parse handler)))) I get a different error: Syntax error (IllegalArgumentException) compiling new at (test.clj:17:17). No matching ctor found for class java.io.StringReader
or you can use (new StringReader)
which is what (StringReader.)
expands to when read
What's confusing me is that (macroexpand '(-> source StringReader. InputSource. (parse handler)))
(macroexpand '(-> source StringReader. InputSource. (parse handler)))gives
also this would be much easier to read with sugared .
forms - so (.newSAXParser (SAXParserFactory/newInstance) ...)
`
<sorry keep forgetting about shit enter> What's confusing me is that (macroexpand '(-> source StringReader. InputSource. (parse handler))) gives (parse (InputSource. (StringReader. source)) handler) which is what worked in the first place?
yes, that's actually weird - that should work even without the parens actually
are you sure you are evaluating that code in the right environment?
I think so, everything else works. I'm just using C-c C-e in Emacs CIDER
@alex.sumner you might want to look at Slack's formatting with triple-backticks so you get
code that looks like this
Yes, that would be better! Thanks.
And under Preferences > Advanced, check the option that pressing enter inside triple-backticks does not send the message. That way you can type three backticks, press enter, type lots of code with newlines, then three more backticks (and then enter will send the message).
OK, so like this? `
I guess not!
You need that preferences change too.
you can also use control-enter to make a new line without sending a message
OK, now we're getting somewhere
Still don't know about the problem with ->
but getting somewhere with Slack!
I think in
(defn sax_parse [source handler]
(. (. (. SAXParserFactory newInstance) newSAXParser)
(-> source (StringReader.) InputSource. (parse handler))))
that first line is a problem. I think (.newSAXParser (SAXParserFactory/newInstance))
makes part of it more readable but you still have that outer (.
which would seem to be trying to process the whole (-> ...)
form as a method call?oh yeah, that is an issue
Oh, .parse
is a method on the parser created?
(defn sax_parse [source handler]
(. (. (. SAXParserFactory newInstance) newSAXParser)
(-> source (StringReader.) InputSource. (parse handler))))
parse handler is in the innermost form accidentally.
but anyways this is impossible to read and would never appear in practice(defn sax-parse [source handler]
(.parse (.newSAXParser (SAXParserFactory/newInstance))
(-> source (StringReader.) (InputSource.))
handler))
Is that what you're looking for?(with kebab-case
name instead of snake_case
BTW for idiomatic Clojure)
What I actually wanted to try was
(defn sax_parse [source handler]
(.. SAXParserFactory newInstance newSAXParser
(-> source StringReader. InputSource. (parse handler))))
Agreed.
Mixing ..
and ->
can be pretty confusing to read (and, as we see here, often confusing to write).
(defn sax-parse [source handler]
(let [factory (SAXParserFactory/newInstance)
parser (.newParser factory)
input (-> source (StringReader.) (InputSource.))]
(.parse parser input handler)))
..
is good for method chains in Java:
Foo.getOrder().getLineItem(2).getPrice()
(.. Foo getOrder (getLineItem 2) getPrice)
OK, thanks. I am still worried that
(A (B))
works but
(A (C))
does not, when
(macroexpand '(C))
evaluates to
(B)
Where A is either
.. SAXParserFactory newInstance newSAXParser
or its macroexpansion
`. (. (. SAXParserFactory newInstance) newSAXParser)
B is
parse (InputSource. (StringReader. source)) handler
and C is
-> source StringReader. InputSource. (parse handler)
Right, cool!
(-> source StringReader. InputSource. (parse handler))
doesn't work because the last form becomes:
(parse PREVIOUS handler)
this is a difference between ..
and other forms
Yes parse is a method
so without the leading .
it thinks it's either a clojure function or a local in a let-binding
@ghadi But he has an outer (. parser (-> ,,,))
form in the above
SAXParser.parse takes an input and a handler, so if you want to translate this to the "fancy style" https://clojurians.slack.com/archives/C053AK3F9/p1560381542398400
(defn sax-parse
[source handler]
(let [input (-> source (StringReader.) (InputSource.))]
(.. (SAXParserFactory/newInstance)
(newParser) ;; no parens necessary here, but I'm including them
(parse input handler))))
OK thanks. That looks better. I am still confused as to why
(defn sax_parse [source handler]
(.. SAXParserFactory newInstance newSAXParser
(parse (InputSource. (StringReader. source)) handler)))
does work however, and it does (and it is basically what's in the Programming Clojure example)I think it could have to do with ..
being expanded before ->
?
if you think about it - ..
can't be clever about the transform it does, if it finds a form starting with ->
it would need to find a method named ->
right?
there are two differences between that and what I pasted: 1) the first argument to parse (the input thingy) is inlined
2) (.. SAXParserFactory newInstance)
is equivalent to (. SAXParserFactory newInstance)
which is equivalent to (SAXParserFactory/newInstance)
in practice I never see the first two styles for calling a static method -- always (Class/staticmethod)
If it helps
user=> (macroexpand-all '(.. SAXParserFactory newInstance newSAXParser (-> source StringReader. InputSource. (parse handler))))
(. (. (. SAXParserFactory newInstance) newSAXParser) (parse (new InputSource (new StringReader source)) handler))
user=> (macroexpand '(.. SAXParserFactory newInstance newSAXParser (-> source StringReader. InputSource. (parse handler))))
(. (. (. SAXParserFactory newInstance) newSAXParser) (-> source StringReader. InputSource. (parse handler)))
user=>
I think the .
special form is treating the (-> ,,,)
form as a literal form to turn into a method call?right, that's my theory as well - it can't be clever about expanding its method forms, because it doesn't expect them to be expandable, it expects method names to turn into method invocations
I wish there was a way to ask the compiler what it turns (. object (-> arg-1 (method arg-2)))
into...
but the middle style (. Class methodName)
is the "final" macroexpansion that the compile operates on
user=> (macroexpand '(java.util.Objects/nonNull a b c))
(. java.util.Objects nonNull a b c)
I think this is an issue too
user=> (macroexpand '(.. foo (-> x (bar y) (someMethod))))
(. foo (-> x (bar y) (someMethod)))
- I don't expect .
to be clever about discovering someMethoduser=> (. "hello" (-> "l" (replace "L")))
Execution error (IllegalArgumentException) at user/eval163 (REPL:1).
No matching method __GT_ found taking 2 args for class java.lang.String
user=> (. "hello" (replace "l" "L"))
"heLLo"
user=>
The .
form doesn't know what to do with ->
and it definitely shouldn't try to invoke the initial symbol in a form
yeah, there's no sane way it could, for the operator position at least
So it turns it into (.-> "hello" "l' (replace "L"))
which is munged to (.__GT_ "hello" "l" (replace "L"))
So that's why you can't mix ..
and ->
like this.
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L970-L974
Yup. And going back to ..
to be more like @alex.sumnerās original code
user=> (.. "HELLO" toLowerCase (-> "l" (replace "L")))
Execution error (IllegalArgumentException) at user/eval170 (REPL:1).
No matching method __GT_ found taking 2 args for class java.lang.String
user=> (.. "HELLO" toLowerCase (replace "l" "L"))
"heLLo"
user=>
(because you get (.__GT_ (.toLowerCase "HELLO") "l" (replace "L"))
because ->
is munged to __GT_
in JVM code)
Thank you for the question/puzzle @alex.sumner -- it's been a great learning experience for us old 'uns too!
OK, thanks all of you! So, to check my understanding, you can't put macros inside
.
forms (and therefore inside ..
forms either) because they won't be expanded? I was working on the basis that if your macro A expands to B then anything with A in it iis equivalent to the same thing with A replaced by B. That's clearly not always the case though.you can, but not in the method position
because it intentionally doesn't expand that position - you don't expect it to look up parse
and replace it right?
it's used as is
you can safely use -> to construct args to one of the methods
OK, thanks again.
this all makes me wonder if explicit message passing OO isn't a lot more sane, I'm sure it doesn't optimize as nicely as what the JVM does, but it would make higher order code much simpler to construct
Going back to your original code @alex.sumner this should work:
(defn sax-parse [source handler]
(.. SAXParserFactory newInstance newSAXParser
(parse (-> source StringReader. InputSource.) handler)))
(but I haven't tested it)