Would love some help finding the simplest way to recreate the following code in ClojureCLR (for testing purposes)
public static bool IsBoxed<T>(T value)
{
return
(typeof(T).IsInterface || typeof(T) == typeof(object)) &&
value != null &&
value.GetType().IsValueType;
}
If it needs to be C# code that is :imported into ClojureCLR, that's fine but ideally it's local and co-located. Building the standalone library should be fine but not sure how to setup ClojureCLR to find it on the classpathAlso looking for a way to do assignment of a System.Object to a primitive value, i.e.
public static void Assign(System.Object o, long l)
{
o = l;
}
set! doesn't seem to work for thisFor IsBoxed, I don't know how to create the exact equivalent of a generic method in ClojureCLR.
If you are willing to take an argument instead of a generic type parameter, this might work:
(defn boxed? [^Type t v]
(let [cv (class v)]
(and v
(.Contains (bases cv) t)
(or (.IsInterface t) (= t Object))
(.IsValueType cv))))
Sample use:
(boxed? IComparable #inst "2010-11-12T13:14:15.666-06:00") ; => true
Annotated
(defn boxed? [^Type t v] ; passing Type t replaces generic type argument
(let [cv (class v)]
(and v ; value != null
(.Contains (bases cv) t) ; verify v implements the interface, implied by signature
(or (.IsInterface t) (= t Object)) ; (typeof(T).IsInterface || typeof(T) == typeof(object))
(.IsValueType cv)))) ; value.GetType().IsValueType
I think this captures the intent.Got something working with assembly-load-from as well but wondering if cljr ever got something with :import going for local DLLs
Regarding assignment of an object to a primitive value, I need more context.
set! won't work on a local binding.
(There is an exception to this rule in ClojureCLR. We have to allow the setting of by-ref function parameters.)
In what context are you trying to make an assignment?
(The Assign method shown is a no-op. It has no effect on the world.)
I'm trying to write tests for num from clojure.core, for ClojureCLR. I had to do some shenanigans with the Clojure variant to get observable/testable behavior
num is a bit tricky. "Coerce to Number" is not meaningful in ClojureCLR in any direct sense: there is no equivalent to the class Number. Technically, I suppose, I could have had it throw an exception. Instead it is the identity function. What kind of test are you trying to write?
Something to showcase its ability to box a primitive
using System;
public class HelloWorld
{
public static bool IsBoxed<T>(T value)
{
return
(typeof(T).IsInterface || typeof(T) == typeof(object)) &&
value != null &&
value.GetType().IsValueType;
}
public static void Main(string[] args)
{
System.Int64 l = 13;
System.Object o = new object();
Console.WriteLine (IsBoxed(o));
o = l;
Console.WriteLine (IsBoxed(o));
Console.WriteLine (IsBoxed(l));
}
}
My assumption would be that num would change the output of one of these calls from IsBoxed => false to IsBoxed => trueGiven the following outputs
False
True
FalseYou cannot pass num an unboxed primitive. The parameter in the underlying code is typed as Object, so any call to it with a primitive will already have boxed the primitive.
Where you could say ClojureCLR's num is incorrect is that it does not throw exceptions where ClojureJVM does, as for example in (num "12") : ClojureJVM throws, ClojureCLR just returns the argument. Perhaps ClojureCLR should throw in this case. ClojureCLR cannot implement num: there is no Number type.
> The abstract class Number is the superclass of platform classes representing numeric values that are convertible to the primitive types byte, double, float, int, long, and short. The specific semantics of the conversion from the numeric value of a particular Number implementation to a given primitive type is defined by the Number implementation in question.
To which the CLR would probably have to add sbyte, uint, ulong, ushort , and (maybe?) decimal.
However, the direct known subclasses in the JVM (8) docs are:
> Direct Known Subclasses:
> https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicInteger.html, https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicLong.html, https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html, https://docs.oracle.com/javase/8/docs/api/java/math/BigInteger.html, https://docs.oracle.com/javase/8/docs/api/java/lang/Byte.html, https://docs.oracle.com/javase/8/docs/api/java/lang/Double.html, https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/DoubleAccumulator.html, https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/DoubleAdder.html, https://docs.oracle.com/javase/8/docs/api/java/lang/Float.html, https://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html, https://docs.oracle.com/javase/8/docs/api/java/lang/Long.html, https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/LongAccumulator.html, https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/LongAdder.html, https://docs.oracle.com/javase/8/docs/api/java/lang/Short.html
I just gave up and implemented num as the identity. The only way I could check if value is convertible is to actually convert it. I cannot check for having Number as a base class.
At any rate, I'm not sure how you would test that num returns a boxed value. It returns an Object, which is a reference, and hence boxed if primitive.
The call IsBoxed(l) returns false not because the value is not boxed, but because Int64 is not an interface. The test
(typeof(T).IsInterface || typeof(T) == typeof(object))
fails.
Because this is a generic method, the value of the type parameter T on compilation of a call to IsBoxed is going to reflect the apparent type of the argument at the callsite.
If you wanted to get way behind the scenes, you can write unsafe C# code, grab the pointer and decode the boxed value. That seems a bit extreme to test something that cannot be otherwise: If you call num, it gets boxed value and must return a boxed value.yeah my goal is basically to either document some observable behavior via a test or write a comment explaining that num is a no-op, if that's the case 100% of the time for ClojureCLR
It's a no-op.
I will say, I'm surprised num doesn't do this in cljr, like it does in bb
user=> (num (fn []))
java.lang.ClassCastException: sci.impl.fns$fun$arity_0__1138 cannot be cast to java.lang.Number [at :1:1] I think it should at least throw for non numeric values but may be too late for that
> What am I supposed to cast it to?
on the JVM, you can check that the base class is Number.
But that includes lots of things:
> https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicInteger.html, https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicLong.html, https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html, https://docs.oracle.com/javase/8/docs/api/java/math/BigInteger.html, https://docs.oracle.com/javase/8/docs/api/java/lang/Byte.html, https://docs.oracle.com/javase/8/docs/api/java/lang/Double.html, https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/DoubleAccumulator.html, https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/DoubleAdder.html, https://docs.oracle.com/javase/8/docs/api/java/lang/Float.html, https://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html, https://docs.oracle.com/javase/8/docs/api/java/lang/Long.html, https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/LongAccumulator.html, https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/LongAdder.html, https://docs.oracle.com/javase/8/docs/api/java/lang/Short.html
The only way I could test a value to see if it is convertible to a primitive numeric type is to actually try converting to each primitive numeric type and see if one of them works. That would be up to 11 attempted conversions on a call to num . That is how I read "platform classes representing numeric values that are convertible to the primitive types"
One could take a more prescriptive approach and explicitly list the types that are allowed.
That would be the primitive numerics plus BigInt, BigInteger, BigDecimal and Ratio. (See definition of Util.IsNumeric.)
In which case clojure.lang.Numbers.num(object o) would become
if ( !Util.IsNumeric(o)) throw new InvalidCastException("Not a numeric type");
return o;
But that didn't occur to me when I was porting the numeric subsystems back in the day.
That change does not break any tests.Yeah, numis an odd function so the existing behavior is understandable, but what you're suggesting in the latter case seems preferable and I imagine is unlikely to break existing ClojureCLR code even if it is a breaking change (since what folks were using before if they were using this in that way was undefined behavior essentially)
@dmiller after some testing it seems like num is not quite a no-op in ClojureCLR. num will upcast shorts and ints to longs (`System.Int64`)
also upcasts bytes to System.UInt64. Interestingly it doesn't seem to upcast floats
I did forget about that part. Thanks for noting that.
Likely floats should go to double -- isn't that the JVM result?
Upcasting bytes to System.Uint64 is a rather bizarre effect.
But it is related to several things:
• Not all numeric types are treated equally by num. the overloads for clojure.lang.Numbers.num are limited.
• On the JVM, byte is signed. On the CLR, byte is unsigned.
• It likely is the case that the promotion is to the only unsigned type surfaced for num on the CLR, namely, Uint64.
It might make more sense for this to be Int64 , that being the smallest surfaced integral type that can hold a byte.
Are the tests for this visible at this point in the repo? I'd like to take a quick look at how you are testing.
Yep! https://github.com/jank-lang/clojure-test-suite/pull/831
@dmiller https://ask.clojure.org/index.php/14766/how-to-use-core-async-from-cljr-command-line-tool
Done. Announcement in #clr and #releases.
Awesome thanks. Works a treat
Initial response made. I'll need your assistance adding deps.edn to tools.analyzer. I'll put this on top of my task list.
Thanks