ML Style constructors are not particularly idiomatic, but you can write basically the same thing in Julia:

struct Cons{T, S}; head::T; tail::S end
pairwiseReduction(f, a::Cons{T, Cons{S, Tail}}) where {T,S,Tail} = Cons(f(a.head, a.tail.head), a.tail.tail)
pairwiseReduction(f, a) = a

(https://github.com/thautwarm/MLStyle.jl if you want the syntax) but that's somewhat beside the point.

I think the primary problem here is one of terminology. Take Stefan's quote from the post you're responding to:

> The primary difference between static and dynamic languages, in my view, is this: in static languages, expressions have types; in dynamic languages, values have types.

There is a clash in terminology here. People coming from a static languages background would use the term "tag" for what dynamic languages people call a type. There isn't really a super good word in the dynamic languages world for what static languages people call a type, and the word type is generally re-used, since the distinction isn't as important (generally there is an embedding of the tags into the type system, so using "type" for both can be sensible). I'm gonna adopt (my best interpretation of) the static languages terminology here for this comment.

Some languages have both types and tags. The biggest difference then between static and dynamic languages are which of the two is primarily dispositive (in the sense of determining the semantics of the language). In dynamic languages, the tags are, in static languages the types are. This choice has deep seated implication for the kind of programs that can be written and the behavior of the program.

In fact, you sometimes end up in situations where static languages have multiple tag systems (but generally only one type system) and dynamic languages have multiple type systems (but generally only one tag system).
In julia, the tag system is primary, but we have a very rich type system that is embedded in the tag system by parametricity and is used for dispatch. This type system is constructed in such a way that the subtyping relationship is induced by tag system (for types T, S, (T <: S iff for all values v s.t. tagof(v) <: T, tagof(v) <: S)) [1]. You basically want to choose a decidable type system that satisfies this constraint, but is at the same time sufficiently expressive to be useful for what people want to do.

We also have another, larger type system that is used for inference judgements, but that is generally transparent to the user. It is in principle possible to type check over any of these type static system, but type checking is not part of the semantics of the language.

The problem with statements like "Julia's type system does not allow for X", is that it presumes that the type system having some feature is a pre-requisite for having the corresponding functionality. So while Julia's type system does happen have support for unions (and universally qualified unions), the significance of that is for dispatch, not for whether the language can have expressions that can take on values of different tags.

In any case, I can assure you that many people working on Julia are well familiar with Haskell's type system. Hopefully I was able to convince you that there is at least something interesting about the way Julia's type system works.

[1] I should credit the Northeastern PL group for the term "tag-based semantic subtyping" here to describe this notion (https://github.com/julbinb/ftfjp-2019/blob/master/paper/mini...).