top | item 41572114

(no title)

skrrtww | 1 year ago

I wish they would stop introducing more magic syntaxes.

discuss

order

schrodinger|1 year 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)
          }
        })
      }
    }

tiltowait|1 year 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.

Aloisius|1 year 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.

rescripting|1 year 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.

MBCook|1 year ago

Is there a specific one you’re objecting to in 6?

skrrtww|1 year ago

Since you asked:

The provided `drinkable` example I think is pretty bad and it's very surprising to me that this is a headline feature.

  protocol Drinkable: ~Copyable {
    consuming func use()
  }

  struct Coffee: Drinkable, ~Copyable { /\* ... */ }
  struct Water: Drinkable { /* ... \*/ }

  func drink(item: consuming some Drinkable & ~Copyable) {
    item.use()
  }

  drink(item: Coffee())
  drink(item: Water())

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.