5 ms·
To play against the current wave, and give a contrarian pov: I'm grown up with statically typed languages from C/C++/C#/Java and later and didn't know about dy
by diminish 6y ago
To play against the current wave, and give a contrarian pov:
I'm grown up with statically typed languages from C/C++/C#/Java and later and didn't know about dynamic languages till recently.
1. Which types are we talking about?
- In earlier static-typed language, the processor-based types `uint64` looked very strict, optimized the code for hardware architecture.
- Having mathematically and ontological correct types is one approach `integer`/`float`/`string`/`datetime`.
- Each type being well defined and each object-class being used as type is another strict approach.
Look at the types of Rust and Kotlin and see the cacophony of decisions made:
- Rust https://doc.rust-lang.org/reference/types.html https://doc.rust-lang.org/reference/types.html
- Kotlin https://kotlinlang.org/docs/reference/basic-types.html https://kotlinlang.org/docs/reference/basic-types.html
2. When you want to implement a method which applies to multiple libraries, you end up writing `fooString`, `fooInteger`, `fooDatetime` etc (or foo(String), foo(Integer)) etc. DRYing is too hard. So you end up writing 10x more methods
3. No you don't avoid `null` checks at all.
4. Generalizations are harder to implement.
5. Interfaces are uglier compared to dynamic languages.
6. The code becomes less readable, not concise and verbose
7. Every new and old typed language is less elegant compared to the dynamic. Look at typescript vs javascript, typescript code is ugly, verbose, non-readable at all.
- inertiatic 6y ago>2. When you want to implement a method which applies to multiple libraries, you end up writing `fooString`, `fooInteger`, `fooDatetime` etc. DRYing is too hard. So you end up writing 10x more methods As discussed in the article, you want a sum type here. >3. No you don't avoid `null` checks at all. Entirely language dependent. >4. Generalizations are harder to implement. For some definition of harder. Harder to implement, causing you to think harder about what should be allowed under these generalizations, leading to less bugs, leading to things being... easier to actually implement in the long run. >6. The code becomes less readable, not concise and verbose. Since you only recently got into dynamic languages, just wait till you try to get into a large codebase without types. Where everything basically devolves into containers of arbitrary things that you don't know what they contain until you've gone through the thing with a debugger. Maybe your day job becomes more interesting that way. Making CRUD apps is admittedly boring, but this isn't in my opinion the way to keep one's self on their feet.
- joppy 6y agoHow many languages have true sum or union types though? For example, in Haskell I can’t declare a function as (foo: (String | Int) -> Int) and then call (foo “bar”) or (foo 123), I need to create some kind of wrapper type (like Either) to contain the possibility of a String or Int. If this were possible, there would not be such a proliferation of useless types throughout code, and code could be updated incrementally much more easily, since if I want to add the possibility of another type in a functions inputs, I don’t have to go and update every callsite.
- dddbbb 6y agoHaskell's Either is a 'true' sum type, it corresponds exactly to the way sums have been defined in the literature for decades, and also is the Curry-Howard representation of logical or. It necessarily must be inside a Either-like wrapper in order to be type safe, String and Int are ultimately different and so at some point we must discriminate between them. foo could call further functions with its argument inside itself accepting Either String Int, but eventually a destructor will be hit somewhere in the call stack. If String and Int do implement the same behaviour under some circumstance, then a typeclass should be used to define that behaviour. Then foo can have type (Bar baz) => baz -> Int, where String and Int have Bar instances.
- throwaway17_17 6y agoI’m curious, is the ‘true’ sum types, I mean where true is in quotes, based on the premise that lazy language can not have logically true sum types? Or is it in the quotes for some other reason?
- dddbbb 6y agoI was just responding to the parent comment's claim that Haskell's sum types are not true sum types.
- joppy 6y agoRight, so I must have used the wrong words “sum type” when I should have said “union type”, but I thought the intended meaning was clear. It doesn’t necessarily need to be inside an Either wrapper, that is an implementation detail, neither does there need to be a typeclass specifically defined for it. For example the new Dotty dialect of Scala has support for union types as I described them [1]. This is nice because it makes union of types an actual union operator, satisfying actual commutativity (A | B) = (B | A) and idempotence (A | A) = A, which Either does not without some extra explicit isomorphisms. [1]: https://dotty.epfl.ch/docs/reference/new-types/union-types.html https://dotty.epfl.ch/docs/reference/new-types/union-types.h...
- ohgodplsno 6y agoDynamic 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.
- mplanchard 6y ago> 2. When you want to implement a method which applies to multiple libraries, you end up writing `fooString`, `fooInteger`, `fooDatetime` etc (or foo(String), foo(Integer)) etc. DRYing is too hard. So you end up writing 10x more methods This is only a problem in typed languages without generics (like go) or without sum types (like go). With generics, the compiler takes care of writing fooInteger, fooDatetime, etc. With sum types, the compiler verifies that you’ve handled all of the cases in your foo function.