Fork me on GitHub
#clojure-dev
<
2016-08-18
>
cfleming00:08:13

With the namespaced maps functionality, is the intention that the namespaces refer to valid existing namespaces?

cfleming00:08:00

i.e. in the example: #:person{:first "Han" :last "Solo" :ship #:ship{:name "Millenium Falcon" :model "YT-1300f light freighter"}}, is the assumption that person and ship are actual namespaces, or is it considered ok that they just be ad-hoc qualifiers?

seancorfield01:08:05

Just ad hoc qualifiers (although they can be namespaces).

seancorfield01:08:50

That’s why there’s a JIRA issue open for allowing some sort of alias call for a non-existent namespace, so you can use it with :: keywords.

cfleming01:08:48

@seancorfield: Thanks - got a link for that JIRA?

seancorfield01:08:05

Having said that, I can’t find the JIRA issue (hah! I knew you were going to ask!)

cfleming01:08:31

Does that mean that :: keywords and namespaced map forms do have to use aliases or valid namespaces?

seancorfield01:08:37

I swear @alexmiller said that such an issue was under consideration...

cfleming01:08:47

Yeah, I remember something along those lines too.

seancorfield01:08:06

Right now, you need a valid namespace to create an alias. Alex offered a workaround (that is used somewhere in core I think?) where you use in-ns to enter the non-existent namespace then in-ns to switch back to your own namespace, then you can create the alias.

cfleming01:08:37

So that actually creates the namespace.

seancorfield01:08:44

That’s the current workaround 🙂

seancorfield01:08:57

"Clojure/core-approved!" 😈

cfleming01:08:46

That’s pretty horrible, I’d better check what Cursive thinks of that, especially since it looks like those aliases will be used outside that ns.

seancorfield01:08:40

This in one of our files where we’re using specs and namespaced keywords:

