6
lorentz
4y

Generic arguments can't be cast. List<Dog> can't be cast to List<Animal>, because any methods that take Dog as an argument would suddenly have to work with an Animal (same works the other way round with return values).

But there are many situations where this would be okay. For instance, a Date can be cast to a String, so if we know that no method directly or indirectly accessible from a ListView<T> (including accessible property and field setters) will ever take an argument of type T, then ListView<Date> can be cast to a ListView<String>. Conversely, if we know that methods of StreamWriter will only ever take arguments of the generic type and interact in ways that don't change the object, then we can safely cast a StreamWriter<String> to a StreamWriter<Date>.

There could be a pair of generic constraints signifying that the type only crosses the interface boundary in one direction. I think this would be an interesting feature, but I don't know any strict type system that allows it. What do you think?

Comments
  • 4
    As those types are just meta - doesn't the need to cast it later mean you designed the meta wrong?
  • 2
    But List<Animal> can accept Dog. That's the OOP way and it's just shifting your definition from the data structure (in this case the list) to the function
  • 6
    What you want is structural typical and a hindley-milner type system with implicit eta-conversion support.
  • 1
    Afaik a Date cannot be cast to String :) you can call toString(), yes, but not cast it

    last I checked String is not extendable.
  • 1
    @netikras It was just an example.
  • 1
    @SortOfTested That's a lot of keywords, thanks!
  • 0
    @hubiruchi Yes, but if you have a list of dogs, you can't pass them to a method that expects a read-only list of animals.
  • 0
    @hubiruchi But you don't have a list of Animals, you know for a fact that it should only contain Dogs, and you don't want to downcast everytime you access it.
  • 1
    Oh god, this is giving me flashbacks to school learning about variants, invariants and contravariants
  • 1
    @Lor-inc yeah that's annoying
  • 2
    @Lor-inc
    I'm very much "lead the horse to water" 😘
  • 1
    @alexbrooklyn I sincerely hope that uni won't make me dislike the topic, because this is among my favourite topics in IT.
  • 3
    assuming you are speaking of java:
    I had this problem a few days ago and it's possible to still implement such generics by using a type parameter. so, for example, you can have

    public <T extends Animal> List<T> getBestAnimal(List<T> animals){
    animals.sort(Comparator.comparingInt(animal::getGoodnessScore));
    return animals;
    }

    this can then be called implicitly, e.g.:
    getBestAnimal (new ArrayList<Dog>());
  • 1
    @writeascript what I got upset with there was that even for interfaces, your write<T extends interface> even though you usually implement an interface, not extend it.
  • 2
    Kotlin has in/out modifiers just for that.
    https://kotlinlang.org/docs/...
  • 3
    @writeascript you do extend an interface when you define a more concrete contract layer, based on that interface. And it fits the bill here too -- with generics you do nothing but define a contract
  • 1
    @SortOfTested I'm having trouble seeing how the eta-conversion applies here. Do you mean extensional equality of functions instead? I.e. something along the lines of
    A x = B x -> A = B
  • 2
    @RememberMe
    Moreso suggesting ideas that will drive away from the current ask to more formalized methods that already accomplish similar things.

    My assumption without drowning the thread is that he would want to be able to infer that if something can indirectly become something else required by a function, that the reduction should become implicit. However that happens.
  • 0
    @SortOfTested My ask is that if something can become something else than a consumer of the result can become a consumer of the initial, assuming either reference conversion or that the consumer can't change the object.
  • 0
    ... but ia there any reason?

    if Dog can be cast to Animal, then whatever you want to do with the dog as an animal, you can do to ((Animal)dogList[index]), so why would you need to cast the whole list?
  • 1
    @Midnight-shcode Because I would probably have functions that take ListView<Animal> as an argument.
  • 0
    @Lor-inc hmm... there might be a way to overload ListView's implicit casting function?

    but ok, i kinda see your point.
  • 0
    @Midnight-shcode How would that happen? Implicit casts work by specifying an exact type, and it's at best a transitive relationship. You can't define a cast to "ListView<U> where (U)T is defined". Besides, ListView may be stateful so I might not want to cast it (which would result in it getting passed by value)
  • 0
    @Midnight-shcode I'm convinced that this is technically impossible to do in, say, Java, what I'm unsure about is whether the situation can always be avoided, and if so, at what cost. Object-oriented languages are notoriously bad in terms of boilerplate, and limiting something that can be programmatically proven possible doesn't help that.
  • 0
    I realised that Typescript does this, however it's implicit with no option to add it as an explicit constraint, so if I cast typeargs too much it will eventually turn into a minefield of implicit constraints where I can't be sure that a given method can be added to an interface without breaking casting semantics client code relies on.
Add Comment