clojure-dev

Issues: https://clojure.atlassian.net/browse/CLJ | Guide: https://insideclojure.org/2015/05/01/contributing-clojure/
ikitommi 2021-03-07T17:29:14.017800Z

1. is there a syntax guide for Var meta :arglists? things like clojure.core/eduction have hand-written syntax '([xform* coll]) 2. are there better ways to extract the arities from normal defns? 3. what ways there are to pull out return type hints per artities, besided Var :tagmeta - which is shared for all arities? 4. what ways there are to get argument type hints? 5. would it be possible to extract all the info (arities, basic type hints) programmatically in the future? e.g. compiler option to inject more info into Var & fn meta? could use the stellar tooling from @borkdude (sci, edamame & clj-kondo), but as the clojure compiler knows all these already, would like to hear if it is possible to get access to that info directly

bronsa 2021-03-07T17:46:34.020600Z

every argvec for each arity can specify an arity specific return type hint

bronsa 2021-03-07T17:47:11.021900Z

the algo for return type is to first check on the argvec and fallback on the var type hint

bronsa 2021-03-07T17:47:49.022800Z

for args it's per binding in the argvec of the specific arity

bronsa 2021-03-07T17:48:41.025100Z

the compiler does no validation on 'correct' arglist format, so sometimes handmade ones are not in the right format and they're just ignored

bronsa 2021-03-07T17:49:17.025700Z

afair there's no exposed api from the compiler to get this info

bronsa 2021-03-07T17:49:22.026Z

and no specification

bronsa 2021-03-07T17:51:16.027100Z

(apologies for being terse, typing from a phone :) )

alexmiller 2021-03-07T17:51:25.027300Z

there is no formal spec and in the wild you can encounter things that deviate from what you see in core

alexmiller 2021-03-07T17:52:51.028Z

in general, "locking" a var into a concrete set of arities immediately raises questions about what that means for future (additive) modifications to the signature

bronsa 2021-03-07T17:54:45.029200Z

Well but the compiler does specialise interop calls according to the "current" arglist so it is locking in a way

bronsa 2021-03-07T17:55:14.029500Z

ah, you said additive :) fair enough

borkdude 2021-03-07T18:46:25.030100Z

> the compiler does no validation on 'correct' arglist format, so sometimes handmade ones are not in the right format and they're just ignored does the compiler do anything with the handmade arglists at all other than adding it to the metadata?

bronsa 2021-03-07T18:51:17.030700Z

it doesn't know if it's handmade or installed by defn

bronsa 2021-03-07T18:51:57.031800Z

it just looks for type hints if the arglist is present, if the format is incorrect it may ignore it or derive the wrong type hint at callsites

borkdude 2021-03-07T18:53:06.032Z

"it" = the clojure compiler?

bronsa 2021-03-07T18:54:53.032500Z

ye

borkdude 2021-03-07T18:54:57.032700Z

I'm surprised since I assumed that the "handwritten" arglists was just for documentation clarification

borkdude 2021-03-07T18:55:08.033Z

e.g. when you define a function using comp or so

bronsa 2021-03-07T18:55:22.033500Z

it serves two purposes, documentation and holds type hints

bronsa 2021-03-07T18:55:36.034Z

there was a proposal years ago about decoupling for this reason

bronsa 2021-03-07T18:56:14.035100Z

so you could have a different arglist for documentation that doesn't need to be 'correct', while keeping the true one for the compiler

bronsa 2021-03-07T18:56:54.036300Z

but as things are you just need to be careful if you're crafting a tyoe hinted arglist manually

bronsa 2021-03-07T18:57:11.036900Z

which to be honest not many do anyway, apart from some instances in core

borkdude 2021-03-07T18:57:36.037800Z

if you want to optimize you might as well write it out in full

bronsa 2021-03-07T18:57:39.037900Z

so not particularly high value I'd say, even if the idea would be nice

bronsa 2021-03-07T18:58:04.038200Z

optimise in what sense?

borkdude 2021-03-07T18:58:09.038400Z

using type hints

bronsa 2021-03-07T18:58:28.038800Z

sorry, being dense :) not following

borkdude 2021-03-07T18:58:58.039400Z

e.g. when I define a function using comp but I want some type hint optimization, I might as well not use comp but write my fn using defn

bronsa 2021-03-07T18:59:18.039700Z

ah, right

ikitommi 2021-03-07T19:00:38.041100Z

tools.analyzeris just for analyzing source code, right? it’s awesome tool, but could the normal clojure compiler expose more info to the runtime? (via compiler option)

ikitommi 2021-03-07T19:00:43.041400Z

(->> (jvm/analyze
       '(fn
          ([] "kikka")
          ([x] (inc x))
          (^Boolean [x y] (+ x y))
          ([x y & zs] (apply + x y zs)))
       (jvm/empty-env))
     :methods
     (mapv (fn [{:keys [arglist body tag params]}]
             {:tag (or (:tag body) tag)
              :arglist arglist
              :args (mapv (fn [arg] (select-keys arg [:form :tag])) params)})))
