#Runtime types vs Generics

1 messages · Page 1 of 1 (latest)

plain frigate
#

I'm working on a project and the question I keep running into is when to use generics. If the type returned by a function is determined at runtime rather than compile time, is there any use for generic methods?

obtuse sandBOT
#

<@&987246399047479336> please have a look, thanks.

#

Here is an AI assisted attempt to answer your question 🤖. Maybe it helps! In any case, a human is on the way 👍. To continue talking to the AI, you can use </chatgpt:1108714622413963314>.

#
TJ-Bot
I'm working on a project and the question I keep running into is when to use generics. If the type returned by a function is determined at runtime rather than compile time, is there any use for generic methods?

then you would typically use runtime types instead of generics. Generics are more commonly used when the type information is known at compile time.

Generics allow you to write reusable code that can work with different types without sacrificing type safety. They provide compile-time type checking and help in writing more flexible and maintainable code.

On the other hand, if you are dealing with situations where the type information is not known until runtime, such as deserializing JSON data or working with reflection, then using runtime types may be more appropriate.

Overall, the choice between using generics or runtime types depends on the specific requirements of your project and how the type information is determined. Keep in mind that using generics can help in catching errors at compile time, while using runtime types may provide more flexibility in certain scenarios.

It's important to understand the advantages and limitations of both approaches to make an informed decision based on your project's needs. Experiment with both options and see which one works best for your particular situation.

timber epoch
# plain frigate I'm working on a project and the question I keep running into is when to use gen...

There are two simple ways to return something that is of a type that you input.

  1. Generics. You declare the generic when declaring the method, and then you add it as a param, do something, and return it back. T represents anything that is passed and is only available during compile time.

  2. Objects. All classes extend the "Object" class. This is why you can cast an object to anything and if you know that the type casted is valid it will work.

This makes generics more "useful". They do not require things to be casted (for the most part) because you don't have to cast something that is already that type. Generics also give you the option to go even more extreme and make your code even better.

You should use an Object when processing data where the types are scattered around and you can't expect the same result. (An example would be calling a method from Class A to Class B that accepts an Object varargs. Then, you return those varargs back to another method in Class A to process the varargs Object. You could not use generics for this as the varargs could be of different types and sizes.

plain frigate
#

Yeah, it sounds like Object is going to be my only option here because the return types are determined at runtime rather than compile time, hence my struggle with generics.

ocean pendant
timber epoch
#

You could use a wrapper class that uses generics inside of it to store whatever so when you get it its already done for you.

ocean pendant
#

generics exist for compile-time safety, and thats it

plain frigate
#

I'm creating a tool that reads in a schema from yaml and then generates data for it. I often won't know the data type defined at runtime. It's similar to a database in the fact that it will often implicitly cast values to the correct type.

timber epoch
#

you would need to check and then cast it if you know the data types you're working with

plain frigate
#

Yeah, I had to build a bunch of utility methods to convert primitives around and then I have classes that deal with getting an object input and just use runtime type inspection to determine how to cast it. So if I get a String type and I want to store it in an integer, the integer class handles how to convert that string into a number, like how a database knows that if you insert '3' into an int column, it becomes 3

timber epoch
#

Yeah that is probably the only solution

#

that is viable

plain frigate
#

I was just wondering if there was a better way but honestly, I think I'm just learning that every language that casts values has to go through this same process.

timber epoch
#

Yep.

#

Although, you should look into if maybe calling a util method that converts the type instead of auto-converting?

#

I just feel that might help in the future, because you are explicitly doing it in the code instead of it doing it in the background if that makes sense. It might help with debugging later

#

¯_(ツ)_/¯

#

up to you

#

just my preference

plain frigate
#

Could you clarify what you mean?

timber epoch
#

Like

#

putting a string in an integer value in a db or whatever

#

you would have to convert it like so: Integer.parseInt or Integer.valueOf, instead of placing it in and letting your code that places it inside convert it for you*

#

and therefor you know exactly what you are doing and can solve any issues that arise fast without having to go on a hunt

#

its a preference for sure, but just something to also think about and be weary of.

plain frigate
#

The way I have it right now is that each data type is in charge of casting their own values. I have a data type for each of the data types in the RDBMS. So a PostgresIntDataType is in charge of how to convert an object into an Integer type, which often uses Integer.parseInt or Double.parseDouble().intValue();

#

Just because there are some database types that are backed by a certain type, yet are more restrictive or simply have different restrictions than java types

#

Such as MySQL's Mediumint type. It's backed by a 3 byte int but it was significantly easier to just use Java's 4 byte int and then check if it's out of bounds

timber epoch
#

This is similar to what I was explaining above, and also works. It's different from the other setup I was talking about as you're essentially connecting the methods/casting instructions to the actual data type which makes it easy to debug and control.

#

I don't think you can really go farther into improving it unless I'm missing something.

plain frigate
#

Good to know. Just ugly as sin code to look at

earnest beacon
#

