I would be surprised, if you couldn’t also sell an account with a bunch of stars on the black market to bot farm operators…
I would be surprised, if you couldn’t also sell an account with a bunch of stars on the black market to bot farm operators…
Right, so this is the part where I get to sound like a smart ass, because I snuck a “tons of” into there.
What you do always need, is tests serving as a specification of the intended behavior, to document it for your team members and your future self.
But the thing that static typing is an alternative to, is integration tests for many code paths. For example, in dynamic languages you have no reassurance that a call to your database library still works, unless you have an integration test which actually calls into the database. Similarly, you hardly know whether the many error-handling code paths are working, unless you write tests for those, too.
In static languages, we don’t test this stuff outside of the specification-like integration tests, because the database library and the error handling library are already separately tested, and the type system ensures that we interface with them correctly.
Eh, it’s most definitely part of it, but the biggest time sink that I expect when working with Python is fixing the build system every two weeks on different devs’ PCs. I do imagine, if you eventually find a solution that works on most PCs that this workload will go down, but we had a substantial Python part in my previous project and over the course of the 1½ years that we worked on it, it really felt like we were making negative progress. Near the end of it, I couldn’t use PyCharm anymore, because I couldn’t figure out for the life of me, how to make it recognize the dependencies again.
Personally, my estimate doubles when we’re asked to implement something in Python…
Yeah, the alternative to static typing is to write tons of unit tests, which definitely adds a lot more code to your codebase.
Fine, sure, but to pretend that a language from half a century ago is the be-all and end-all of human wisdom, is ridiculous. The field is progressing, so on average newer languages improve compared to old ones.
What the hell? Is this the new “Microsoft ❤️ Cancer Open-Source” ?
Well, because I’m of a very different opinion about its readability. If you know the format, then sure, you can mostly read it as expected. But our logs are often something that customers or sysadmins will want to read. If it says Retrying in PT5S...
in there, they’ll think that’s a bug, rather than a duration.
And yeah, I almost figured it was for de-/serialization. I guess, that’s something where I disagree with the designers of Java.
In my opinion, you don’t ever want to rely on the implicit behavior of the language for your serialization needs, but rather want to explicitly write down how you’re serializing. You want to make a conscious decision and document that it’s the ISO 8601 format, so that if you need to hook up another language, you have a chance to use the same format. Or, if you need to change the format, so that you can change the one serialization function, rather than having to find all the places where a .toString()
happens.
Admittedly, the Java devs were between a rock and a hard place, due to them having to implement .toString()
and the meaning of .toString()
being kind of undefined, i.e. it’s never stated whether this is a format for serialization, for debugging or for displaying to the user. And then I guess, because it didn’t explicitly say “for debugging” on there, they felt it was important to go with a standard format.
The person who announced this change noted in a reply that “Negative filtering is coming soon.”…
Nope. The actual disclosure dialog is a bit more precise in its wording: https://itch.io/t/4309690/generative-ai-disclosure-tagging
Implementation of the add()
function is here: https://github.com/raldone01/python_lessons_py/blob/main/lib.py
In Rust, as far as I understand anyway, traits define shared behavior.
They’re certainly the concept closest to e.g. C#/Java interfaces. But you can also define shared behaviour with enums, as Rust’s enums are on steroids.
Basically, let’s say you’ve got two existing types TypeA
and TypeB
for which you want to define shared behaviour.
Then you can define an enum like so:
enum SharedBehaviour {
A(TypeA),
B(TypeB),
}
And then you can define the shared behavior with an impl
block on the enum:
impl SharedBehaviour {
pub fn greet(&self) {
match self {
SharedBehaviour::A(type_a) => println!("Hi there, {}!", type_a.name),
SharedBehaviour::B(type_b) => println!("Hello, {}!", type_b.metadata.forename),
}
}
}
On the flipside, Rust doesn’t have superclasses/inheritance for defining shared behaviour.
I feel like this has somewhat shifted in recent years. Due to type-aware IDEs/editors being pretty much universal now, having types speeds you up in that initial phase, too. And type inference eliminates much of the inertia, too.
Yep, some code examples from the official documentation. This:
printPersons(
roster,
(Person p) -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25
);
…is syntactic sugar for this:
interface CheckPerson {
boolean test(Person p);
}
printPersons(
roster,
new CheckPerson() {
public boolean test(Person p) {
return p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25;
}
}
);
…which is syntactic sugar for this:
interface CheckPerson {
boolean test(Person p);
}
class CheckPersonEligibleForSelectiveService implements CheckPerson {
public boolean test(Person p) {
return p.gender == Person.Sex.MALE &&
p.getAge() >= 18 &&
p.getAge() <= 25;
}
}
printPersons(roster, new CheckPersonEligibleForSelectiveService());
The printPersons
function looks like this:
public static void printPersons(List<Person> roster, CheckPerson tester) {
for (Person p : roster) {
if (tester.test(p)) {
p.printPerson();
}
}
}
Basically, if you accept a parameter that implements an interface with only one method (CheckPerson
), then your caller can provide you an object like that by using the lambda syntax from the first example.
They had to retrofit lambdas into the language, and they sure chose the one hammer that the language has.
Source: https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
I always hated the implementation for .toString()
of Duration
. It gives you a string like that: PT8H6M12.345S
(not a hash)
Apparently, it’s an ISO 8601 thing, but what the hell am I supposed to do with that?
It’s not useful for outputting to end users (which is fair enough), but I don’t even want to write that into a log message.
I got so used to this just being garbage that I would automatically call .toMillis()
and write “ms” after it.
Well, and not to gush about Rust too much, but I recently learned that its debug string representation is actually really good. As in, it’s better than my Java workaround, because it’ll even do things like printing 1000ms as 1s.
And that’s just like, oh right, libraries can actually provide a better implementation than what I’ll slap down offhandedly.
It makes it look like they’re just adding random noise to avoid colliding with existing syntax. Maybe they can try a UUID next time…
Yeah, I came to Rust from Scala and Kotlin, where equality is default-implemented (for case class
and data class
respectively, which is basically all we ever used), so this meme surprised me a bit.
I do actually like that you can decide a type cannot be compared, because sometimes it really just doesn’t make sense. How would you compare two HTTP clients, for example? But yeah, it certainly is a choice one can disagree with.
I find these videos give a very visual explanation and help to put you into the right mindset: http://intorust.com/
(You can skip the first two videos.)
Sort of when it clicked for me, was when I realized that your code needs to be a tree of function calls.
I mean, that’s what all code is anyways, with a main-function at the top calling other functions which call other functions. But OOP adds a layer to that, i.e. objects, and encourages to do all function calls between objects. You don’t want to do that in Rust. You kind of have to write simpler code for it to fall into place.
To make it a bit more concrete:
You will have functions which hold ownership over some data, typically because they instantiated a struct. These sit at the root of a sub-tree, where you pass access to this data down into further functions by borrowing it to them.
You don’t typically want to pass ownership all over the place, nor do you typically want to borrow (or pass references) to functions which are not part of this sub-tree.
Of course, there’s situations where this isn’t easily possible, e.g. when having two independent threads talking to each other, and then you do need Rc
or Arc
, but yeah, the vast majority of programming problems can be solved with trees of function calls.
The first iteration of the Rust compiler was written in OCaml…
There’s no real mobile app for it, is kind of my personal main reason why I didn’t pick it up…