;[{:tag java.lang.String
;  :arglist []
;  :args []}
; {:tag java.lang.Number
;  :arglist [x]
;  :args [{:form x, :tag java.lang.Object}]}
; {:tag java.lang.Boolean
;  :arglist [x y]
;  :args [{:form x, :tag java.lang.Object}
;         {:form y, :tag java.lang.Object}]}
; {:tag java.lang.Object,
;  :arglist [x y & zs],
;  :args [{:form x, :tag java.lang.Object}
;         {:form y, :tag java.lang.Object}
;         {:form zs, :tag clojure.lang.ISeq}]}]

bronsa 2021-03-07T19:01:17.042100Z

yeah sure, I wasn't suggesting using t.a

bronsa 2021-03-07T19:01:51.044200Z

just pointing to that function to extract the relevant argvec for an arity, which more or less defines the syntax the compiler understands

2021-03-07T19:01:58.044400Z

Bronsa knows best, but my understanding is that the Clojure compiler might reveal some or all of this via JVM objects that it creates, but none of it is documented outside of the Clojure compiler source code (and perhaps someone's personal documentation notes), and none of it is promised not to change across versions of the Clojure compiler

ikitommi 2021-03-07T19:02:48.045600Z

this kinda thing:

(let [f (fn
          ([] "kikka")
          ([x] (inc x))
          (^Boolean [x y] (+ x y))
          ([x y & zs] (apply + x y zs)))]
  (pull-out-arities-and-types f))

bronsa 2021-03-07T19:03:07.046Z

no, the clojure compiler only uses arglists during a 'read' and analysis, it's not exposed in the ast

bronsa 2021-03-07T19:03:27.046600Z

ah @ikitommi, arglists and type hints are on vars not functions

bronsa 2021-03-07T19:03:33.047Z

there's nothing reified on function objects for that

ikitommi 2021-03-07T19:03:52.047600Z

could there be?

bronsa 2021-03-07T19:03:57.047900Z

so nothing you can do on lambdas apart from doing reflection over the class

bronsa 2021-03-07T19:04:15.048500Z

there could, I believe there's a ticket in jira but no patch

bronsa 2021-03-07T19:05:12.050100Z

but also not super useful, type hints are used at callsites, in a HOF setting they would not be analyzeable

bronsa 2021-03-07T19:05:41.050900Z

unless doing complex flow analysis and per object specialisation which clojure doesn't do

ikitommi 2021-03-07T19:06:07.051600Z

(defn f
  ([] "kikka")
  ([x] (inc x))
  (^Boolean [x y] (+ x y))
  ([x y & zs] (apply + x y zs))) )

(meta #'f)
;{:arglists ([] [x] [x y] [x y & zs]),
; :line 2331,
; :column 1,
; :file ...
; :name f,
; :ns ...}

bronsa 2021-03-07T19:06:20.052100Z

that's on the var

ikitommi 2021-03-07T19:06:59.052700Z

it has arglists (which is good), but no type-info 😞

bronsa 2021-03-07T19:07:07.052900Z

it does

ikitommi 2021-03-07T19:07:15.053300Z

really?

bronsa 2021-03-07T19:07:17.053500Z

you need to print meta of the symbols and vectors

bronsa 2021-03-07T19:07:51.054300Z

if you get the meta of [x y] from that arglists you'll see the boolean tag

ikitommi 2021-03-07T19:08:27.054700Z

oh, cool. didn’t know that, thanks!

ikitommi 2021-03-07T19:09:09.055500Z

… but the inferring of return types from the body is not there, needs t.a, right?

bronsa 2021-03-07T19:15:06.056800Z

clojure doesn't do return type inference, it only does a simple form of local type inference (to specialise interop calls), but doesn't propagate across fn boundaries

bronsa 2021-03-07T19:16:12.057900Z

t.a could, and with some minor enhancements so could the clojure compiler, but I believe it's a design decision not to

bronsa 2021-03-07T19:19:08.058800Z

what the compiler does is super basic and, I believe, intentionally limited

bronsa 2021-03-07T19:19:56.059600Z

remember they are type hints, not type declarations

bronsa 2021-03-07T19:22:14.061100Z

there's at least one function in core that has a ^String type hint on an argument that could be a symbol, and to the compiler that's fine

bronsa 2021-03-07T19:22:58.062100Z

(since that local is only used on an interop call after a string? check)

ikitommi 2021-03-07T19:33:29.067500Z

thanks for the great explanation. Couldn't find the relevant jira issue, only the SO question with few ways to resolve the fn arity at runtime. Would be great if core could expose those directly (in a dev-mode, as extra meta would consume memory)

ikitommi 2021-03-07T19:36:34.070400Z

pluggable analyzing would solve both things: t.a could push out arity & infered type hints for both vars and fns.

cfleming 2021-03-07T20:56:26.070900Z

TIL about arglists too, I also assumed they were just documentation.