So basically, there are 4 dimensions:
- Static (expressions have types) vs. dynamic (values have types)
- Strong (values cannot be coerced to other types without a cast) vs. weak (the runtime performs a variety of coercions for convenience)
- Latent (no type declarations) vs. manifest (type declarations)
- Nominal (subtyping relations are declared explicitly) vs. structural (subtyping relations are inferred from the operations available on types)
And you can place most languages on one of these 4 axes, though several support multiple forms of typing:
- Ocaml: static, strong, latent, structural typing
- Haskell: static, strong, latent, structural typing, with nominal typing available via newtype and manifest typing through optional type declarations.
- Erlang: dynamic, strong, latent, structural typing
- Scheme: dynamic, strong, latent, structural typing, with nominal typing available in many object systems.
- Common Lisp: dynamic, strong, latent or manifest typing. Same note about structural vs. nominal typing as Scheme, but nominal subtyping is used more often in practice.
- Python & Ruby: dynamic, strong, latent, structural typing. Nominal subtyping is available via isinstance or Ruby equivalent, but good practice frowns upon it.
- PHP: dynamic, weak, latent, nominal or structural typing. Culture is much friendlier to nominal subtyping than Python or Ruby, but it’s not required.
- Java & C : mostly static, strong, manifest, nominal typing. The casts give you a form of weak-typing when necessary, and C templates are structurally typed.
- C: static, generally weak, manifest, nominal typing.
- Assembly: dynamic, weak, latent, structural typing.