This makes it literally impossible to represent an invalid pizza, and as a bonus, eliminates the need for PhantomData. You can still use the builder pattern if you want to, though for a simple case like this, you usually wouldn't in idiomatic Rust. Using something like enumset instead of HashSet can help with performance and ergonomics
The problem with this is the same as its benefit - it requires changing the API every time you want to add a sauce or a sauce/ingredient combo. In many cases, consumers of the service may not want to be exposed to these details, or have to structure their requests based on constraints internal to the service you're providing.
Especially as the dimensions of constraints add up, the structure gets more and more contorted. You suddenly need CreamOnWheatThinToppings and TomatoOnWhiteChicagoToppings plus every other combo in between.
The whole premise here is that we want to make illegal state unrepresentable. If we take that as a given, this is the way to do it.
If you actually have specific toppings that should only go on a cream-base thin-wheat-crust pizza, then a CreamOnWheatThinToppings enum is the simplest way to enforce that constraint. Any alternative approach is going to be at least as complex. If you want to enforce a constraint about which toppings are allowed with what stuff, you need a way to get that list of toppings, and the enum is pretty much the simplest way to do that.
I do agree that in the real world, this is not great. If some degenerate wants pineapple on a cream-base, I can't see why you wouldn't sell that pizza. It's absolutely a mistake to write constraints into your code that don't reflect actual real world constraints.
But if you actually do want to enforce the constraints, this is a good way to do it.
Yeah, in a world where people not only can but intentionally do order none pizza with left beef, this was probably a poor/distracting choice of metaphor.
A local place offers a mustard and saeurkraut one. I'm not sure how many orders they get for it, but it's been on the menu for about a decade. I've ordered it twice. Its surprisingly not disgusting, though it's not really my thing. If I'm in a Reuben mood and can't actually get a good Reuben, I might order one of those.
Does this extend well, if the pizza place expands to have a choice of something like 20 toppings, 5 crusts and 5 bases, limited-time options for each choice, last-second additional choices dictated by unexpected needs...
And then the higher-ups come down, and they say you've gotta make this pizza
In most ways it’s easier because you just add the new variant. It only becomes an issue when you have to decide how to organize your data (does herb dusting the crust count as a topping or a modification of crust?) which is a variant of “the hardest problem is naming things”, but usually the objects are smallest enough that there is only one sensible way to do it.
For limited time toppings, you could add a custom variant that takes a string, price, and a date range so you don’t have to update the code whenever a new one gets added.
It boggles my mind that people tout this as a Rust only feature. I could do the same in any OOP language from the start. Functional languages enhance it further. But no, everything is in terms of "Rust like Result pattern" which I think speaks volumes about the state of the industry.
I don't think anyone is saying this is a Rust only feature. I personally learned this pattern first in Haskell. I will say that only a few languages can compete with Rust for good ergonomics around algebraic data types.
OP's Java, Kotlin, and Python implementations seemed reasonably idiomatic to me. I commented on the Rust implementation because it felt like OP had missed the natural way to do this there.
Ohh, and I've never even heard of Gleam, so no comments there, though my gut instinct is that a modern language like Gleam ought to have a better way to model this problem.
Once you try sum types (that “enum with stuff”) any language without them feels clunky. Indeed, they’re called sum types because they’re a direct mathematical analog to elementary addition, where structs containing multiple fields are equivalent to multiplication. From that POV, imagine trying to do arithmetic without addition and trying to approximate it with multiplication
We’re gonna need you to teach dependent type theory to Gen Z. If dependent sums are gigachad, where does that leave dependent products? Does Pi mog Sigma?
Nice try but isomorphismmaxxing makes the attempt to mog straight cap.
Pi a f = forall r. Sigma a (\x -> f x -> r) -> r
Sigma a f = forall r. Pi a (\x -> f x -> r) -> r
Assuming you have things like parametric polymorphism System F/FC type stuff, maybe in a more limited system you could support Sigma without admitting Pi.
Yeah, and many of them (including some in the blog post) also simulate sums by using subtyping (which doesn’t have as nice of a mathematical interpretation) and closing the common super type somehow. Scala and Kotlin like doing it that way, and on one hand it makes me itch and on the other it works fine.
I do love seeing people write down type arithmetic in /r/programming though 😁 but down with the +1! Give me sums and give me products and don’t force me to add 1 to everything
I love the idea, but at least in enterprise applications, constructing valid state is like 90% of the work anyway. Meaning you need to have not-yet-validated state in 90% of your code. A change request for a user comes in, and you spent all the time validating every last element against 5 times joined data from every corner of the application becuase there's a configuration or an exception for everything. By the end, all you have is two lines:
So you're only guarding like 2 lines against bad state. Another option might be to have intermittent valid states, but then you're rebuilding your validation codes entire process as a state machine.
It really doesn't matter. After you have made the situation unrepresentable in your API the product owner will bitch and moan until someone in the front end team hacks it with the help of some metadata field not intended to contain this data.
It gets fun when you iterate backwards over an array and index >= 0 never fails. Or if you try to calculate the distance between two indices and nothing prevents you from just doing indexA - indexB .
Valid indices may not be negative, but using unsigned forces you to carefully consider any operation that might require a signed result.
It would be an implementation detail if you wrapped the integer indices in an opaque Index type, by exposing a raw integer or unsigned integer you make the operations they support a part of your public facing API.
Yes, but only works if you're ready to also maintain high quality, up-to-date documentation and tests that encode your assumption, and also build your system in preparation for the time where there won't be such a thing as a statically valid Pizza anymore.
But this really doesn't come across in any of these articles; it's usually implied that if you use enough of the "strong" type sauce you'll live in a magic world where nothing could ever go wrong.
The whole point of encoding it in the type system is that you don't need "documentation" and "tests". In fact, it's impossible to write tests if your state is unrepresentable, that's what unrepresentable means
Where is the limit then? I saw other comments suggesting using a Non zero 8bit unsigned integer to count the ingredients, but in this example I would also like to limit the number of time one ingredient is present to (let's imagine) 8.
Should we have a type that cannot go higher than 8 so that the wrong state is unrepresentable? Or add some business logic, validation and tests so that it doesn't happen?
I'm not sure every system state can be forced to be representable only through a type system.
I'm all in favor on making very wrong state impossible, but the world is messy and evolves over time, and some new state should be added, which may directly clash with the existing system enforced by types
The limit is that your program would statically only be able to represent legal states. So yes, you should use LessThanPositiveEightNonZero for your ingredients
Now, this assumes that you really only ever want less than eight. Is that a reasonable assumption? It depends. I would argue that for the majority of programs such level of precision is unfortunately not possible, but when it is, of course you should enforce it
Perfect future foresight like an oracle, you know what the future holds and build a perfect solution for that perfectly defined future.
Idealized interfaces, you can assume that services, libraries, API, IO, network etc... all behave in however way your idealized world chose to.
Perfectly immutable and fixed requirements, no stake holder changing the objective of what we are doing continuously.
Idealized consumers, we assume that the ideal consumer that perfectly satisfy the consumer of our system is whatever we chose it to be.
Zero need for observability
In the real world none of the above hold, a developer that build a rigid system will soon need to loosen it as the world require it to be, because such rigidity is generally embedded in the foundation of a system.
It means the whole foundation will be rapidly shaken, and all modules that are built on top will rapidly spaguetify.
The thing is that in simplified situations where you can afford to design a simple system living in an idealized world a lot of things make a lot of sense.
In the real world, things outside of your control, changing requirements, faulty/unexpected/[you just didn't understand it] external communications or IO, etc...
There are so many things that makes such a rigid approach less than ideal and even more so greatly decreases the ability to have powerful observability around failures.
It also assumes that the developer has perfect future foresight. It creates issues not because it's rigid but because the rigidity create assumptions in other dependent modules, once rigidity is loosened which always happens because the developer didn't have foresight, previously working modules start having problems.
It's a cool concept for a Hello World or a Pizza Factory or something that's fully specified from the get-go. Good luck trying to model like that some actual real-life software (eg. you end up with tens of thousands of classes/records/structs/enums and no one remembers which goes where). And then maintain it through constant changes (eg. tell your manager you need 2 months to add one button, because it requires re-doing the whole type-system).
Feel like the given examples do a pretty poor job of elucidating the intended finite state machine. Even worse, invalid pizzas are still representable:
Pizza(Crust(), CreamBase(), frozenset({Pineapple()})) # invalid pizza with no type errors!
In Python, it'd be both clearer and more type-safe with:
Not sure if the Java's and the Rust's version are equivalent: toppings in Java are in a List, so one can add ham multiple times. In Rust presence of ham is a boolean. I might have misunderstood, though.
protip: when ordering pizza online under flow the ham toppings to get 2147483647 ham on your pizza. This is a trick that big pizza doesn't want you to know about.
I think this is a perfect example where an object oriented approach fails very fast and hard. This needs a data driven approach for the entities and constrains and a more abstract model in code.
I think you either use a functional approach with algebraic data types and pattern matching or model the options and constraints in a data structure. Single inheritance is too limited most of the time.
if “Functor” is an unfamiliar term, and you are invested in preventing illegal representations at compile time, may I suggest that Haskell is a language to study. This whole premise of your post is one of the major design goals of Haskell, which itself makes heavy use of Category Theory to make the sorts of guarantees you are interested in.
which is to say, there is a whole serious and deep field of study around this kind of question, and you can join in.
Python’s gradual typing proves that static typing helps avoid calling non-existent transitions. However, because of the way Python works, one needs to invoke a dedicated type checker. In statically-typed languages, it’s unnecessary.
I prefer static typing, but pointing out the key thing that defines static typing as an advantage seems a bit biased.
Furthermore, it’s simply a workflow complaint. My IDE does type checking as I edit and so does my CI. So I can shift type checking as far left or right as I want at an individual or team basis.
I would have liked to have known what Python’s type system can and can’t represent compared to the other languages instead of quitting that part of the analyst because of the need to execute or integrate mypy.
NoLemurs@reddit
In Rust, you can use the types to provide better actual guarantees. Your struct:
allows representing invalid pizzas with a cream base and pineapple if you don't use the builder. I would probably choose a representation like:
This makes it literally impossible to represent an invalid pizza, and as a bonus, eliminates the need for
PhantomData. You can still use the builder pattern if you want to, though for a simple case like this, you usually wouldn't in idiomatic Rust. Using something like enumset instead ofHashSetcan help with performance and ergonomicsImNotHere2023@reddit
The problem with this is the same as its benefit - it requires changing the API every time you want to add a sauce or a sauce/ingredient combo. In many cases, consumers of the service may not want to be exposed to these details, or have to structure their requests based on constraints internal to the service you're providing.
Especially as the dimensions of constraints add up, the structure gets more and more contorted. You suddenly need
CreamOnWheatThinToppingsandTomatoOnWhiteChicagoToppingsplus every other combo in between.NoLemurs@reddit
The whole premise here is that we want to make illegal state unrepresentable. If we take that as a given, this is the way to do it.
If you actually have specific toppings that should only go on a cream-base thin-wheat-crust pizza, then a
CreamOnWheatThinToppingsenum is the simplest way to enforce that constraint. Any alternative approach is going to be at least as complex. If you want to enforce a constraint about which toppings are allowed with what stuff, you need a way to get that list of toppings, and the enum is pretty much the simplest way to do that.I do agree that in the real world, this is not great. If some degenerate wants pineapple on a cream-base, I can't see why you wouldn't sell that pizza. It's absolutely a mistake to write constraints into your code that don't reflect actual real world constraints.
But if you actually do want to enforce the constraints, this is a good way to do it.
Xiaopai2@reddit
You can do this with sealed interfaces in Kotlin and Java as well.
ShiitakeTheMushroom@reddit
What if I want a cream base with pineapple on my pizza?
GrandOpener@reddit
Yeah, in a world where people not only can but intentionally do order none pizza with left beef, this was probably a poor/distracting choice of metaphor.
FlyingRhenquest@reddit
A local place offers a mustard and saeurkraut one. I'm not sure how many orders they get for it, but it's been on the menu for about a decade. I've ordered it twice. Its surprisingly not disgusting, though it's not really my thing. If I'm in a Reuben mood and can't actually get a good Reuben, I might order one of those.
pragmatick@reddit
That's illegal.
Ignisami@reddit
Then the shop says 'no pizza for you'
ShiitakeTheMushroom@reddit
Thank you! I needed a laugh today.
alvenestthol@reddit
Does this extend well, if the pizza place expands to have a choice of something like 20 toppings, 5 crusts and 5 bases, limited-time options for each choice, last-second additional choices dictated by unexpected needs...
And then the higher-ups come down, and they say you've gotta make this pizza
Full-Spectral@reddit
Well, in reality, that's not something anyone would ever enforce at compile time, so it's not a good choice of example.
JShelbyJ@reddit
In most ways it’s easier because you just add the new variant. It only becomes an issue when you have to decide how to organize your data (does herb dusting the crust count as a topping or a modification of crust?) which is a variant of “the hardest problem is naming things”, but usually the objects are smallest enough that there is only one sensible way to do it.
For limited time toppings, you could add a custom variant that takes a string, price, and a date range so you don’t have to update the code whenever a new one gets added.
ReallySuperName@reddit
It boggles my mind that people tout this as a Rust only feature. I could do the same in any OOP language from the start. Functional languages enhance it further. But no, everything is in terms of "Rust like Result pattern" which I think speaks volumes about the state of the industry.
Chroiche@reddit
Trying to do this in python sucks, you end up with type unions everywhere.
NoLemurs@reddit
I don't think anyone is saying this is a Rust only feature. I personally learned this pattern first in Haskell. I will say that only a few languages can compete with Rust for good ergonomics around algebraic data types.
OP's Java, Kotlin, and Python implementations seemed reasonably idiomatic to me. I commented on the Rust implementation because it felt like OP had missed the natural way to do this there.
Ohh, and I've never even heard of Gleam, so no comments there, though my gut instinct is that a modern language like Gleam ought to have a better way to model this problem.
godofpumpkins@reddit
Once you try sum types (that “enum with stuff”) any language without them feels clunky. Indeed, they’re called sum types because they’re a direct mathematical analog to elementary addition, where structs containing multiple fields are equivalent to multiplication. From that POV, imagine trying to do arithmetic without addition and trying to approximate it with multiplication
Tysonzero@reddit
Of course sum types are just a poor man’s dependent pair.
godofpumpkins@reddit
A dependent pair is like a big sum 😝 people even write it as a capital sigma
Tysonzero@reddit
Dependent sums are very sigma gigachad yes.
godofpumpkins@reddit
We’re gonna need you to teach dependent type theory to Gen Z. If dependent sums are gigachad, where does that leave dependent products? Does Pi mog Sigma?
Tysonzero@reddit
Nice try but isomorphismmaxxing makes the attempt to mog straight cap.
Assuming you have things like parametric polymorphism System F/FC type stuff, maybe in a more limited system you could support Sigma without admitting Pi.
how_gauche@reddit
Sadly I'm still DecidableTypeInferenceMaxxing
jpfed@reddit
Many languages that lack flexible sum types do have the ability to add 1 inhabitant to any type: null. This leads to a sad approximation of sum types:
(X + 1) * (Y + 1) = XY + X + Y + 1 but if you pretend that XY (both populated) and 1 (both null) can’t/ won’t happen, you get X + Y…
godofpumpkins@reddit
Yeah, and many of them (including some in the blog post) also simulate sums by using subtyping (which doesn’t have as nice of a mathematical interpretation) and closing the common super type somehow. Scala and Kotlin like doing it that way, and on one hand it makes me itch and on the other it works fine.
I do love seeing people write down type arithmetic in /r/programming though 😁 but down with the +1! Give me sums and give me products and don’t force me to add 1 to everything
Vidyogamasta@reddit
"does this pizza have ham on it?"
"No, but it has ham on it."
The perils of two ham enums
juhotuho10@reddit
Could be solved with common toppings in addition to base specific ones:
```rs
pub enum Crust {
Thin,
Thick,
}
pub enum CreamTopping {
Potatoes,
}
pub enum TomatoTopping {
Pineapple,
}
pub enum CommonTopping {
Ham,
Olives,
}
pub enum Toppings {),),
Cream(Option
Tomato(Option
}
pub struct Pizza {,
crust: Crust,
base_toppings: Toppings,
common_toppongs: HashSet
}
```
Own-Zebra-2663@reddit
I love the idea, but at least in enterprise applications, constructing valid state is like 90% of the work anyway. Meaning you need to have not-yet-validated state in 90% of your code. A change request for a user comes in, and you spent all the time validating every last element against 5 times joined data from every corner of the application becuase there's a configuration or an exception for everything. By the end, all you have is two lines:
UserChangeCommand cmd = validate(userChangeRequest)
userRepository.update(user, cmd)
So you're only guarding like 2 lines against bad state. Another option might be to have intermittent valid states, but then you're rebuilding your validation codes entire process as a state machine.
Intrepid_Result8223@reddit
It really doesn't matter. After you have made the situation unrepresentable in your API the product owner will bitch and moan until someone in the front end team hacks it with the help of some metadata field not intended to contain this data.
nfrankel@reddit (OP)
I feel seen 😂
Known_Cod8398@reddit
You should check out statum
I think you'd benefit from it
nfrankel@reddit (OP)
Seems like a perfect fit to the theme. Thanks
Supuhstar@reddit
They don't like me for using UInt so much but array indices don't go negative 🤷🏽
josefx@reddit
It gets fun when you iterate backwards over an array and index >= 0 never fails. Or if you try to calculate the distance between two indices and nothing prevents you from just doing indexA - indexB .
Valid indices may not be negative, but using unsigned forces you to carefully consider any operation that might require a signed result.
Supuhstar@reddit
I’m talking about API surface, not implementation details
josefx@reddit
It would be an implementation detail if you wrapped the integer indices in an opaque Index type, by exposing a raw integer or unsigned integer you make the operations they support a part of your public facing API.
Supuhstar@reddit
Those can be exceedingly useful
Full-Spectral@reddit
Or just don't use indices in loops at all, which is fairly doable in some languages.
Supuhstar@reddit
and yes, it’s a rare day when I use an index in Swift or Rust
Full-Spectral@reddit
And the few times you need an index inside the loop, Rust's iter().enumerate() handles most of those without any of the muss and fuss.
Supuhstar@reddit
Mhmm, and Swift's
.enumerated()SaltMaker23@reddit
The whole idea of making illegal states unrepresentable sound noble in theory, the keyword here is theory.
Practice and theory are the same in theory, in practice however ...
TomKavees@reddit
I get what you mean, but minimizing them is good for your domain too
Absolute_Enema@reddit
Yes, but only works if you're ready to also maintain high quality, up-to-date documentation and tests that encode your assumption, and also build your system in preparation for the time where there won't be such a thing as a statically valid Pizza anymore.
But this really doesn't come across in any of these articles; it's usually implied that if you use enough of the "strong" type sauce you'll live in a magic world where nothing could ever go wrong.
teerre@reddit
The whole point of encoding it in the type system is that you don't need "documentation" and "tests". In fact, it's impossible to write tests if your state is unrepresentable, that's what unrepresentable means
orygin@reddit
Where is the limit then? I saw other comments suggesting using a Non zero 8bit unsigned integer to count the ingredients, but in this example I would also like to limit the number of time one ingredient is present to (let's imagine) 8.
Should we have a type that cannot go higher than 8 so that the wrong state is unrepresentable? Or add some business logic, validation and tests so that it doesn't happen?
I'm not sure every system state can be forced to be representable only through a type system.
I'm all in favor on making very wrong state impossible, but the world is messy and evolves over time, and some new state should be added, which may directly clash with the existing system enforced by types
teerre@reddit
The limit is that your program would statically only be able to represent legal states. So yes, you should use
LessThanPositiveEightNonZerofor your ingredientsNow, this assumes that you really only ever want less than eight. Is that a reasonable assumption? It depends. I would argue that for the majority of programs such level of precision is unfortunately not possible, but when it is, of course you should enforce it
SaltMaker23@reddit
In idealized worlds you have these things:
In the real world none of the above hold, a developer that build a rigid system will soon need to loosen it as the world require it to be, because such rigidity is generally embedded in the foundation of a system.
It means the whole foundation will be rapidly shaken, and all modules that are built on top will rapidly spaguetify.
Absolute_Enema@reddit
And that's why software is ever buggier.
teerre@reddit
The whole point of encoding it in the type system is that you can't write a bug
SaltMaker23@reddit
The thing is that in simplified situations where you can afford to design a simple system living in an idealized world a lot of things make a lot of sense.
In the real world, things outside of your control, changing requirements, faulty/unexpected/[you just didn't understand it] external communications or IO, etc...
There are so many things that makes such a rigid approach less than ideal and even more so greatly decreases the ability to have powerful observability around failures.
It also assumes that the developer has perfect future foresight. It creates issues not because it's rigid but because the rigidity create assumptions in other dependent modules, once rigidity is loosened which always happens because the developer didn't have foresight, previously working modules start having problems.
Absolute_Enema@reddit
Very well put.
CatolicQuotes@reddit
What are the problems with it?
SaltMaker23@reddit
See my other comment
PrimozDelux@reddit
If you're writing a library it makes sense.
Pharisaeus@reddit
It's a cool concept for a Hello World or a Pizza Factory or something that's fully specified from the get-go. Good luck trying to model like that some actual real-life software (eg. you end up with tens of thousands of classes/records/structs/enums and no one remembers which goes where). And then maintain it through constant changes (eg. tell your manager you need 2 months to add one button, because it requires re-doing the whole type-system).
CatolicQuotes@reddit
Ok, maybe I'm gonna try it in one of the apps and see the problems I will encounter. But I would do this in f#. What do you suggest instead?
Kache@reddit
Feel like the given examples do a pretty poor job of elucidating the intended finite state machine. Even worse, invalid pizzas are still representable:
In Python, it'd be both clearer and more type-safe with:
Absolute_Enema@reddit
This is the
AbstractPizzaServiceProviderConfiguratorFactoryManagerof the '20s.Kache@reddit
Yeah, the given examples do a pretty poor job of elucidating the intended finite state machine. Even worse, invalid pizzas are still representable:
Feel like the author is trying to cram Java builders into other languages. In Python, it'd be both clearer and more type-safe with:
ZjY5MjFk@reddit
Yea, but it's hard to use that so I'm going to make a factory to generate that Manager to simplify things.
jeenajeena@reddit
Not sure if the Java's and the Rust's version are equivalent: toppings in Java are in a List, so one can add ham multiple times. In Rust presence of ham is a boolean. I might have misunderstood, though.
nfrankel@reddit (OP)
Correct. Actually, toppings should be a Set everywhere.
jesseschalken@reddit
What if I want extra ham? Is a man not entitled to some extra ham on his pizza if he so chooses?
AugustusLego@reddit
If you want to support this, use a hashmap where you can increment the number of each ingredient
Absolute_Enema@reddit
And remember to validate the counts, lest you end up with negative ham.
AugustusLego@reddit
Just set it to be
NonZeroU8backfire10z@reddit
Surely there is no human out there who wants 255 hams on their pizza
AndrewNeo@reddit
Pizzas Georg is an outlier and should not have been counted
ZjY5MjFk@reddit
protip: when ordering pizza online under flow the ham toppings to get 2147483647 ham on your pizza. This is a trick that big pizza doesn't want you to know about.
DarkLordCZ@reddit
But what if I want cheese first, then ham and then another layer of cheese (so it's not one large clump of cheese)?
nfrankel@reddit (OP)
Nooooo!
ChadBroChill_17@reddit
I think the idea here is better explained in this video from elmconf.
derp_b0t@reddit
upvote for Elm
InterestingQuoteBird@reddit
I think this is a perfect example where an object oriented approach fails very fast and hard. This needs a data driven approach for the entities and constrains and a more abstract model in code.
Venthe@reddit
How so? I've employed that quite successfully in OO languages.
InterestingQuoteBird@reddit
I think you either use a functional approach with algebraic data types and pattern matching or model the options and constraints in a data structure. Single inheritance is too limited most of the time.
josephjnk@reddit
The two are not mutually exclusive. I use Scott encodings to do “pattern matching” in class-based multiparadigm code all the time.
jeenajeena@reddit
That was an interesting read. Incidentally: have you ever played with applicative builders (using applicative functors)?
nfrankel@reddit (OP)
Nope. I never came upon these terms to be fully transparent
geeeffwhy@reddit
if “Functor” is an unfamiliar term, and you are invested in preventing illegal representations at compile time, may I suggest that Haskell is a language to study. This whole premise of your post is one of the major design goals of Haskell, which itself makes heavy use of Category Theory to make the sorts of guarantees you are interested in.
which is to say, there is a whole serious and deep field of study around this kind of question, and you can join in.
godofpumpkins@reddit
I feel like it’s a tad more than your hypothesis. There’s an entire field of study dedicated to this topic
moreVCAs@reddit
or a tautology? “only compile-time type checking can make claims about types at compile time”.
chucker23n@reddit
Yeah. And isn't that quite obvious?
I prefer static typing, but pointing out the key thing that defines static typing as an advantage seems a bit biased.
Smallpaul@reddit
Furthermore, it’s simply a workflow complaint. My IDE does type checking as I edit and so does my CI. So I can shift type checking as far left or right as I want at an individual or team basis.
I would have liked to have known what Python’s type system can and can’t represent compared to the other languages instead of quitting that part of the analyst because of the need to execute or integrate mypy.