Fork me on GitHub
#clojure
<
2019-06-11
>
pinkfrog03:06:35

wha’ts the (num) function used for?

pinkfrog03:06:50

if its purpose is to coerce to a number, then why (num “333”) fails?

didibus03:06:25

It coerces to a clojure.lang.Number

didibus03:06:32

It doesn't parse a string into one

pinkfrog03:06:04

@didibus could you name an argument that is not a number but `num’ accepts?

the2bears04:06:26

@i change your comment to "is not a clojure.lang.Number" and it should change your perspective.

didibus04:06:42

It's a super niche function that you probably don't need

pinkfrog04:06:08

@didibus still i don’t know what it does. for example, (num 333), however, 333 is already a number.

didibus04:06:08

333 is read as a number by the reader

didibus04:06:26

But if you cast it to an Integer with (int 333)

didibus04:06:45

And then want it back as a Number, you can use num back on it

didibus04:06:09

Honestly, I have no idea when or why this use case would come up

didibus04:06:30

But basically all the number coercion functions coerce from one to the other

didibus04:06:41

They don't coerce from non Number types

didibus04:06:09

Which is why (int "333") also doesn't work

didibus04:06:52

You need to use either Java interop and the Parse methods to do so, or use the reader itself.

pinkfrog04:06:04

i see. Number is the base class of Integer, Long, etc. so basically its using the base class Number as abstraction. Very java-ish, oo flavor.

didibus04:06:16

It basically takes from any more concrete type, like float, int, double, long, etc. And coerce it back to the Clojure default for the number

didibus04:06:36

Like how the reader does it

didibus04:06:13

I think you would use it like if you write a number operation

didibus04:06:47

And for the operation, maybe you had to coerce numbers, but you want to return it back to the appropriate Number type

didibus04:06:08

Then before returning, you'd call number back on it

didibus04:06:02

Also, it seems it just. Basically boxes a primitive number to its object variant

didibus04:06:26

I'm looking at the source now

didibus04:06:48

So long becomes Long, int becomes Long, double becomes Double, float becomes Float

didibus04:06:26

And everything else become java.lang.Number

didibus04:06:52

Which mostly mean they stay the same

didibus04:06:03

So BigInt will remain BigInt. Etc.

didibus04:06:16

Bottom line, probably not what you want. Try: (Long/parseLong "333") instead.

didibus04:06:54

There's also Float/parseFloat and same for Double, Integer, etc.

didibus04:06:45

If you don't want to specify the type to parse into, and want the same logic as Clojure litteral syntax, you can use (edn/read-string "333")

didibus04:06:14

It if you trust the input string, you can use Clojure's read-string as well

pinkfrog05:06:16

@didibus still a little bit confused. (type (num 1)) gives long, not number.

pinkfrog05:06:05

(def a 3) (def b (num a)) (identical? a b ) ==> true

lilactown05:06:33

what’s (type (int 1)) and (identical? (int 1) (num 1))?

pinkfrog05:06:42

i feel the doc may be little misleading.

pinkfrog05:06:52

(num) says, `coerce to a Number’.

orestis05:06:53

This a relevant issue: https://clojure.atlassian.net/browse/CLJ-2451 — it’s something that confuses a lot of people

seancorfield05:06:30

@i It doesn't say "coerce to a Number", it says "coerce to Number". That's an important difference.

pinkfrog05:06:01

@seancorfield what’s the Number here? the java.lang.Number ?

seancorfield05:06:07

But you'll almost never find a use for num. Primitives are automatically boxed in Clojure, which means they end up as Long or Double most of the time already. And those are java.lang.Number.

pinkfrog05:06:43

(num (int 1)) creates a java.lnag.Long. but (int 1) is already a Number (Integer)

pinkfrog05:06:08

(number? (int 1)) ==> true. and (num (int 1)) still coerces. that confuses me.

seancorfield05:06:11

num on a float (primitive) will produce a Float (`Number`). num on a double (primitive) will produce a Double (`Number`). That's all it does.

didibus05:06:57

Yes, it is "java.lang.Number"

didibus05:06:26

java.lang.Float, java.lang.Integer, java.lang.Double, java.lang.Long are all children of java.lang.Number

seancorfield05:06:52

About the only useful behavior of num is that it throws an exception when called on something that isn't a Number 🙂

didibus05:06:58

Clojure will coerce to the most specific children

