Concat breaks Inference

While using Cons is fine when mixing different Types (it will assume the inner bound, Object in this case), doing the same breaks the ConCat node.

VL will refuse to compile the patch, because there are errors.

When using nested generics in the Cons node instead, it falls back to something called UnTyped (instead of Object), but that can be fixed with a CastAs.

My original problem had the nesting one step deeper, and a slightly different error message (but definitely the same problem, that ConCat expects exactly the same type for all inputs.

I believe this is because the ConCat node has all Input pins on the same symbol T while the Cons node reserves a new one for each pin.

my original patch looked like this:

and, for the time being, this is my work-around

1 Like

hey Marco!

it’s really, really tough to get this one right and to explain properly what’s going on.

So first of all: VL is a .Net language in a sense that it inherits the characteristics of the .Net type system. It adds some ideas to that, but most importantly here: it inherits the pros and cons of the underlying system.

When .Net generics got introduced back in the days with .Net 2.0, they added a feature that let you reason a bit more about your types at compile time. So by using generics, a library developer could now trade runtime errors for compile-time errors, which basically means that the end user would stumble upon incorrect usage rather earlier. But it is worth noting that they didn’t add a feature that suddenly would enable more programs than by using .Net 1.0, it would allow less.

They did some little thing different than the to-be-copied Java and it’s deep in there in the .Net way of thinking generics: it is the way how they treat value types, like Float32, Integer64… Java just treated types like Foo<T> in a way that all occurrences of T would reserve some memory for a proper whole object (which internally comes with some pointer to the runtime type in order to look up virtual methods or for support some runtime type checks on that object). If that Foo<T> just comes with a field T MyField, well .Net just behaves exactly the same. For the runtime behavior, it is just like using an object MyField. (again: Using generics only additionally allows to reason a bit more at compile time (for all possible runs to come))

Soo, where again is the difference between .Net and Java in regards to generics?
Here it comes: Java really always made sure that any occurrence of T would behave like a proper object, even if you have an array of T inside your Foo, in which case it is like having an array of objects inside (with added compile time info that there are only Ts inside), which is pretty good from a theoretical position: Barbara Liskov - Wikipedia formalized these intuitions in her Liskov substitution principles (back then girls were the dominant gender in computer science), but it is bad if you want to care a lot about performance and memory. So in Java you basically get an object array internally with each small Float32 being boxed into an object (somewhere else in memory, not directly in the array memory itself that is), each of which comes with RuntimeType info…

.Net on the other hand treats a Foo<Float32> special in a sense that it makes sure that any T array within Foo<T> actually turns out to be a Float32 array, where this Float32 info is stored once for the whole array, and each float sits directly in the array memory. This might be genius from a performance and memory point of view, but it breaks some intuitions concerning subtyping:

A Float32 or Integer64 are told us to be objects. But actually they only behave as objects when you can get hold onto the instances themselves: You can run Object.ToString() on them or ask them for their type.

But as soon as you use them in generics, the .Net type system treats them as not being objects.

An example: Sequence<T> is covariant, which is like saying: if Banana is a Fruit than a Sequence of Bananas is a Sequence of Fruits. Works.
However Float32 is claming to be an object. But a Sequence of Float32 is not a Sequence of Objects:

Actually, we probably could do this differently. Now that i explain this to me another time: Yeah, gosh, we probably could have our own notion of value tpyes, that are just boxed versions of the .Net value types. So we would have the same .Net typing system, but a VL user would never come in touch with the original .Net value types (like Float32). This however comes with the exact small issue that at some point we’d need to do the boxing and unboxing, when talking to SharpDX or whatever library to get the boxed value into a value type and back again.

I attach a small patch that shows what i mean with boxing.

boxingTests.vl (26.4 KB)

Note, that if you want to use a composed type like OuterType<InnerType> and you want to use inheritance intuitions to work for the inner type, your outer type needs to be covariant, which the .Net type system only allows to be specified for interfaces (and delegates). See IEnumerable<T> Schnittstelle (System.Collections.Generic) | Microsoft Learn for an example (the generic Sequence). The type parameter comes with the keywork out, which says you can only read items with that interface, in which case it is save to say that a OuterType<A> is an OuterType<B>, if A is a B. Just saying. A Command<Float32> would only be a Command<Object>, if Command is covariant and Float32 is an object, which probably both is not the case for now.

Sooo. What to learn, what to do?
I wonder if it would be an option to just introduce a ungeneric interface ICommand, which is implemented by your generic command class. This ungeneric version then is perfect for putting all kinds of differently typed commands into one big spread. As this spread now contains all sorts of commands you’d at some time test for the actual type which you could do with a foreach and a cast where you try the different commands.

Not sure if this answers your question. Hoping i could shed some light onto subtyping, generics, covariance and the problem of value types in relation to the former features…


When using nested generics in the Cons node instead, it falls back to something called UnTyped (instead of Object), but that can be fixed with a CastAs.

What do you mean with “in the Cons node”?

Note: You probably talk about type Sequence (Untyped) which is basically the ungeneric IEnumerable from .Net. So it is a sequence that just expresses that is about a sequence, but just doesn’t care about the element type. If think about it this is a very elegant shortcut. Instead of talking about a generic Sequence where you use object as element type (by that trying to make subtyping work for the inner type), just don’t use the feature of generics in that case (then you only need to reason about the outer type and yes any Sequence of T is also a Sequence). Basically this is exactly what i proposed for your Command. Make it implement an ungeneric variant of it for the cases where you just want to think about commands, but not so much about what exactly they store.

Note that the Cons example works as the cons can get a grip onto the single elements themselves, box them and put them into a Sequence of objects…

3 Likes

Thanks for the primer. I hope it will serve to enlighten more than just me.

I understand you are saying, because there is no such thing as an IObject, the vl inference mechanism cannot infer safely anything until I hide all generic pads of my Command record behind an ungeneric interface.

How do I make a VL interface? Are there examples? Because it still seems an experimental feature (greybook wise), I have no grip on them at all.
Do I need to provide getter and setter (headers) in the Interface, or even pads? Or is it just the flat name of the Interface that will matter?

1 Like

Ah i see, your Command is patched. nice.

Well alright then, let’s try the interfaces in VL. But note that we are still working on the way we support interfaces. That’s why the documentation is lacking still.

Actually, for the typing idea to work ( Sequence < ICommand > ), you don’t need any operation in your interface, no getters, no setters.

You can just create an empty interface in your document patch, name it and make sure it is empty (Create and Update should not be in there) See ICommand in the patch.

In your generic Command patch, you now can just implement that empty interface by adding it to the list of interfaces. As this interface only establishes the idea of the type without any specific operation to implement there nothing more to do than just adding it to the list. (Note that very small interfaces, even empty interfaces can make sense. They are only there to capture some simple idea, the simpler, the better as you can collect the ideas and implement different interfaces on one type…)

You now can construct a Sequence < ICommand > like shown in the Root.

boxingTests.vl (52.2 KB)

Making me try to explain the issues also triggered some other idea. Because, seriously, we can’t expect our users to be aware of all this. So another way out is sketched inside: A Concat working on ungeneric sequences. Maybe this will be added to the corelib in the future. You’d then just work with Sequence (Untyped) instead of Sequence < ICommand >, which might feel a bit too ungeneric, but yeah, it’s just another solution to have a look at.

2 Likes

boxingTests.vl (89.2 KB)

started to make sense of it, putting it into a different perspective, but the patch stopped working after I tried adding my own color candidate to the adaptive Boxing.

left some breadcrumb documentation, so it can do some proper teaching about the three alternatives (once it is working as proposed, hehe)

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.