;; workaround aliasing restriction
(in-ns 'ws.domain.member)
(in-ns 'ws.domain.member.update)
(in-ns 'ws.domain.member.validation)
;; back to normal
(in-ns 'ws.profile.field)

(alias 'm  'ws.domain.member)
(alias 'mu 'ws.domain.member.update)
(alias 'mv 'ws.domain.member.validation)
😆

Alex Miller (Clojure team)01:08:29

It is under consideration but I don't think there is a public jira

cfleming01:08:01

@alexmiller: Is the current expectation that :: keywords and maps do use valid aliases or namespaces?

cfleming01:08:15

i.e. should I be flagging it if the user used #::ship{:name "Millenium Falcon" :model "YT-1300f light freighter”} where ship is not a valid alias or namespace?

seancorfield01:08:45

It needs to be a valid alias

seancorfield01:08:00

The reader blows up otherwise:

boot.user=> #::ship{:foo :bar}

clojure.lang.LispReader$ReaderException: java.lang.RuntimeException: Unknown auto-resolved namespace alias: ship
             java.lang.RuntimeException: Unknown auto-resolved namespace alias: ship
clojure.lang.LispReader$ReaderException: java.lang.RuntimeException: Unmatched delimiter: }
             java.lang.RuntimeException: Unmatched delimiter: }

cfleming01:08:14

Or namespace: ::clojure.string/foo is fine

cfleming01:08:21

Ok, thanks.

seancorfield01:08:24

boot.user=> (in-ns 'ship)
:foo:bar#object[clojure.lang.Namespace 0x74bf3db1 "ship"]
ship=> (in-ns 'boot-user)
#object[clojure.lang.Namespace 0x215119de "boot-user"]
boot-user=> #::ship{:foo :bar}
#:ship{:foo :bar}

Alex Miller (Clojure team)01:08:27

Right, can be fully qualified

Alex Miller (Clojure team)01:08:12

Basically the namespace part is resolved in the current ns

cfleming01:08:48

Ok. But the same is not true of #: forms, they can have some arbitrary disambiguator?

Alex Miller (Clojure team)02:08:55

If you mean namespaced maps they follow the same rules

Alex Miller (Clojure team)02:08:19

Has to be either fully qualified or an alias in current ns

cfleming02:08:06

@alexmiller: So in #:ship{:name "Millenium Falcon" :model "YT-1300f light freighter”} ship must be a valid namespace?

cfleming02:08:25

I mean namespaced but not auto-resolved maps.

seancorfield02:08:29

No, we showed that above.

Alex Miller (Clojure team)02:08:38

It must be either a fully qualified ns or a valid alias in current ns

cfleming02:08:57

Even in the non-auto-resolve case?

cfleming02:08:14

i.e. #: as opposed to #::?

Alex Miller (Clojure team)02:08:14

I don't know what you mean by that

seancorfield02:08:16

#:anything{:foo :bar} is valid right?

Alex Miller (Clojure team)02:08:27

Those are both auto resolved

seancorfield02:08:17

Ah, I see the confusion. That produces {:anything/foo :bar} which is fine.

cfleming02:08:26

The JIRA makes it sound like :: forms are “auto-resolved"

seancorfield02:08:31

So the result is fully qualified.

cfleming02:08:49

I’m trying to figure out if only “auto-resolved” forms are required to use a valid alias or namespace.

seancorfield02:08:58

But if you use :: on a key it will auto-resolve

Alex Miller (Clojure team)02:08:09

Using same rules as :: keywords

cfleming02:08:52

So, is it valid to say #:anything{} where anything is not a valid alias or ns?

seancorfield02:08:59

So #: as a prefix is a red herring here. Only :: matters. For your question.

cfleming02:08:01

It seems to work currently, my question is if it should.

seancorfield02:08:34

The alias or valid namespace issue only applies to ::

cfleming02:08:36

Right. So for both maps and keywords, using the :: form means that the following name must refer to an alias or namespace, but for : forms it can be anything you like.

seancorfield02:08:34

And there's #:ship{:name ".." ::thing 42} as well, right? Where ::thing will auto-resolve.

cfleming02:08:50

Right. In the map case where the keyword actually specifies a ns, that ns will be used (taking into account the _ case too)

seancorfield02:08:21

So in boot.user=> #:ship{:name ".." ::thing 42} will produce {:ship/name ".." :boot.user/thing 42}

seancorfield02:08:47

Did the _ thing get in? Hmm, let me try...

cfleming02:08:18

According to the JIRA it did, yeah.

seancorfield02:08:33

Yeah, :_/a produces :a

seancorfield02:08:59

(inside a ns-qualified map literal)

seancorfield02:08:44

boot.user=> #:ship{:name "..." ::thing 42 :_/a 1}
{:ship/name "...", :boot.user/thing 42, :a 1}

andy.fingerhut05:08:29

Is all of this documented somewhere, hopefully? i.e. what is supposed to be accepted, and for bonus points whether the implementation accepts things that are not supported?

cfleming05:08:14

The one case that is confusing is the #:foo{} case, where the JIRA uses the word “namespace” but foo is not actually required to be one.

cfleming05:08:27

It’s overloading the term “namespace”, basically.

andy.fingerhut05:08:25

So that case is something that could be documented more explicitly? (assuming it is intended to be accepted, not an accident of the current implementation)

cfleming07:08:16

Yes, I think so

cfleming07:08:28

(as someone needing to understand how it worked)

dm310:08:27

Is there any known resolution regarding (defn ^long bad [x] (inc x)) vs (defn good ^long [^long x] (inc x))? I know the second approach is the proper one, but if you mistakenly apply the first one - there's nothing to warn you that you might get a clojure.lang.Compiler$CompilerException: java.lang.IllegalArgumentException: Unable to resolve classname: clojure.core$long@414dde97 later on

dm310:08:48

when doing (* 1 (bad 1))

dm310:08:35

the explicit {:tag 'long} approach got fixed in 1.8 (http://dev.clojure.org/jira/browse/CLJ-1533)

Alex Miller (Clojure team)12:08:01

The issue with the dual meaning of "namespace" (as a naming qualifier in keywords and symbols) and as a named var container is not unique to here. older Clojure docs refer to the latter as libraries, which is equally confusing in a different way. Not enough words.

Alex Miller (Clojure team)12:08:20

@dm3 what resolution are you looking for? In general type hints are hints and this ignored if not applicable. This is a bit different in that it's a hint of an invalid form, not a hint that doesn't apply. I think http://dev.clojure.org/jira/browse/CLJ-1674 may be applicable

dm312:08:29

yep, that applies

nathanmarz12:08:30

Regarding primitives support in Clojure, I'm guessing the 4 argument restriction was to avoid generating too many interfaces. Was an alternate design considered of dynamically generating interfaces based on what combinations of primitive arguments are actually used throughout a project?

nathanmarz12:08:50

So if only usage of primitives is [a1 ^long a2 a3 a4 a5] the only interface generated is clojure.lang.IFn$OOLOOO

Alex Miller (Clojure team)13:08:47

don’t know, that was long before my involvement

andy.fingerhut15:08:06

@dm3: the Eastwood linter helps detect some incorrect type hints, and I think the one in your example, too. https://github.com/jonase/eastwood

dm317:08:38

yep, that's where I found an explanation

cfleming22:08:19

@nathanmarz: The problem is that Clojure in general does not assume global knowledge of the project

cfleming22:08:07

I guess you could have a global cache of type signature->interface, and that would be populated as you compiled forms

arrdem22:08:58

I looked at doing exactly that for a while, but you quickly run into problems with naming the generated interfaces, especially if you allow the generation of typed invoke signatures for arbitrary user types.

arrdem22:08:48

The JVM gives you a 65535 chr budget for class/interface names, so for the most part you can just splat fully qualified type names together, but I never got past a proof of concept using short class names and the hash of the full thing which was problematic from a repeatability/caching perspective. For binary compat your generated invoke interface names need to be fully deterministic. I know @bronsa looked at this too at some point, don't think he took it any further.

nathanmarz22:08:01

@cfleming @arrdem Yea the caching approach is what I was thinking of, and you could just limit the types to objects, doubles, and longs. Pushing user types down to the bytecode level seems like a separate issue.

bronsa22:08:58

I tried that a while ago, unfortunately it doesn't go well when AOT comes into play

bronsa22:08:57

I think it should be an easier way to extend over the 4args support by reducing the number of combinations, making eg IFn$LOLD , IFn$LLOD, $LDOL and so on map to the same interface and have the compiler just reorder args at compile time but I never tried implementing it (using this approach the current number of prim interfaces would go from 358 to 100 and adding a 5th arg would go from 100 to 162 rather than from 358 to 1086, we could support 7 args with just 352 interfaces)

Alex Miller (Clojure team)23:08:10

I suspect there’s probably a much better approach using invokedynamic that would let you do this stuff dynamically without all the interfaces