There is some confusion in various terms on this subject. In some cases there is no universally accepted formal definition.
Popularly, languages are classified by their typing, so static languages formally are languages that are statically typed. And dynamic languages are dynamically typed.
Some languages can be classified as dynamic because they have other characteristics. They allow arbitrary code execution (eval) or the transformation of existing code at runtime. It is increasingly common for modern languages to allow these flexibilities. The typing doesn't matter, so formally there are languages that are dynamic by this definition and static by typing.
The basic definition of static typing that a programming language can have as a characteristic is that there is a check of the types used in data and variables to ensure that a type that is expected in all situations is always being used. This verification is done in the source code by the compilation process. This analysis helps in the so-called type safety in the use of data by the program, allowing the programmer to worry less about this issue. The compiler provides guarantees that some problems cannot occur after the program has passed this check, that is, errors are detected soon, before the program is actually executed.
A variable cannot change its type.
However, static typing can cause a false sense of security. Only a part of errors can be discovered in advance.
Example (it's in C# but could just as well be pseudocode):
var x = 1; x = "1"; //compilation error, cannot change variable type
In dynamic typing, this verification also occurs, but it is performed on the data itself, since the variables can contain any type of data. Of course, at any given time a variable can only contain one data type and this is checked. But the main difference is that this check is done at runtime. This is done through an auxiliary infrastructure (a virtual machine or a library commonly called a runtime). It is common for the programmer to have to do their own checks in the program or in external test code to ensure that all types are correct at the right times. The programmer has to worry more about the types although in simple solutions it may seem that the concern is not necessary.
What is effectively dynamic is the type of the variable. Understanding and documenting types is still required. There are languages that encourage doing documentation in code with Hungarian notation.
Data needs to be represented concretely on the computer. Variable is a design pattern (variable). So it's an abstraction. Data cannot take many forms, at most it can be interpreted in different ways in specific cases. Variables can refer to different types. So dynamic typing is often a special case of static typing. According to Bob Harper, one of the creators of the Standard ML language, "A dynamically typed language is a statically typed language with only one static type".
In practice dynamic typing is an abstraction too. There is an illusion that you can refer to a data that has different types but in practice there is only one marker and there is a pointer to a different static data.
The most common techniques to achieve this illusion are the use of a union type (union in C/C++) and/or typeless pointers (void * in C/C++). All data will have some memory overhead.
var x = 1; x = "1"; //normal operation, the type that was a number becomes string
Another big difference is in terms of performance. Although it is possible to make optimizations in a program written with "dynamic language" to approach or even surpass the performance of programs written in "static languages", due to the difficulty of achieving this, it is usually not done efficiently. In theory, a JIT compiler is better able to optimize code with precise information on how the program will be executed and even in practice it can even achieve better results in isolation.
On the other hand, if you already know what you're going to deal with, you don't need to have anything in the generated code helping the program to work and the program itself doesn't need to have certain checks written by the programmer. If checking is done before execution, a statically typed program does not need to do this work during execution.
It also doesn't help that most dynamically typed programs will run in a virtual machine as this makes language development easier and allows for some extra flexibilities normally desirable in "dynamic languages".
In practice languages are not usually 100% anything. Pragmatic tools know when to stray a bit from a concept to best advantage for the programmer.
There are hybrid languages. In practice it is not possible to have both types of typing but it is possible to use part of one or the other concept.
Static language with dynamic types
When a language is considered static, formally it is statically typed, it may have the necessary infrastructure to store different data in a variable. But the data is stored in a different way than other types. You will have a static type that can dynamically store data, but the language is still statically typed at its core.
Roughly speaking, the language uses an extra data structure to store its type and indicate where the actual data is. It is usually done in the library and usually has the help of the compiler just to indicate that the verification normally done should be relaxed since the verification will be done by this library to a certain extent and also by the programmer to avoid that certain errors will be generated at runtime through this library.
Dynamic language with static types
Dynamically typed languages cannot effectively be partially static. After all, a dynamic language, formally typed dynamically, must always expect any type. If it starts expecting a specific type and checks before execution, it becomes "static language". It is not possible to reduce the level of abstraction.
It is even possible to provide a previous type checking as an additional feature of the language but it does not make much sense if it is not accompanied by a change in the way data is managed in memory. If the program needs to have its types fixed and guaranteed in advance, it would be wasteful for it to use a structure that allows it to have multiple types. This structure has memory (type tag, references to the type in all situations*) and processing cost (extra indirection, selection of the specific data/method to be used).
It doesn't make sense, but languages are doing this, almost all mainstream languages are adopting a form that approaches, without being statically typed.
It is possible for a "dynamic language" to use manifest typing without changing its dynamic typing characteristic. The advantage is small or even questionable.
But there are also cases of languages that can compile statically typed parts of code and dynamically typed parts. The interface between the parts need to be normalized one way or another. Interestingly, some prefer to define themselves as static or dynamic to try to instill a prevailing culture and use the other as an exception. Purists, for better or worse, consider it a bad choice. There are actually two very similar languages in this case and not just one.
*Some static languages have references to types when data will be stored on the heap. And even on the stack the information will still exist in the code to allow reflection, but there will be no memory consumption on the stack to store the type.
Static typing does not mean that all types need to be declared explicitly. It is common, in most situations, for the compiler to be able to infer the type of a variable according to its assignment or even its use (less common).
The great advantage of static typing is more in the fact that the variable cannot change its type. This really helps development a lot. Having to write the type explicitly helps little or nothing. Some think it's more readable when the type is written, others think it's just a compiler shortcoming.
So it is possible to have a "static language" that uses implicit typing, partially reducing the ceremony that these languages tend to have.
PHP has the possibility to do a type check before execution, but this helps little because the language is usually executed in an interpreted way and mainly because it cannot check all types, all situations, so you have no security. Security is defined by the weakest link. During execution everything is dynamic.
I don't know the Hack language well enough to say how it works but it seems like it's statically typed.
Eventually you can use the mixed type which is that data structure that will allow any data to be stored there. It is likely that the compiler will treat this type differently. The program handles data dynamically by exception, the programmer says that anything can happen there because that's what he wants.
For various reasons it seems that Hack is the PHP that drinks in the water of C#, it seems to me that mixed behaves like dynamic in C#.
Advantages and disadvantages
The main advantages of "static languages" are security, performance and development-time assistance (refactoring, code completion, auxiliary information, code coverage, etc.).
"Dynamic languages" are flexible, quick to prototype, concise.
It is good to understand that the most complicated bugs remain equally complicated in both types.
There is certainly an advantage to "dynamic languages" when thinking about language development itself. Defining the language and creating a basic implementation is much simpler than a "static language". But to create a powerful implementation, which can solve some of its drawbacks, becomes a very difficult job. So far no one has solved all disadvantages.
Strong and weak typing
Sometimes these concepts are confused with strong and weak typing. This is partly explained because the strength of typing is not well defined and not universally accepted.
Strong typing is usually the characteristic that does not allow the same data to be treated as if it were of another type. It is very common for static languages to be strongly typed. But there are exceptions.
This makes the code more robust.
C, for example, allows one piece of data to be accessed/interpreted as if it were another. You can, for example:
-Write an int and access it as if it were a pointer.
-Write a float and access it as if it were an int. It is certain that the result will be catastrophic in this case, but it is possible.
-Getting a 0 and being considered false or other numbers (no matter what type) being interpreted as true in operations that require a boolean.
-Write two shorts in sequence and read as an int. Probably nothing useful will be obtained but it is possible.
-Write "SOpt" and read this as if it were an int, I don't know why.
C is a weakly typed language. C++ too, although it tries to enforce a style where it's not used as much.
C/C++ compilers try to prevent this from being abused.
Hence we conclude that type safety is not an inherent characteristic of so-called static languages. Security can be broken by other factors. Type safety is another different concept that can be mistakenly confused with strong typing and static typing.
Many "dynamic languages" are strongly typed, but others are weakly typed, often creating implicit coercions. Coercion is common in some situations under defined rules. Example: "1" == 1 is true and 1 + "1" gives "11".
Implicit coercion has the advantage of making the code slightly shorter. This is often characteristic of scripting languages, where code size makes a difference. So languages made for developing applications, as opposed to scripts, should not have this feature (hello PHP).
The definition of these terms doesn't help much, and saying that a language is uniquely weakly or strongly typed isn't usually true either.
In general, we can say something like this:
-Variable has type => static typing
-Untyped variable => dynamic typing
-Value has type => strong typing
-Untyped value => weak typing
It becomes increasingly difficult to define scripting languages. They are usually dynamic but nothing prevents them from being static.
Dynamic typing gives you more flexibility and helps you make small applications quickly. These languages usually require little ceremony, and dynamic typing provides little ceremony in typing. But it's more a question of suitability than a requirement.
Dynamic languages tend to run more interpreted than compiled and is another indirect factor that helps them to be used for scripting. But it's just a facilitator, again, it's not a requirement.
No language wants to lose ground so more and more static (typing) languages allow dynamic typing features, reflection, brevity, and simplified execution (illusion of interpretation). So they can be considered scripted too, despite not being their main focus.
In addition to suitability, the scripting characteristic is more linked to the implementation than to the language itself.
If a language allows its static types to have values that are invalid or do not match what is expected of this type, a null for example, is it still statically typed? It seems so, but there is doubt whether they should. It breaks type safety and in a way it can be interpreted as data having two types. Forces a runtime check to ensure there are no issues with the type.
What is the best?
The question that don't want and will probably never shut up is: which is better?
It is obvious that one is not universally better than the other. We can always say the cliché that there is a better tool for a problem.
In practice, I see that the choice falls, most of the time, on taste and experience. And this is good. I usually say that before choosing the best tool for the problem, choose something you know and feel comfortable using: "the best tool is the one you know how to use". It's the old contest between engineers who want everything to be perfect and administrators who want the best result to come. A chainsaw cuts wood faster and more accurately. A weekend woodworker can get hurt with a chainsaw.
Of course, there are cases that technically one is better than the other, but this is becoming more and more subtle.
My personal observation is that "dynamic language" programmers tend to think less about the problem and create overly simplified designs causing future problems. But there are cases where a better design is not an advantage.
On the other hand, I see that "static language" programmers tend to overthink problems and create overly complicated designs without bringing many future benefits and not solving all future problems.
Note that this does not define the quality of the programmer and much less that 100% of programmers are like that in 100% of the cases. There are cases where the opposite happens. And it really should. When programming in "dynamic languages", advance planning is often more important.
I cannot claim that the typing itself is responsible for the trend (if it is true). It could be the kind of programmer that typing attracts. It is the surfer going to the sea with waves and not the sea with waves making the subject become a surfer. Maybe people choose typing for the wrong reasons.
What I see happening a lot is that programmers who use "static languages" do not use the dynamic facilities existing in these languages unless there is no other way. And programmers of "dynamic languages" think that checking contracts should never be done inside the code (in fact these languages don't usually provide facilities for this). I don't know if it should be like that.