Can we bring dynamically typed languages back into trend?

Running out of memory is why it says “in practice.”

Division by 0 can be handled using NaNs or sum types like Option (another great feature I wish we had in Julia).

TypeScript is a static language. It compiles to a dynamic language, but typescript itself is static, and will throw compile time errors if it detects cases where you’d produce dynamic code.

That’s pretty much what many people want in julia, but they tend to think it’s unnecessary to make an overlay language like Typescript, and instead just leverage the very powerful systems we already have in the compiler to do it.

People complain about Javascript all the time. It’s one of the most complained about languages ever written. The main thing people tend to hate about typescript is that it’s still Javascript under the hood.

Yes people want it. They want to be able to mark a module or a function with an annotation that says “don’t allow any dynamic dispatch to occur in this function” and be guaranteed that if it would occur, the compiler would throw an error instead of just silently causing dynamic disptach or runtime code generation.

4 Likes

However, other people might wish to preserve the dynamic dispatch when inference fails. Perhaps this may be resolved by command line flags requesting strictly static dispatch?

3 Likes

It’s not strictly static (no gun to your head), but it makes static typing very easy, and idiomatic Typescript is statically typed.

The problem is that Julia supports explicit static types (ugly, painful) and dynamic typing (easy but error- prone), but it has no easy way to use inferred static typing.

I’m no expert on TypeScript. Maybe you’re right. Do you mean if you limit yourself to it? I think you can certainly use it with JavaScript. Call to (and from?). Your full code base would in effect be “dynamic”?! Are you sure you MUST use static types/the type system. I see front-and-center:

Adopt TypeScript Gradually

and in its docs:

TypeScript stands in an unusual relationship to JavaScript. TypeScript offers all of JavaScript’s features, and an additional layer on top of these: TypeScript’s type system.

There is already a small set of primitive types available in JavaScript: boolean, bigint, null, number, string, symbol, and undefined, which you can use in an interface. TypeScript extends this list with a few more, such as any (allow anything), unknown (ensure someone using this type declares what the type is), never (it’s not possible that this type could happen), and void (a function which returns undefined or has no return value).
[…]
Generics provide variables to types. A common example is an array. An array without generics could contain anything.

What do you mean? It’s not ugly to me using concrete types. You can and should to that for structs. You never need it for functions, but you could (if would not be faster). Being restricted to static, i.e. with StaticCompiler is painful limiting, but at least some of the limitations would potentially go away, and PackageCompiler.jl has no limitations on what you can use.

Easy yes, and what kind of errors do you have in mind? Seemingly tools can help, and the problems not serious.

What do you mean, what would you want exactly?

The idea is mostly for library authors, and would not (and could not) be a globally applicable thing. It would be for marking certain functions, or calling contexts where everything must be static.

i.e. someone writing a control loop for a realtime system would want to ensure that everything in their tight inner loop must be concretely inferred and can’t allocate, but they’d still presumably want their setup steps to be dynamic and allowed to allocate.

3 Likes

Rather than using a command line flag I think it would be better to use a macro to mark a code section as “static”…

5 Likes

Concrete types are different from explicit type annotations. I’m talking about cases like my earlier example:

function gcd(n1::Integer, n2::Integer)
    gcd = 1
    i = 1
    while i <= n1 && i <= n2
        # Checks if i is factor of both integers
        if (n1 % i == 0 && n2 % i == 0)
            gcd = i
        end
        # Error: Type instability
        # failed to infer if i is integer or float!
        i += 1.0
    end
    return gcd
end

The only way to make this statically typed would be to add types explicitly, as in:

function gcd(n1::Integer, n2::Integer)
    gcd::Integer = 1
    i::Integer = 1
    while i <= n1 && i <= n2
        # Checks if i is factor of both integers
        if (n1 % i == 0 && n2 % i == 0)
            gcd = i
        end
        # Error: Type instability
        # failed to infer if i is integer or float!
        i += 1.0
    end
    return gcd
end

Here it’s fine, but if you start having to add parametrized types, etc. with long types it starts to get ugly fast. (I don’t want to have to annotate x::AbstractArray{<:AbstractFloat, N} = ... every time I have an array…)

I don’t understand this, why would you ever want to annotate variables with non-concrete types (except for dispatch in function arguments). Instead, in Julia a function is statically typed, i.e., type stable, if all types of internal variables, function calls etc are determined by the argument types:

