3 ms·
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 prom
by schrodinger 2y ago
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)
}
})
}
}
- rescripting 2y ago> 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.
- schrodinger 2y agoI don't know if I'd agree better ergonomics in this case, since you lose a lot. What if you wanted to load your test cases from a CSV? e.g. you had a two column CSV with 1,000 words, first column mixed case, second case lowercase. They're mixed languages, with unicode oddities sprinkled in. So it really is worth testing against such a corpus. In Go, I could simply open a csv checked into the codebase and use it for my test cases. I'm sure it's possible, but you probably have to break way from the macro (which I argue doesn't add anything) and take a completely different approach. In Go, it's JUST CODE. Again, I really like Swift (besides xcode performance at times… ugh!). It's possible to find flaws in both languages yes still like them. Swift knocks Go out of the water in so many ways. But I'm scarred from ruby on rails magic growing and growing until you had to be an expert in the magic to write code, when the point was for the magic to make it easier.
- tiltowait 2y ago> 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.
- schrodinger 2y agoMaybe I'm being a bit harsh myself, but with the Go code, it's the same syntax that I use whenever I would write a for loop anywhere else in my production codebase. It's not something special to testing, it's literally _just code_. However, I do like Swift, I in fact single-handledy wrote an entire iPhone app used by 10s of thousands of people on it and there were a lot of wonderful things, like nullability being "solved", and smart enums etc. This isn't a language war, I like them both, and could point out flaws in either just as easily. I just feel like Swift has a bit too low of a bar for adding new features, which leads to a lot of nice things, but also a lot of functionality bloat. I can look at Go written 10 years ago and it'll largely be the same as how it'd be written today; with Swift, it's night and day. I built the aforementioned app in 2014 to 2017 (mostly the first year), and there's so much I don't recognize. I think one of the things that bothered me the most where I feel they sort of "jumped the shark" is with ViewBuilders. It looks like code, acts like it 99% of the time, but it isn't. Does that `var body: some View { ... }` return a view anywhere? No, and it'll break if you try! It's a whole different concept to admittedly offer a very nice experience emnulating React with idempotent views. But still, it's awfully strange that this works: struct IntroView: View { @State private var text = "Yes" var body: some View { VStack { Text(text) Button("Toggle") { text = text == "Yes" ? "No" : "Yes" } } } } But this does not, because it's not _actually_ code. struct IntroView: View { @State private var text = "Yes" var body: some View { VStack { Text(text) Button("Toggle") { text = text == "Yes" ? "No" : "Yes" } print("debugging line") } } }
- Aloisius 2y agoSwiftUI's ViewBuilder isn't part of the Swift language. It's a macro implemented in Swift. Now whether macros themselves were a good idea or not, that's an entirely different question.
- Aloisius 2y ago@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.