didibus05:06:37

So primitive type long (no uppercase), will coerce to java.lang.Long, which is a child of java.lang.Number

didibus05:06:14

I suspect num is mostly used by Clojure itelf, as the implementation for the auto-boxing Sean mentioned, but I can't confirm

seancorfield05:06:16

Well, there are lots of calls to the static method num() inside clojure.lang.Numbers.

pinkfrog05:06:25

i am little bit lost in the conversation. given your code snippet, of `return (Number) x’. I wonder what happened when (type (num (int 1)) becomes Long instead of Integer.

seancorfield05:06:02

Because (int 1) is autoboxed to Long, and Long is already Number so the cast does nothing.

seancorfield05:06:45

Like I said, pretty much the only useful behavior of num is that it will throw an exception if called on something that isn't a Number.

seancorfield05:06:07

I would imagine that it is almost never used in production code.

seancorfield05:06:42

(and if http://crossclj.info still existed, we might be able to answer that to some degree!)

pinkfrog05:06:21

(type (int 1)) ==> java.lang.Integer

pinkfrog05:06:05

@seancorfield i mean, it is boxed to Integer not Long.

seancorfield05:06:25

Sorry, I meant Integer there.

user=> (type (int 1))
java.lang.Integer

didibus05:06:49

Ya, I'm not sure about that part either

seancorfield05:06:54

(...and Integer is already Number so the cast does nothing)

didibus05:06:00

In Java, casting an Integer to Number returns an Integer

didibus05:06:31

So this is confusing:

didibus05:06:36

user=> (type (num (int 2)))
java.lang.Long
user=> (type (int 2))
java.lang.Integer
user=> 

seancorfield05:06:56

But that wouldn't be invoked anyway: (int 2) is already boxed to Integer by that point.

seancorfield05:06:12

So it would go through the Object case and be cast to Number.

didibus05:06:37

Ya, and that cast should not coerce to Long

seancorfield05:06:48

user=> (type (cast java.lang.Number (int 1)))
java.lang.Integer
So there must be some additional boxing going on with (type (num (int 1)))

didibus05:06:47

Its weird though:

user=> (type (identity (int 3)))
java.lang.Integer

didibus05:06:16

This is a rabbit whole I'm afraid

seancorfield05:06:26

user=> (type (num (double 1.0)))
java.lang.Double
user=> (type (num (float 1.0)))
java.lang.Float
user=> (type (num (short 1)))
java.lang.Long
user=> (type (num (byte 1)))
java.lang.Long
user=> 
OK, so the double and float overloads "preserve" the cast type and end up with Double and Float respectively, but everything else ends up promoted to Long which is Clojure's default integral type?

didibus05:06:48

Hum.. maybe. But there is an overload for num(long) and num(float) respectively which coerces them to that.

didibus05:06:25

For int, there isn't, so Clojure first auto-boxes it into something, and calls num(Object) with it,which just returns it casted to Number

didibus05:06:58

Maybe, type has an overload as well 😛

seancorfield05:06:51

Ah, I'd missed the long overload. That explains it: in Java, byte, short, and int will all coerce to long first -- so that's the overload that processes all of them.

didibus05:06:05

Oh does it now

seancorfield05:06:07

The five definitions of num() are not together.

seancorfield05:06:15

I only saw the first three... doh!

didibus05:06:25

I didn't know Java did that

seancorfield05:06:36

Well, four since the int overload is commented out.

seancorfield05:06:40

Now it makes sense.

didibus05:06:20

So java will favor the long overload given an int?

didibus05:06:25

If there are no int overload ?

didibus05:06:17

Interesting, well that explains it

didibus05:06:54

Clojure will call num with primitive type int, and Java will coerce it to primitive long, and dispatch to the primitive long num overload, which will coerce it to a boxed Long.

seancorfield05:06:31

(! 686)-> cat Promote.java 
public class Promote {
public String foo(long x) { return "Long"; }
public String foo(short x) { return "Short"; }
public static void main(String[] args) {
	Promote p = new Promote();
	System.out.println( p.foo( (byte)1 ) );
	System.out.println( p.foo( (short)1 ) );
	System.out.println( p.foo( (int)1 ) );
	System.out.println( p.foo( (long)1 ) );
	System.out.println( p.foo( 1 ) );
}
}

Mon Jun 10 22:58:56
(sean)-(jobs:0)-(~/java)
(! 687)-> java Promote
Short
Short
Long
Long
Long
Took me so many attempts to get this to compile -- after using Clojure for nine years, I can't write Java any more! 🙂

didibus06:06:39

I guess it picks the best type that can fit

didibus06:06:01

if you remove the short overload, does byte and short now go to long ?

didibus06:06:33

Also, haha, ya, I've been writing so much Clojure, when I need to do a Java code review or code change, I'm like.. what the hell is this mess! 😛

didibus06:06:39

Clojure is spoiling me actually

didibus06:06:45

That's the biggest downside of it 😮

didibus06:06:53

You can never go back and be happy

seancorfield06:06:09

I've written almost zero Java in nine years at this point...

didibus06:06:34

Wow, that's crazy though! I still deal with a lot of mixed Java/Clojure project, but slowly Clojure is taking over

seancorfield06:06:59

Work was a CFML (ColdFusion) shop when I joined!

didibus06:06:30

My partner was going through interviews recently though. And as I was helping her practice some interview questions, I realized... damn, you can't do an interview question in Clojure 😛 And I was trying to get back to using Java and I kept being like.. Where are the functions I need!

seancorfield06:06:35

I tried to introduce Scala but that didn't suit the other team members. So I tried Clojure, and that did.

seancorfield06:06:16

So we slowly switched from CFML to Clojure, trading one dynamically compiled JVM language for another 🙂

didibus06:06:50

Also people are like... Given an ArrayList... can you modify it in place. And you're like... You mean HashArrayMapTrie 😉

didibus06:06:17

Oh ya, you told me CFML's latest versions were actually JVM based

seancorfield06:06:49

Consequently, our CFML code contains stuff like this in a few places:

var cols = clj.clojure.string.join(
            ",",
            core.map(
                core._name(),
                core.keys( core.first( items ) )
            )
        );
which is (clojure.string/join "," (map name (keys (first items)))) 🙂

didibus06:06:19

That's pretty cool that you can do Clojure interop from CFML

seancorfield06:06:20

(the weird _...() syntax just gets a Var reference)

the2bears06:06:17

Heh... Late Night with Clojure, very interesting episode tonight!

didibus06:06:23

We have people using Clojure libs from Java and Scala. Clojure is really easy to use from other JVM languages, its a great strenght

seancorfield06:06:34

Our entire process for the last eight years has been based on CFML/Clojure interop. But we've retired our biggest CFML app now.

the2bears06:06:43

I've run a Clojure REPL in a Scala microservice.

didibus06:06:20

Ya, its one way though... Scala from Clojure is not that easy

didibus06:06:45

That's why you should move from "other jvm lang" to clojure only 😛

the2bears06:06:14

Can't imagine a reason to try, either. Or rather, can't think of anything I'd want to use from Scala in Clojure

seancorfield06:06:32

Yeah, we have a live Socket REPL in our last remaining CFML app 🙂

seancorfield06:06:21

(I just pushed the latest cfmljure.cfc from our work codebase but the rest of the repo is about two years out of date)

didibus06:06:18

So, when using CFML, do you still use the ColdFusion IDE ?

seancorfield06:06:15

@didibus Hahaha nope. I used Emacs for a few years, then Atom.

seancorfield06:06:34

@deleted-user Look at clojure.java.shell I think

didibus06:06:50

Ya, clojure.java.sh is great

didibus06:06:54

super easy

didibus06:06:35

Just make sure you call System/exit or shutdown-agents when done

didibus06:06:48

Or your program will take an extra minute to quit

👍 4
didibus06:06:48

Ya, I think it can do that

didibus06:06:55

Otherwise what's the point of using agents

didibus06:06:09

But I never tried, so maybe not 😮

seancorfield06:06:45

No, clojure.java.shell/sh assumes the process ends.

seancorfield06:06:05

So you'll probably have to use Java interop and some standard Java libraries?

seancorfield06:06:06

java.lang.ProcessBuilder is probably what you want.

seancorfield06:06:31

java.lang.Process lets you get an OutputStream which you could read as it becomes available.

seancorfield06:06:18

Lets you get program output as a lazy sequence of lines.

didibus06:06:37

It seems it returns stream, but I'm not 100%

seancorfield06:06:54

Just use Conch 🙂

seancorfield06:06:18

I wasn't sure if it had been forked into clj-commons, so I had to go check.

seancorfield06:06:59

(and with that, my work here is done... time for bed!)

seancorfield06:06:29

Yeah, pretty much anything in Clojure that uses concurrency will require (shutdown-agents) or an explicit (System/exit n) call.

pinkfrog07:06:17

@didibus i see. i didn’t notice the java overloads.

didibus07:06:59

Wow, joker is pretty neat for quick scripts

didibus07:06:11

beyond a linter

Suni Masuno14:06:29

If I want to read the implementation of the thread macros where would I normally go look for that? (I'm less interested in the code than I am the skills to go find those sorts of answers myself in the future) ^_^

dpsutton14:06:10

(source ->) could be handy 🙂

Alex Miller (Clojure team)14:06:45

and try expanding examples (clojure.pprint/pprint (macroexpand '(-> {} (assoc :a 1) keys first val)))

Suni Masuno14:06:22

I really need to pick up the habit of using the repl analysis tools more in general I'm finding. ^_^

💯 4
Drew Verlee14:06:03

is it always possible to turn a recursive function into a tail recursive function?

Aron14:06:24

Possibly wrong question, but I am very much out of the loop. How safe is to use the python version of the transit library, in production? It seems quite abandoned but I know that can be misleading…

Alex Miller (Clojure team)14:06:08

it's not abandoned, Cognitect supports it

Alex Miller (Clojure team)14:06:23

is something not working?

Aron14:06:43

no, just that it didn’t looked active, but if it is supported that’s a different question

Aron14:06:18

thanks for the answer btw 🙂 i thought I started with this 🙂

Alex Miller (Clojure team)14:06:48

it works, and the spec isn't changing, so... no active work happening. But if there's an issue, we have people that will look at it.

Aron14:06:16

hopefully there will be no issues 😉

andy.fingerhut14:06:40

I am pretty sure the answer is no, although there might be some pretty wild contortions or modifications to a program that theoretically make it possible. The kind of example function that I do not see a clean way to turn into a tail recursive function is one like Clojure's tree-walk, or in general anything where the return value is the result of two or more different recursive calls.

Drew Verlee14:06:25

> return value is the result of two or more different recursive calls. Yep that's exactly what i have.

andy.fingerhut14:06:41

If a recursive function calls itself only one time in its body, then I think it might be possible in general to transform it into a tail recursive version.

Drew Verlee14:06:21

time to switch to haskell! 🙃

andy.fingerhut14:06:24

Are you trying to avoid stack depth explosion? i.e. is that the reason you are looking into writing a tail recursive version?

andy.fingerhut14:06:03

Yeah, what @U0C7D2H1U said could be a way to help avoid stack depth increase, even if you do not write a tail recursive function.

dpsutton14:06:18

i'm reading that this is possible

dpsutton14:06:27

but just may not be easy

dpsutton14:06:33

you get into CPS style coding quickly it seems but it can be made tail recursive

andy.fingerhut14:06:40

You can in general I think create your own stack of values (or sets of values) that represent what the recursive function would put on the stack for you, and write a non-recursive version of the function that behaves identically.

Drew Verlee14:06:13

i'm working through a discrete optimization class and the first lesson is doing the knapsack problem using dynamic programming. I put together a recursive solution but it needs to be memoized and additionally i assume it would be ideal to make it tail recursive. Though the later might not matter... its unclear to me.

tavistock14:06:11

can you post a gist of what you have

andy.fingerhut14:06:13

Clojure's core.memoize library makes it pretty trivial to memoize a function, if it is a pure function.

tavistock15:06:15

I memoized it, im not sure I can make it tail recursive, how would haskell do this? https://gist.github.com/Tavistock/a1c3adf631dbf07de36765beda65038f

Drew Verlee15:06:18

when i mentioned haskell i was mostly joking, i believe haskell has general recursion optimization, due to the order not mattering. Thanks! ill take a look. Oh yea, that makes sense. I think i have to do a bit more work to make the memoization help. specifically im calling each function with the current items that are being put in the knapsack, which would reduce the number of cached hits. possible all of them 😕 No worries, this the top down approach which is easier to reason about but, apparently, very hard to optimize.

hiredman15:06:42

You can make any function tail recursive using continuation passing

didibus16:06:32

Is it the algorithm that can't be made tail recursive? Or just struggling to have it use TCO in Clojure?

didibus16:06:29

Not all algorithms can be tail recursive, sometimes you do need to keep a stack to backtrack too and pop

dpsutton16:06:22

you can go CSP style and they can be tail recursive

hiredman16:06:51

the entire point of csp is for all calls to be tail calls. but just because a call is a tail call that does not mean it is optimized, for example clojure doesn't have tail call optimization. you can recur from a tail position and get a constant size call stack (which is what tco does), but tail calls don't get any guarantees there

hiredman16:06:52

so if you want an algorithm to have a constant size call stack in clojure you need to go beyond transforming things in to tail recursive form

hiredman16:06:27

and either use trampolines or something more like an abstract machine

hiredman16:06:31

but if you have something that is all tail recursive (because it has been cps transformed) making it trampoline is usually trivial

hiredman16:06:10

but memoizing with cps has some hazards

👍 4
didibus18:06:34

That's not what I meant

didibus18:06:06

I meant that some algorithms actually need a stack.

didibus18:06:13

No way around it

didibus18:06:28

In those cases, there is nothing to optimize

didibus18:06:42

So TCO can't be applied

didibus18:06:08

Because TCO is just a way for recursive calls to disregard the stack

didibus18:06:21

But if you need it, you need it 😋

didibus18:06:21

Otherwise, isn't trampoline basically continuation passing style?

didibus18:06:35

Is there any TCO trampoline can't do?

didibus18:06:05

I don't think so. What else is there beyond self recursion and mutual recursion?

hiredman18:06:46

there is needing a stack and consuming the call stack

hiredman18:06:13

consuming the call stack is kind of implicit thing and there can be restrictions and limitations to the call stack, where cps basically makes the call stack as an explicit thing, and often in the process moves data from the call stack to the heap, which is often a more free wheeling place

hiredman18:06:13

trampoline wise the issues are more about the mechanics of the trampoline, for example clojure's built in clojure.core/trampoline has issues if the ultimate value you return is a function

hiredman18:06:46

with trampoline you are still representing the program state as clojure function objects

hiredman18:06:19

if you move to something more like an abstract machine, you can have the entire machine operate in a loop/recur and represent the machine state as fungible data instead of relatively opaque closures

didibus18:06:37

Ya, trampoline is quirkier to use, but I think the quirks can be worked around if you're willing to do it

didibus18:06:28

The way I see it, trampoline is a kind of abstract CPS machine

didibus18:06:25

And ya, I mean that. The first question to ask is, does this solution need a stack. If it does, what stack do you want to use? If it doesn't, and you're doing recursive function calls, then you need to figure out how you're going to optimize the call stack away

didibus18:06:00

So like, sometimes you need to rewrite the algorithm so it is applicable for TCO

didibus18:06:35

And sometimes that's not possible

Suni Masuno17:06:20

So I finally understand the difference between -> and as-> when expanded, with -> nesting forms and as-> setting up a let with each step. My question is, why do they do it differently? Is there some trade off between the two approaches? Or are those transparent to the compiler?

Alan Thompson17:06:28

They both have the same result.

bronsa17:06:24

lets are mostly immaterial in the compiled code, using lets in as-> leads to a simpler implementation

Suni Masuno17:06:03

So, seeking to achieve deeper enlightenment on quoting and macros, I thought to build my own. But I can't seem to understand how a symbol is getting resolved...

1 (defn-                                                                                                                                                                
  2   placeholder
  3   "put __ in as first arg if none present"
  4   [form]
  5   (conj (rest form) '__ (first form))) ;;if not yet written...
  6   
  7 (defmacro =>
  8   [expr & forms]
  9   (let [new-forms (map placeholder forms)]
 10   `(let [__ ~expr
 11          ~@(interleave (repeat '__) (butlast new-forms))]
 12      ~(if (empty? new-forms)
 13         name
 14         (last new-forms)))))
is giving me Exception Unsupported binding form: (quote com.joe.jane.forms.macro/__) clojure.core/destructure/pb--5167 (core.clj:4315) When I was expecting that the quote on line 10 would "force" the to be in the later namespace... What am I missing about quoting here?

noisesmith17:06:31

the ` on line 10 turns into a namespaced form, but the code on line 11 should be OK

Suni Masuno17:06:58

Is there a way to avoid it namespacing it?

noisesmith18:06:30

the usual idiom is

~'__

Suni Masuno18:06:21

So really what I was missing there was the differences between backtick and quote

Alan Thompson18:06:24

Quote disables symbol substitution for a form. Backtick is similar, but also replaces plain symbols with fully namespaced symbols (e.g. "filter" => "clojure.core/filter")

noisesmith18:06:00

(as well as allowing unquoting / splicing)

seancorfield18:06:13

it-> is just as-> with the symbol name fixed to it

seancorfield18:06:10

(as-> 1 it 
      (inc it)
      (+ it 3)
      (/ 10 it))

Alan Thompson18:06:31

but I hate the "backwards" nature of as-> . 😞

seancorfield18:06:38

It's mostly designed to be used in -> pipeline

noisesmith18:06:00

yeah, all the arrow macros are designed so -> can be their parent, and you can mix them

seancorfield18:06:06

(-> 1
    (inc)
    (+ 3)
    (as-> it (/ 10 it)))

Suni Masuno18:06:35

I mean, I made a version that's a knock off of ramda's, not sure if that's good but I'm all kindsa tickled to have made it work.

Suni Masuno18:06:04

1 (ns whatever-you-want)
  2 
  3 (defn- in?                                                                                                                                                            
  4   "true if coll contains elm"
  5   [coll elm]
  6   (some #(= elm %) coll))
  7     
  8 (defn- 
  9   placeholder
 10   "put __ in as first arg if none present"
 11   [form]
 12   (if (in? form '__)
 13     form
 14     (conj (rest form) '__ (first form))))
 15 
 16 (defmacro =>
 17   [expr & forms]
 18   (let [new-forms (map placeholder forms)]
 19   `(let [~'__ ~expr
 20          ~@(interleave (repeat '__) new-forms)]
 21      ~'__)))

noisesmith18:06:20

small suggestion - if you are relying on the prefix behavior on lists, it's often more readable to use cons instead of conj

noisesmith18:06:36

that way we get the same ordering of elements in the form as the output list

noisesmith18:06:34

or perhaps here concat

noisesmith18:06:52

(concat [(first form) '__] (rest form))

noisesmith18:06:39

or (cons (first form) (cons '__ (rest form)))

Suni Masuno18:06:40

I think concat wins the beauty contest there, but I'm not 100% sure. ^_^

Suni Masuno18:06:02

I found myself wanting to write a "plug in" helper there

Suni Masuno18:06:13

any idea why (list (first form) '__ (rest form)) is bad?

Suni Masuno18:06:09

cause rest puts it in a list... dang

Suni Masuno18:06:09

and now I miss my ... from JS

noisesmith19:06:46

ahh, I forgot list* and that does precisely what we want

noisesmith19:06:49

user=> (list* 'a 'b '(c d e))
(a b c d e)

noisesmith19:06:04

so (list* (first form) '__ (rest form))

Suni Masuno19:06:10

❤️ ❤️ ❤️ ❤️

nickmbailey20:06:03

what are people's thoughts on using datafy to parse java object models into data representations for returning back via an api

nickmbailey20:06:33

for example i'm playing around with something like that for the google maps java lib:

(extend-type DirectionsLeg
  p/Datafiable
  (datafy [this]
    {:arrival-time (.arrivalTime this)
     :departure-time (.departureTime this)
     :distance (.inMeters (.distance this))
     :duration (.inSeconds (.duration this))
     :duration-in-traffic (.durationInTraffic this)
     :end-address (.endAddress this)
     :end-location (LatLng->vec (.endLocation this))
     :start-address (.startAddress this)
     :start-location (LatLng->vec (.startLocation this))}))

(extend-type DirectionsRoute
  p/Datafiable
  (datafy [this]
    {; ignoring until we need to draw things
     ;:bounds (.bounds route)
     :copyrights (.copyrights this)
     ;:fare (process-fare (.fare route))
     :fare (.fare this)
     :legs (map datafy (.legs this))
     ; ignoring until we need this to draw things
     ;:polyline (.overviewPolyline route)
     :summary (.summary this)
     :warnings (.warnings this)
     :waypoint-order (.waypointOrder this)}))

(extend-type GeocodedWaypoint
  p/Datafiable
  (datafy [this]
    {:partial-match (.partialMatch this)
     :place-id (.placeId this)
     :address-types (map str (.types this))
     :status (str (.geocoderStatus this))}))

(extend-type DirectionsResult
  p/Datafiable
  (datafy [this]
    {:waypoints (map datafy (.geocodedWaypoints this))
     :routes (map datafy (.routes this))}))

nickmbailey20:06:29

previously i just had processing functions that did the same thing. i like this better because its immediately clear what java object something is operating on where as before that would only be clear if you added type hints

nickmbailey20:06:28

but i suppose there's the chance for causing some confusion by making third party objects datafiable

nickmbailey20:06:03

but this is just internal application code not a library so seems lowish risk

Alex Miller (Clojure team)20:06:12

seems fine. you probably want to type hint this or you're using reflection for all of that

bronsa21:06:37

I think it's type hinted automatically by the extend-* macros

nickmbailey20:06:20

seems fine as in its not totally bonkers or seems fine as in that could be a reasonably common pattern 🙂

Alex Miller (Clojure team)20:06:55

seems like a common thing already being done in many places to translate from Java objects to Clojure data. might as well do it under the datafy protocol

ghadi20:06:14

don't extend Datafiable to things not under your control in a library

ghadi20:06:51

you can extend it to whatever if you're building an application

ghadi20:06:47

extending to com.foo.geo.GeocodedWaypoint is fine if your library pulls in and owns all interaction with com.foo.geo

ghadi20:06:31

also shouldn't extend Datafiable to platform things like java.time.LocalDate

ghadi20:06:40

you can always duplicate the protocol under a different name if you need to extend it to things you don't own

nickmbailey20:06:56

yeah doing it in a library sounds like a recipe for confusion

ghadi21:06:26

I've written Datafiable a bunch of times under the name IShredJava with a single method shred

👍 4
nickmbailey21:06:38

@alexmiller just as an fyi, the docs say that extend-type automatically adds the type hints to 'this' for me

nickmbailey21:06:07

oh someone already said that in a thread, whoops

Alex Miller (Clojure team)21:06:31

well then nvm! :) I forgot that

CyberSapiens9721:06:11

Hello guys, I don’t know if this is the right place to ask for an opinion on my case here, but anyway: Me and 2 friends started on the beginning of this year to study together, Design, Front-End and Back. I’m on the back with clojure... And we have finished a small web app that downloads musics and playlists from YouTube. Now, we are going to start a second project, but here’s the thing, I need to build an app which will be scalable, fully tested and ready for production, including DevOps tools and best practices. But this app needs to serve two purposes: first it needs to be something that we can use as an example of an app built by us, to show to possible clients in the future (so they won’t think we are starting out of nowhere) and this needs also to be something I can use myself to show if I need in the future a regular job with clojure. (Btw I’m going to document every bit of this project on a personal blog with daily posts, with videos too, to show my Learning path along the way.) So, we don’t know what kind of clients we can come across on the future (likely that will be small systems using web clients, small because it’s only me at the back end for now) but we don’t know what kind of system we would need to build, so the web app we are going to create now, should be something that suffices as a real world example and that also gives us more knowledge for the upcoming future. What you would suggest for us to build? What kind of app? Would any kind be enough for this? I have some ideas but I’m not sure if will be enough to show off...

noisesmith21:06:47

start with the industry you are trying to work in, and find something that incorporates the standard tech and practices you'd see in that field

CyberSapiens9721:06:48

That’s a nice way to see this, we don’t really know what industry we are going to get in, but we have to chose something that’s suitable for a super small team, at least for now

noisesmith21:06:38

I'd be more impressed by something that uses the tech stack and practices of my field, I think you'll make better progress if you specialize

noisesmith21:06:19

(if your definition of success is impressing someone and getting a contract)

CyberSapiens9721:06:06

It’s not but for now it has to be because of money issues haha I’ll definitely use clojure, we are trying to find what we can explore that will be suitable for our small team. We only have 1 guy in front and me at back, and with no technical real world knowledge, we have a vague sense of what is too big for us

Graham Seyffert22:06:12

can someone here help illuminate me as to why emitUnboxed might get called instead of emit on a StaticMethodExpr? I’m assuming it has something to do with the method signature?

Graham Seyffert22:06:22

In Compiler.java

bronsa22:06:13

if the method is called in a position that allows for primitive return, then you'll get emitUnboxed

Graham Seyffert22:06:17

It seems that both emit & emitUnboxed get called during compilation for the method in question

Graham Seyffert22:06:01

When you say “in a position”, you mean e.g. (if (MyClass/myMethod ...) ...)

bronsa22:06:22

or (.method inst (.othermethod inst)) for example

Graham Seyffert22:06:37

vs. (fn [& args] (MyClass/myMethod ...))

bronsa22:06:49

yes, or (identity (.method inst))

Graham Seyffert22:06:32

Thanks, that’s helpful!

hiredman22:06:42

if you are chasing down some problem and haven't turned on reflection warnings I would suggest starting there instead of digging through the compiler

Graham Seyffert22:06:02

We fail compilation on any reflection warnings 🙂

Graham Seyffert22:06:13

You should see the gratuitous type hints in our code

Graham Seyffert22:06:41

It’s the life of Java -> Clojure -> Java unfortunately….

bronsa22:06:10

you can look for canEmitPrimitive impls in Compiler.java for all the paths where emitUnboxed can be used

Graham Seyffert22:06:12

I’m mucking around in the compiler because I’m getting IncompatibleClassChangeErrors saying Method {myMethod} must be InterfaceMethodref constant

Graham Seyffert22:06:53

Calling this method in my REPL works just fine… but calling the function in our code with that method in my REPL on JDK 11 throws that error

Graham Seyffert22:06:05

It looks to me like this test should trigged the emitUnboxed code path? - must be InterfaceMethodref constant

Graham Seyffert22:06:54

Haha oh man. I fixed the error. Hallelujah

Graham Seyffert22:06:04

Anyone that’s a contributor want to submit a patch for me? Lol

hiredman22:06:58

not sure what you mean, in that tast the result of the method calls are then passed to a function =, so not emitUnboxed

Graham Seyffert22:06:27

yeah that makes sense

Graham Seyffert22:06:38

There’s a bug in emitUnboxed

Graham Seyffert22:06:56

for StaticMethodExprs

hiredman22:06:13

sorry, reverse that

hiredman22:06:41

double reverse it

Graham Seyffert22:06:46

For some reason I thought = was a built-in form like if

bronsa23:06:31

it kind of is, sometimes -- if it's in the appropriate position and over the appropriate types it can get inlined and intrinsified as a direct *CMP bytecode

Graham Seyffert22:06:54

I’m not sure why

hiredman22:06:22

if doesn't cause emitUnboxed either, because it has to handle anything (Object), true, false, and nil

hiredman22:06:14

if will sometimes result in emitUnboxed being called if the test can emit a primitive boolean

Graham Seyffert22:06:22

Well, I can reproduce by putting this method as a condition to if. It will spit out this error at runtime

Graham Seyffert22:06:48

I changed the code in emitUnboxed and now if works properly

bronsa22:06:54

what's the diff?

hiredman22:06:02

my guess is you are running with slightly different versions of some class at runtime and at compile time

Graham Seyffert22:06:28

Compiler.java line 1802

Graham Seyffert22:06:36

gen.invokeStatic(type, m); -> gen.visitMethodInsn(INVOKESTATIC, type.getInternalName(), methodName, m.getDescriptor(), c.isInterface());

Graham Seyffert22:06:07

It’s the same fix that was applied to emit for this class

Graham Seyffert22:06:44

let me see if I can break Clojure’s unit tests

bronsa22:06:55

makes sense

Graham Seyffert23:06:28

I can repro on the unit test too

Graham Seyffert23:06:43

oof, this took me longer to figure out than I’d like to admit

Graham Seyffert23:06:54

I thought it was an issue in clojure-maven-plugin for so long

bronsa23:06:02

I can make a ticket + patch for you if you want, but registering for a CA should be easy nowadays if you want to do it yourself

Graham Seyffert23:06:11

Honestly not sure if my company would let me contribute even if I asked nicely

Graham Seyffert23:06:41

I can get a diff together though with a unit test

bronsa23:06:56

it's ok I'm on it

bronsa23:06:16

thanks for finding out about it

Graham Seyffert23:06:17

For the unit test, I just added -

bronsa23:06:32

nevermind looks like I can't login to the new jira 🙃

Graham Seyffert23:06:58

That last case breaks w/out the patch to Compiler.java

Graham Seyffert23:06:57

I think this line needs to be staticMethod2? -

+    (is (= 1 (if (compilation.JDK8InterfaceMethods/staticMethod0 true) 1 2)))))

Graham Seyffert23:06:23

Thanks for doing that 🙂 much appreciated!

bronsa23:06:19

so it does, it's a bit too late for doing this :) thanks