I agree! I'm a Go programmer, and while I do wish it had some more features at times, Swift is an example of how it can easily go out of control and ruin a promising language.
For example tests, there's so much magic. How do I know it runs the test for each item in the arguments array? What if there were multiple arguments? After using Go for close to a decade now, I'm really seeing the wisdom of avoiding magic, and making your testing code the same language as your building code! Compare:
Swift:
@Test("Continents mentioned in videos", arguments: [
"A Beach",
"By the Lake",
"Camping in the Woods"
])
func mentionedContinents(videoName: String) async throws {
let videoLibrary = try await VideoLibrary()
let video = try #require(await videoLibrary.video(named: videoName))
#expect(video.mentionedContinents.count <= 3)
}
Go:
func TestMentionedContinents(t *testing.T) {
tests := []struct{ Name string }{
{"A Beach"},
{"By the Lake"},
{"Camping in the Woods"},
}
for _, tt := range tests {
video, err := library.FindVideoByName(tt.Name)
if err != nil {
t.Fatalf("failed to get video: %v", err)
}
if len(video.MentionedContinents) > 3 {
t.Errorf("video %q mentions more than 3 continents", tt.Name)
}
}
}
Go with timeout handling in case the FindVideo function takes too long (idk Swift magic well enough to know if it'd do this automatically!)
func TestMentionedContinents(t *testing.T) {
tests := []struct{ Name string }{
{"A Beach"},
{"By the Lake"},
{"Camping in the Woods"},
}
for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Millisecond)
defer cancel()
video, err := library.FindVideoByName(ctx, tt.Name)
if err != nil {
t.Fatalf("failed to get video: %v", err)
}
if len(video.MentionedContinents) > 3 {
t.Errorf("video %q mentions more than 3 continents", tt.Name)
}
})
}
}
> How do I know it runs the test for each item in the arguments array?
At the risk of coming across a bit rudely: this feels analogous to asking “how do I know `for _, tt := range tests` loops over every element in the array?” Both are language/syntactic constructs you have to learn.
@Test, #require and #expect are just macros. You can expand them if you want to see what they do (or just look at the swift-testing code itself).
Perhaps I'm just used to Python unit testing with similar decorators. Presumably, if you need to pass in two arguments, you'd either pass arguments: an array of tuples or a tuple of arrays for combinatorial testing.
> How do I know it runs the test for each item in the arguments array
I mean the APIs aren’t magic; you can “inspect macro” to see what code is generated at compile time which boils down to something similar to the Go code with better ergonomics.
Here we have a drink() function that either accepts something `Copyable` OR non-Copyable (uh, I mean, `~Copyable`) and either consumes it...or doesn't? That seems to me like a fountain for logic errors if the function behaves completely differently with the same signature (which is, in fact, explicitly labeled `consuming`). It seems like it should not just compile if you try to call this with a `Copyable` type like Water, but it does.
The syntax for representing this complex and weird concept of "maybe consuming" being `consuming some Drinkable & ~Copyable` is also just totally gross. Why are we using bitwise operation syntax for some weird and logically incoherent kludge? We cannot apply these & and ~ operators indiscriminately, and they do not mean the same thing that they logically mean in any other context, but this function definition definitely implies that they do.
schrodinger|1 year ago
For example tests, there's so much magic. How do I know it runs the test for each item in the arguments array? What if there were multiple arguments? After using Go for close to a decade now, I'm really seeing the wisdom of avoiding magic, and making your testing code the same language as your building code! Compare:
Swift:
Go: Go with timeout handling in case the FindVideo function takes too long (idk Swift magic well enough to know if it'd do this automatically!)tiltowait|1 year ago
At the risk of coming across a bit rudely: this feels analogous to asking “how do I know `for _, tt := range tests` loops over every element in the array?” Both are language/syntactic constructs you have to learn.
Aloisius|1 year ago
Perhaps I'm just used to Python unit testing with similar decorators. Presumably, if you need to pass in two arguments, you'd either pass arguments: an array of tuples or a tuple of arrays for combinatorial testing.
rescripting|1 year ago
I mean the APIs aren’t magic; you can “inspect macro” to see what code is generated at compile time which boils down to something similar to the Go code with better ergonomics.
MBCook|1 year ago
skrrtww|1 year ago
The provided `drinkable` example I think is pretty bad and it's very surprising to me that this is a headline feature.
Here we have a drink() function that either accepts something `Copyable` OR non-Copyable (uh, I mean, `~Copyable`) and either consumes it...or doesn't? That seems to me like a fountain for logic errors if the function behaves completely differently with the same signature (which is, in fact, explicitly labeled `consuming`). It seems like it should not just compile if you try to call this with a `Copyable` type like Water, but it does.The syntax for representing this complex and weird concept of "maybe consuming" being `consuming some Drinkable & ~Copyable` is also just totally gross. Why are we using bitwise operation syntax for some weird and logically incoherent kludge? We cannot apply these & and ~ operators indiscriminately, and they do not mean the same thing that they logically mean in any other context, but this function definition definitely implies that they do.