so for those few cases you do actually know you can do something with it

timber epoch
#

Its the casting

plain frigate
earnest beacon
#

idk if postgres has a "sealed" type system but you could maybe make use of that?

#

like "a pgtype is one of uuid, text, ..."

#

but if not yeah - welcome to Object

timber epoch
#

lol

earnest beacon
#

at the very least you can make a marker interfacae

#
interface PgType {}
#

so you have a place to start when figuring out "what can this return"

#

but if you have a truly dynamic thing, you have a truly dynamic thing

#

thems the ropes

timber epoch
#

I also have a crazy idea

plain frigate
#

I kind of did that. I created a class for each of the database types and then each one has a method to take in an object and then return a casted type. So, PostgresIntegerDataType implements DataType<Integer> so it returns an integer from an object

timber epoch
#

you could have a web and use that to convert something into a "middle man" data type so that you can convert that aswell. This will cut down on the amount of logic you need to implement.

However it would probably be messy and really hard to figure out how to use if you forgot it.

I'm just throwing stuff out here

#

so like a UUID type of all ints

#

you have a string

#

so string --> int --> UUID

#

but again it has many drawbacks

#

maybe you could find a way to use some of my wack advice in your code

#

anyways good luck

plain frigate
#

Thanks for the help. Good to at least know I'm not crazy

timber epoch
#

ofc

earnest beacon
#

i think you might also be a little biased by the way java is "shaped"

#

if you were making an equivalent thing in clojure you wouldn't care nearly as much

#

since dispatch isn't based on types

#

but all the same mechanics are available in the end

plain frigate
#

I think it would be a little easier in C# too just because they have the dynamic keyword so I could defer the type to runtime instead of having to do all the casting behavior in Java since there is no dynamic keyword

#

Is it not supported just because Java has static dispatch instead of dynamic dispatch?

earnest beacon
#

hmm

#

not sure tbh

#

although...

#

1 sec

#

i starred a "dynamic" library some time ago

plain frigate
#

I think I was reading that C# does static dispatch except for the dynamic types, which is dynamic dispatch but I could be totally confusing terms

earnest beacon
#

you are

#

but its gonna be exhausting to drill into it

#

i can't find the lib so give me like 5 min to make an example

#
static void Main(string[] args)
{
    ExampleClass ec = new ExampleClass();
    // The following call to exampleMethod1 causes a compiler error
    // if exampleMethod1 has only one parameter. Uncomment the line
    // to see the error.
    //ec.exampleMethod1(10, 4);

    dynamic dynamic_ec = new ExampleClass();
    // The following line is not identified as an error by the
    // compiler, but it causes a run-time exception.
    dynamic_ec.exampleMethod1(10, 4);

    // The following calls also do not cause compiler errors, whether
    // appropriate methods exist or not.
    dynamic_ec.someMethod("some argument", 7, null);
    dynamic_ec.nonexistentMethod();
}
#

c# for reference

plain frigate
#

Dynamic is basically duck typing in C# right?

ocean pendant
earnest beacon
#
public final class Dynamic {
    private final Object value;

    private Dynamic(Object o) {
        this.value = o;
    }

    public static Dynamic of(Object o) {
        if (o instanceof Dynamic d) {
            return d;
        }
        else {
            return new Dynamic(o);
        }
    }

    public Dynamic method(String methodName, Object... args) throws Exception {
        for (var method : value.getClass().getDeclaredMethods()) {
            if (method.getName().equals(methodName)) {
                try {
                    return Dynamic.of(method.invoke(value, args));
                } catch (Throwable t) {
                    throw new RuntimeException(t);
                }
            }
        }
        
        throw new RuntimeException("No such method");
    }
    
    public Object unwrap() {
        return value;
    }
}

#
public class Main {
    public static void main(String[] args) {
        var example = Dynamic.of(new ExampleClass());
        example.method("exampleMethod1", 10, 4);
    }
}
#

obviously you'd want maybe a smarter way of picking a method or field or whatever

#

and i wouldn't reccomend building this just to avoid a cast

#

but dynamic doesn't exactly require a language feature

#

at least from what i can tell

#

its just a way to make it more ergonomic to do what you could otherwise do with reflection

#

dynamic languages on the jvm have been able to do it for ages

#
(let [o (ExampleClass.)]
  (.exampleMethod1 o 10 4))
#

but even there you try to avoid it because it neccesarily implies a performance cost

#
(let [o (ExampleClass.)]
  (.ExampleClass/exampleMethod1 o 10 4))
plain frigate
#

Yeah, I'm aware that I'll have some performance cost but thus far, it hasn't been too poor performance wise. The whole point of my project anyway is to seed databases and so it's not really to keep the data within the application or build something around it. It's basically, come in with the schema and leave with generated data to build something else on the data.

#

Something where the type is more defined and more dependable. Since I don't know what types will be provided by the user at runtime, I have all this ugly casting stuff but hopefully I can still keep it mostly maintainable.