Why? I find when you're importing external data or user input, that's exactly where you want strong types as that's the most likely place unexpected values are going to be generated (e.g. unexpected null values, string instead of an array).
Why? I find when you're importing external data or user input, that's exactly where you want strong types as that's the most likely place unexpected values are going to be generated (e.g. unexpected null values, string instead of an array).
As for data validation, the hard part of it is not checking type but that the value is safe and coherent. And dynamic languages excel at that because they make complex declarative schema descriptions easy while powerful.
See marshmallow for Python: https://marshmallow.readthedocs.io/en/latest/
It will validate everything, including nested recursive data with cross references in fields and a need for sanitation and casting. But you don't need a lot of code to do so.
Static languages are good to enforce safe practices at scale . That's why they are popular in big IT structures. Dynamic languages are fantastic when you have a lot of external data manipulations. Hence their popularity for the Web, banking, biology, geography, etc.
>>> (1,2) > {1: 2} True
>And dynamic languages excel at that because they make complex declarative schema descriptions easy while powerful.
Marshmallow looks like it works the same way as existing systems in statically types languages. e.g. Serde in Rust:
This validates at compile time.
In [60]: (1,2) > {1: 2}
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-60-f9d83a4c8f73> in <module>()
----> 1 (1,2) > {1: 2}
TypeError: '>' not supported between instances of 'tuple' and 'dict'And the keyword in my schema validation sentence is "easy".
I love rust, but a low level language cannot be compared in productivity to a high level language for this stuff. They are much, much easier. That's the whole purpose. You trade easiness for rigor.
You mean a dynamic check? It's better than nothing but I don't exactly want my e.g. scheduled data import process crashing because it was sent some bad data. I'd much rather have static checking and have all the possibilities accounted for before the code runs.
> As for data validation, the hard part of it is not checking type but that the value is safe and coherent. And dynamic languages excel at that because they make complex declarative schema descriptions easy while powerful.
Why couldn't you write this with static checks? Statically checked functional languages for example are great for writing compilers.
If you have decent type inference there isn't extra typing and any time you think you're saving with dynamic languages gets eaten up having to manually fix things when errors happen at runtime. I'd much rather spend time fixing predictable compile time errors than unpredictable runtime errors.
Let's say we're doing a user registration. In most dynamic languages the JSON body will get parsed into a map. Excuse the fat controller and pseudo-language, but it'll end up looking something like:
func create(conn, params) do
if not Validator.is_email?(params["email"]) do
return error(conn, "email is not valid")
end
if not Validator.min_length?(params["password"], 10) do
return error(conn, "password must be 10 or more characters")
end
create_user(params)
true
end
In Go, if you want this nice and typed, you'd add a RegistrationInput struct with the field mapping tags: type RegistrationInput struct {
Email string `json:"email"`
Password string `json:"password"`
}
Map the user's input to the structure, handling the error: var input RegistrationInput
if err := json.Unmarshal(req.body, &input); err != nil {
...
}
And then write the exact same validation checks.What safety does Go's version buy you?
Now, if we are talking about error handling, the way it is done in Go is not particularly representative. Most static languages would go with exceptions (or maybe checked exceptions in Java). Haskell/Rust/Scala would probably go with a Result type, and then it's more a question of the language giving you the tools to propagate the errors and wrap them (Rust with error-chain is excellent at this kind of thing).
Your code starts by making a few checks on the shape of the data you're receiving and failing earlier if some conditions are not met. Great.
Once these checks are cleared, you now know that you can materialize an object with valid values in it, something the rest of your business code will be able to manipulate. So you instantiate that new object, return it and... all the knowledge about the shape of that data is lost.
The only thing your callers will see is an Object and zero information about what operations or fields are available on this object. They'll have to guess.
With a statically typed language, you don't have such a problem. You have asserted all these properties about the data that came in, you know exactly the class of the object you are creating, why forget all this precious information that will keep your code robust?
In my experience, dynamically typed languages are at an evolutionary dead end and offer close to zero advantages over modern statically typed languages (e.g. Kotlin, Swift).
I'm not really seeing how that necessarily falls out of the example given by GP. Could you go into that in more detail?
Is there something preventing create_user(params) from emitting an object with a well defined interface?
If you call that function, you have no idea what the type of that object is. Only the function knows it and that information is lost as soon as the function returns.
And yes, you really have no idea what the type of that object is unless you look at the source of the function. Which is obviously suboptimal, especially if you don't have access to that source.
A statically typed version guarantees you haven't made some fundamental mistakes and enables automatic refactoring.
Say we wanted to change an ID field from being a string to an int later in development for example. You've now got to hunt around all your files making sure the right type is being used. It feels incredibly primitive when you need to do this manually or have to rely on an exhaustive and annoying to maintain test suite for this.