Are there people saying that you can't implement Common Lisp in OCaml/Haskell, with heterogeneous structures, dynamically typed and so on? You certainly can, but that's not an easy task.

If your language does not support natively the set of rules you really like to enforce, you can still work in a style you like provided you establishes conventions and abstractions around it.
So if you were actually interested in developing with, say, purely functional collections in Common Lisp, you would use the FSET[0] library:

(equalp (fset:bag 1 2)
(fset:bag 1 2))
T

(equalp (fset:bag 1 2)
(fset:bag 2 1))
T

That's exactly the same approach that people use when they do "C with classes", by reifying the abstraction into the host language.
Unsurprisingly, data-structures in FSET are implemented as Common Lisp structures, which allows having read-only slots as well as type specifiers.
Since we only want to have read-only slots, let's define a wrapper around defstruct (http://pastebin.com/30D6BnRg):

(deftype tree () '(or node null leaf))

(deft leaf data)
(deft node
(left nil :type tree)
(right nil :type tree))

(equalp (node (node (leaf 3) (leaf 4)) nil)
(node (node (leaf 3) (leaf 4)) nil))
=> T

(compile nil
(lambda ()
(setf (data (leaf 3)) 0)))

; compilation unit finished
; Undefined function:
; (SETF DATA)
; caught 1 STYLE-WARNING condition

(compile nil
(lambda ()
(node "strange" "node")))

; caught WARNING:
; Constant
; "strange" conflicts with its asserted type
; (OR COMMON-LISP-USER::NODE NULL COMMON-LISP-USER::LEAF).
; See also:
; The SBCL Manual, Node "Handling of Types"

The above messages are caught while compiling the functions, not when calling them.
The language specification does not give you another way to mutate structure slots other than opt-in accessors. Granted, your implementation is probably smart enough to let you mutate it with (SETF SLOT-VALUE), but as a CL developer, you know that its bad to use it and that you should rely on accessors provided by the API.

The advantage of Lisp at this point is that you can wrap your abstraction so tightly that it is indistinguishable from core features.

But maybe that's not enough for you, because there are not enough checks and you have your own idea of how things should work and you think you should do more compilation and less interpretation.

The compiler is available right here too, so you can even use it from your own compiler (during macroexpansion if you need) and do the analyzes you like. Or, you can even output a completely different executable file. Then you code a compiler in your new language because that's what grown-up languages do.

So, in order to do ML in Lisp, you develop your own DSL, an interpreter and later you bootstrap a compiler which is then self-sufficient to exist alone. That's roughly what happened decades ago:
"Edinburgh LCF, including the ML interpreter, was implemented in Lisp"[1][2].
ACL2, an applicative, side-effect free subset of Common Lisp used for first-order theorem proving (Boyer-Moore), shares the same approach.

But the language that exists now, even though it does perfectly what it was designed for, is not Lisp anymore, and can't do exactly the things we actually like to do in Common Lisp.

[0] https://common-lisp.net/project/fset/Site/index.html
[1] https://ocaml.org/learn/history.html
[2] https://www.cl.cam.ac.uk/~mjcg/papers/HolHistory.pdf