typed

Types are important. They keep you away from inconsistent mess, guard from certain errors, allow to model and document things, and write performant code. The better type system of your language is, the better your code should be.

JavaScript type system... JS type system... well it's formally there 😐. But informally it feels like you're at a playground and nobody's watching, so you can easily find yourself swinging on a merry-go-round and spinning on a swing (I honestly don't mind that kind of fun 🤓).

There are lots of crazy cool examples related to JS built in data-types and its type coercion. If somehow you've missed a hilarious WTFJS - enjoy. Here are some of my favourites:

> true + true
2
> Math.pow(10, false)
1
> "hello " + + "world"
'hello NaN'
> 9_999_999_999_999_999
10000000000000000

And can a poor monkey dodge a snake and have enough mangoes and maybe a pie in a cruel JS world?

let mangoes = [Math.PI]
mangoes[3] = "🥭";
console.log(`${mangoes.length}`);
mangoes[-1] = "🐍";
mangoes[Infinity] = "🥭";
mangoes.forEach((mango, index) => {
    console.log(`🐒 eating ${mango} at ${index}`);
});
🤔

There have been quite a few data-types added to the language over time, like BigInt and Symbol for example. To me, addition of Typed arrays was like entering The Twilight Zone - science fiction, suspense, horror and super cool at once 😐.
Promise and Generator (JS functions are good parts for sure) additions were warmly welcomed as well. How about mango-infinity for that monkey?

function* mangoInfinityGenerator() {
    for(let i = 0; i < Infinity; i++) {
        yield "🥭";
    }
}
let mangoInfinity = mangoInfinityGenerator();
let nextMango;
while(nextMango = mangoInfinity.next().value) {
    console.log(`🐒 eating ${nextMango}`)
}

And I can't but agree that If it walks like a duck and it quacks like a duck, then it must be a duck is a no fuss behavior oriented way to code. And since why not, how about some Duckenstein:

let ifItWalksLikeADuck = it => typeof it.waddle === "function"
let itQuacksLikeADuck = it => typeof it.quack === "function"
let itMustBeADuck = it => ifItWalksLikeADuck(it) && itQuacksLikeADuck(it);

let duckenstein = it => {
    it.__proto__.quack = () => console.log("quack");
    it.__proto__.waddle = () => console.log("waddle");
    return it;
}

[Infinity, false, "not a duck", /.*/, {}, Symbol("@"), null, undefined].forEach(it => {
    try {
        it = duckenstein(it);
        if (itMustBeADuck(it)) {
            console.log(`${it.constructor.name}, ${it.toString()} - It's Aduck!`)
        }
    } catch (err) {
        console.log(`${it} - I am alone and miserable. Only someone as ugly as I am could love me.`)
    }
})
Java has come a long way enhancing its type system over time. Generics were the first notable addition. Backward compatibility requirement, being at the heart of language evolution, obviously impacted generics design choices. Type erasure just had to happen, but it was actually a good old null pointer that some 12 years later allowed for a (fearful) discovery: Java is Unsound (😨...and Scala as well 😱). Should you care though? Well, if you're designing a new programming language then wtf are you doing here 😕? Java isn't done with generics though: Universal Generics are already on the horizon at the time of writing.
There's been quite a few additions since generics. I've patiently waited for var support, wondering why Script had it, but Java did not 🤔. It turned out to be much more than just a syntactic convenience, adding (even if limited) support for non-denotable types. var allows to preserve type information, so the following is possible:
➜ ✗ java Var.java
Var.java
package typed;

public class Var {

    public static void main(String[] args) {
        var var = new Object() { int x; }; //var isn't a keyword
        var.x = 10;

        var hi = new Object() { public String toString() { return "¡Hola!"; }};

        /*
         by all means use a properly named
         local record class if you need stuff like this
        */

        System.out.println(hi);
        System.out.println(hi.getClass());
        System.out.println(var.getClass());
    }

}
 
¡Hola!
class typed.Var$2
class typed.Var$1

Obviously Lambdas happened earlier (Java 8 vs. Java 10) and were a much-much-...-much
bigger than var (why did I mention it first then? 🤔). And they brought in a notion of a target type slash functional interface, and also default methods in interfaces. And then there were.. and also... Well, somebody has to put together a good article about Java type-system history and evolution for sure (📝).
Fast forward and there you have your Pattern Matching for instanceof, Records, Sealed Classes and already a third preview of Pattern Matching for switch.

➜ ✗ java java -version
openjdk version "19-ea" 2022-09-20
OpenJDK Runtime Environment (build 19-ea+31-2203)
OpenJDK 64-Bit Server VM (build 19-ea+31-2203, mixed mode, sharing)
➜ ✗  java --enable-preview --source 19 HeadsOrTails.java
HeadsOrTails.java
package typed;

import typed.HeadsOrTails.Toss.Tails;

import static java.lang.System.out;
import static typed.HeadsOrTails.Toss.*;

public class HeadsOrTails {

    sealed interface Toss {
        record Heads() implements Toss {};
        record Tails() implements Toss {};
    }

    public static void main(String[] args) {
        var toss = Math.random() >= 0.5 ? new Tails() : new Heads();
        out.println(
                switch (toss) {
                    case Heads() -> "heads";
                    case Tails() -> "tails";
                }
        );
    }
}

And with current and ongoing type system changes Java is up for yet another programming paradigm - DOP. (Doh!)

Julia type system is humble and powerful: you may not even notice it's there, but when explicitly needed, its expressiveness is ready to assist both you and the compiler.
julia> double(x) = 2x
julia> double(1)
2
julia> double(x::Rational) = 2x
julia> double(1//1)
2//1
There are many things that I find interesting, like the ability to define custom primitive types (doesn't mean you should) and the fact that Julia's standard primitive types are defined in the language itself:
primitive type Bool <: Integer 8 end
primitive type «name» «bits» end
primitive type «name» <: «supertype» «bits» end
It's also worth noting that in Julia only values have types, so type of a variable is actually a type of the value to which a variable refers.

Julia doesn't allow fields to be declared in abstract types, which (to be honest surprisingly to me) is a really useful restriction that resolves Circle–ellipse problem by not allowing it in the first place. Moreover all concrete types (struct) are final. So there is no inheritance per se (sorry interface inheritance), instead you get behavior subtyping: a function defined for a supertype naturally handles its subtypes. So a kind of (Any) duck typing is possible.

abstract type Bird end

struct Duck <: Bird
    sound::String
    Duck() = new("quack")
end

struct Silence
    sound::String
    Silence() = new("sound of silence")
end

sound(it::Bird) = "bird: $(it.sound)"
sound(it) = "any: $(it.sound)"

With first class support for functions and type unions it's possible to be very specific about what behaviors a method (a definition of one possible behavior for a function) is willing to accept.

up() = println("move on up")
left() = println("everybody's looking left")
right() = println("what the hell is happening right")
down() = println("dont let me down")

Direction = Union{typeof(left), typeof(right), typeof(down), typeof(up)}

move(direction::Direction, steps::Int) = foreach(step -> direction(), 1:steps)
julia> move(up, 3)
move on up
move on up
move on up
julia> move(sin, 3)
ERROR: MethodError: no method matching move(::typeof(sin), ::Int64)
Closest candidates are:
  move(::Union{typeof(down), typeof(left), typeof(right), typeof(up)}, ::Int64)

And unreasonable effectiveness of multiple method dispatch is also rooted in Julia's type system, and once again, Types manual is awesome.