4 ms·
Dynamic languages don't do away with such types, especially uint/float, etc. They just hide it away from you. The only thing it gives you is false confidence,
by ohgodplsno 6y ago
Dynamic languages don't do away with such types, especially uint/float, etc. They just hide it away from you. The only thing it gives you is false confidence, where one day you end up doing operations on two floats because your untyped function is supposed to get numbers in, and you're left with 36.99999999999994$ on your bank account because it wasn't explicit.
So, you write unit tests to make sure that you only pass the rights types when calling it. Doing a very bad job at just being a bad compiler.
Now, I'll agree, the languages you mentioned are absolutely horrible with types. Types in C are basically nothing more than suggestions because you end up casting const away while laughing, C++ has std::types<template typed<std::frobnicate<T, A, R>>>, C# and Java in the past were excessively verbose and required you to type every single thing. Java/C#/C++ finally got a bit better with that with auto/var.
Kotlin types and type inference are absolutely fantastic and make your code clearer.
2/ No, you don't write fooString, fooInteger, etc. You write foo(value: Int), foo(value: String) and let the type system resolve and tell you which ones are available, rather than relying on documentation and runtime type checkings that are plain bad. And if it makes sense, you can even declare them as extension functions, Int.foo()
3/ Yes you do. Unless you explicitly opt in to null values or you're using platform types, like calling code from Java with @Nullable/@NotNull annotation
4/ Nobody calls for generalizations all the time. You're not supposed to write generics for all your methods.
5/ Explicit definitions are uglier compared to duck typing and just going "yolo it has a .frobnicate() method that means I can call it" ? I agree that over time, they might become unyieldy, but once again, with a proper typechecker and type inference, they're a blessing. Using kotlin and writing
when(this) {
is Frobnicator -> this::frobnicate
is Zobtrinatex -> this::zobritnex
else -> this@caller::defaultBehavior
}.invoke()
gives you safety, checks that you're not mixing return types.
6, 7/ Depends. I've written some true abominations, yes. Typecript gets ugly when the libraries you're using abuse types horribly (React used to be absolute trash for that, declaring props was terrible). But then, you pay the cost once (at declaration), and get benefits through your entire app.
Don't write generics for a component, unless it makes sense. If you're exposing a Container<T> and you can access the data inside and you require its type, okay, it makes sense. But don't do a Page<T>, that's dumb.
Generics are a tool. Like C tells you to not abuse (void*), don't abuse generics. Use them with parcimony and your code will be better, give you more guarantess and literally write itself if you've got a competent IDE.
- GlennS 6y agoIn Python you'd usually use a `Decimal` for money and it would give you an error if you tried to mix it with floats, not silently throw away information (which I certainly agree would be a dire consequence). In JavaScript, good luck for now, but a solution is being rolled out: [https://caniuse.com/bigint https://caniuse.com/bigint].
- diminish 6y agoThanks @ohgodplsno for the detailed and thoughtful response. One final point is that with statically typed languages you sometimes fight with the language to deliver. The language and libraries becomes a maze of problems. Developer productivity and sense of accomplishment is low. I sometimes look at my code when I wrote a matlab clone in C++, It seems like half of the code is things which I wrote to overcome or augment the language and the libraries.
- AlexSW 6y agoI'm not following your answer to 2, do you have an example?
- ohgodplsno 6y agoSay you'd like to have a foo method that returns... a number, whatever. You do not need to have multiple symbols "fooString", "fooDecimal", "fooString", etc. Simply overloading the parameters gives you type safety, and keeps a single foo symbol. fun foo(value: String): Int = value.length() fun foo(value: Int) = value fun foo(value: RemoteDatabase) = value.servers.map { it.connect().executeSql("SELECT 1")[0].toInt() }.sum() All these define a foo method, that returns an Int. The untyped alternative (what javascript, python, etc do in a naive way, without trying to duck type) is to do this: fun foo(value: Any) = when (value) { is Int -> value is String -> value.length() is RemoteDatabase -> value.servers.map { ... }.sum() else -> error("Welp, our type system couldn't help us there.") } Additionally, if it really makes sense, you can define foo as an extension function on the type: fun Int.foo() = this fun String.foo() = length() fun RemoteDatabase.foo() = servers.map { ... }.sum() You can then use it directly on the type, rather than call foo(1): val fooResult = 1.foo() val fooStringResult = "Well hello there".foo()