clr

2025-11-25T04:42:18.912069Z

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 classpath

2025-11-25T04:44:32.728839Z

Also 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 this

dmiller 2025-11-25T15:16:07.942079Z

For 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.

1
2025-11-25T15:18:51.043789Z

Got something working with assembly-load-from as well but wondering if cljr ever got something with :import going for local DLLs

dmiller 2025-11-25T15:21:19.329679Z

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.)

2025-11-25T15:22:35.147019Z

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

dmiller 2025-11-25T15:33:27.747549Z

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?

2025-11-25T15:34:57.419919Z

Something to showcase its ability to box a primitive

2025-11-25T15:56:03.312209Z

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 => true

2025-11-25T15:56:30.877549Z

Given the following outputs

False
True
False

dmiller 2025-11-25T16:01:30.917899Z

You 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.

dmiller 2025-11-25T16:36:42.693189Z

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.

2025-11-25T16:44:29.293389Z

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

dmiller 2025-11-25T16:45:09.247549Z

It's a no-op.

👍🏻 1
1
2025-11-25T16:46:40.911829Z

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]

2025-11-25T16:47:14.182519Z

I think it should at least throw for non numeric values but may be too late for that

dmiller 2025-11-25T17:17:19.710829Z

> 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.

2025-11-25T17:19:40.333709Z

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)

2025-11-26T18:12:45.349889Z

@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`)

2025-11-26T18:22:03.917119Z

also upcasts bytes to System.UInt64. Interestingly it doesn't seem to upcast floats

dmiller 2025-11-26T19:05:34.661969Z

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.

dmiller 2025-12-02T04:01:34.182969Z

Done. Announcement in #clr and #releases.

jamesd3142 2025-12-02T04:09:42.844699Z

Awesome thanks. Works a treat

dmiller 2025-11-26T00:42:57.270729Z

Initial response made. I'll need your assistance adding deps.edn to tools.analyzer. I'll put this on top of my task list.

jamesd3142 2025-11-26T01:28:18.215059Z

Thanks