function gcd(n1::T, n2::T) where {T <: Integer}
    gcd = one(T)
    i = one(T)
    while i <= n1 && i <= n2
        # Checks if i is factor of both integers
        if (n1 % i == zero(T) && iszero(n2 % i))
            gcd = i
        end
        # Error: Type instability
        # failed to infer if i is integer or float!
        i += one(T)
    end
    return gcd
end

What’s nice in Haskell is that literals can have different types depending on what is inferred. Bit of a pity that Julia does not do that, e.g.,

julia> f(x::Int32) = x + 1
f (generic function with 1 method)

julia> @code_warntype f(Int32(2))
MethodInstance for f(::Int32)
  from f(x::Int32) @ Main REPL[11]:1
Arguments
  #self#::Core.Const(f)
  x::Int32
Body::Int64
1 ─ %1 = (x + 1)::Int64
└──      return %1

Would (could?) be nice if the literal 1 would automatically be read as one(typeof(x)) in this case?

2 Likes

What would be the type of the standalone expression 1 then?

Well, in that case it could either be a default concrete type, e.g., Float64, or something parametric that would be coerced depending on future use, e.g., like in Haskell:

ghci> :t 1
1 :: Num p => p

In any case, it’s a literal and the coercion could be constant propagated.

1 Like

In this thread, what we’re talking about is more like static type checking than static typing per se. Statically-typed functions are great; the problem is Julia makes it hard to figure out if your function is statically typed. The annotations here are to make sure that everything has the right type (although, as you pointed out, I really should have used a parametric type here, but I didn’t for convenience).

Man, the more I learn about Haskell, the more I think it would’ve been the perfect language if it just used a normal notation and hadn’t given everything obscure names like “monad” or “algebraic data type.”

There’s really nothing inherently wrong with dynamic behaviors. It’s a very useful paradigm. Yes, in fundamental packages and certain deployment situations it’s important or even necessary to avoid dynamic dispatch, but that’s not always the case.

To describe my own personal journey:

27 Likes

https://docs.julialang.org/en/v1/manual/missing/

Isn’t missing what you are looking for instead of Options. Not to be confused with: GitHub - JuliaAttic/Options.jl: A framework for providing optional arguments to functions.

No, NaN is only available for floats. Ok that’s what you get Inf, -Inf or NaN, i.e. Float64 result, for division, but I also had integer divide in mind.

@code_native div(1,0) # gets you idiv, but more, also code div instruction for some reason, is surprisingly verbose, with also the expected ijl_throw and call for it.

What division could plausibly get you are Rationals and there you CAN define it e.g. 1//0, but still not 0//0, while div on rationals will also throw.

My point was that you get exceptions always in some code, practically possible for all code, so why try so hard to not get them at runtime? And are those you get at runtime with Julia practically so bad (better than wrong results)? You can also eliminate most of those with tools.

This would be pretty useful, something like:

@staticonly function foo(...)
...
end

The issue is the compiler doesn’t get called until you call the function. So I guess the macro would have to define the function and then call it to induce compilation, but to call it you need some objects of the input type, and in general valid versions of those could be arbitrarily hard to generate… think of a structure type whose constructor runs a 30 hour physics simulation.

But maybe Julia could just do lowering and type inference without the full compiler I guess.

1 Like

To be honest, I don’t like Option at all as it is contagious, i.e., changes the type of functions which can fail to return an Option and every function that now wants to call this function needs to care (by either handling or propagating None) all the way up the call stack.
Guess that in some (10?) years, Haskell, OCaml, Rust (?) will no longer require Option, but have an effect system instead. It’s basically “checked exceptions” done right and gets you most of the benefits of monads without writing all code in continuation passing style (albeit with a bit of syntactic sugar via the do-notation).

6 Likes

Then you might like to try F#: it is way more normal from a notation point of view.

Bonus:

  • Compiles to JavaScript
  • Compiles to Python
  • Rust compilation WIP
  • It might generate Julia code one day (or my wishful thinking is playing with me)
5 Likes

Neat, thanks for telling me about this! Is there a Julia package for using effects, so I can try it out?

yes. as of 1.9, Julia has an effect system which is used to model properties of functions (including whether a function might throw an error). making these properties user facing would be somewhat tricky since the modeling is imprecise and is continuing too evolve.

4 Likes