There’s also the interpretation that every dynamically typed language can be seen as a singly-typed one.
The clmap type signature could arguably seen as: clmap :: EDN
Exactly, I agree. When we work in any dynamically typed language we constantly think of the types and what we have in mind is often not very formal and it’s not precise. With that mindset we do the type-checking ourselves and we may think about the types that are used to back a certain constructor of an EDN value. You explained it well, we know that map takes a function as first arg. Even a function that has one parameter - as long we map over exactly one seq. If we map over three then our function should have three parameters. This is something that is trivial to express in Clojure but more difficult in Haskell. There we need zipWith and zipWith3 and zipWith7. Haskell is still evolving and it will take possibly 1-3 more years until one single map function in Haskell can do the same (via Dependend Types).
Though to be fair: map is a relict from older days and it’s kept because it makes for a great teaching utility. What we want is something that is even richer, and that would be
fmap :: Functor f => (a → b) → f a → f b
The clmap that you suggested is not complete enough because
(map #(+ %1 %2 4) [1 2 3] [100 200 300])
is a valid usecase of map.
Without dependend types I wouldn’t know how I could even represent this variable-arity map function.
The Seqable can not be heterogeneous. It’s all values of the type EDN. You just think of it as heterogeneous. In Haskell it’s the same if you were to use the EDN type. Besides that: you can express heterogeneous collections with advanced type system features. This is something that we can’t do in dynamically typed languages (although we think about it like that).
And in such a case, if you just want to print things then several dynamically typed languages offer a quick and simple way of making this possible. In Haskell it will likely take a bit more time. It’s Pros and Cons, depending on the situation. If you do this in Haskell either with an EDN type or with a true heterogeneous type-safe collection then you may also benefit from this and actually save development time.
Your first scenario would probably rather be met with a specific type in Haskell. Values that we know can be handled more comfortably and efficient with our own record type.
In your second scenario you can write a Clojure program that can merge two maps with unknown keys. But if you really don’t know what your keys are you can never really do anything of interest with them. You may try to print them but I could supply a .toString implementation that crashes the program. So printing is also not safe for truly unknown keys.
And you can’t do #2 with them, that is, treat them as generic containers.
You could do this with existential quantification. But you couldn’t “do” anything useful to them.
I am not sure here if you refer to row-types: Open Records and Variants