Live data from Hacker News

My Swift Dilemma

owensd.io

121–129 of 129 posts

Re: My Swift Dilemma

#121
post #108
post #98

Earlier quoted context omitted.

There are degrees of static typing. Swift tries to get close to Haskell, but without the full set of tools to do so without losing things along the way. This is sort of the worst of both worlds. In the meantime Strongtalk already demonstrated that you can write a fairly rich optional type system to reap the compile time benefits of static typing while retaining the runtime power of message passing. I think that would…

Yes, there are degrees of static typing (hence, some people unfortunately disregard great type systems because they are only familiar with Java's cumbersome one). The thing is, as soon as you realize that unit testing (of the kind proposed by 3+4=7 ) alone is not enough, or even adequate, and that you must use more advanced tools, and that more advanced tools can and should be automated whenever possible... you will…

Since we're talking about Swift generics here, you have to realize that the point is not whether generics / static typing can be useful, but if the type of generics and static typing as enforced by Swift is good enough.

The problem here is that Swift has incomplete generics. In several aspects they are significantly worse than even Java's(!)

Together with its brand static typing and type inference that doesn't always work, you are really working against the compiler to get things to work. Not because you get the types wrong, but because you have to work around the incompleteness of the generics implementation!

Swift is not Haskell and should not be mistaken for it. The problem with Swift's generics is one of incompleteness, inconsistencies and problematic trade-offs to maintain ObjC compatibility.

Re: My Swift Dilemma

#122
post #111

Earlier quoted context omitted.

Well, in practice the broken generics stop large amounts of reasonable uses of generics, the lack of co/contravariance force you into hideous workarounds everywhere. As for the promised runtime performance improvements they have yet to surface and worse: the language is still plagued by extremely uneven performance, even for optimized builds. I could go on at great length citing issues that will be hard to fix within…

I haven't done that volume but I have written a few thousand lines including this branch of a GCD wrapper library which uses generics to allow return values to be passed between the closures running on the different threads in a typesafe way: https://github.com/josephlord/Async.legacy/blob/argumentsAnd... It was a nightmare getting it all building right (generic methods on generic classes took me a bit of time to sor…

I regard Swift's inconsistent runtime performance and over-reliance on compiler optimizations as a major problem with the current state of the language.

It does not help that some slowdowns are due to bugs, and some due to questionable implementation details that require knowledge of how the runtime operates.

Of course, knowing about the runtime is necessary for all micro-optimization regardless of language, but Swift currently requires it almost all of the time.

The difference between Swift and ObjC/C is glaringly obvious.

Your GCD wrapper examples use only the simplest form of generics, with no constraints at all. Magnify the problems you had to get this to work by x10.000 and you get how easy it is to do anything complex with Swift's generics. It's a maze of missing features and bugs.

I think it's very understandable that Owens got fed up with it.

Re: My Swift Dilemma

#123
post #114

Earlier quoted context omitted.

You read the last chapters of books too just to see if they're worth reading?

How does that analogy make any sense? An uniformed post with several questionable arguments is totally fine to judge someone's understanding of a language. And if it's the "last chapter" of his posts (that is, something he wrote after several previous posts exploring the language), it's even better to see if his opinions are "worth reading". In the sense that a first post with his initial impressions of the language…

"How does that analogy make any sense?"

Well, I told you that the blog forms a narrative, of which the entry forms a part. His reasons for reaching the conclusions in this entry is based on earlier investigations, which are detailed in previous blog entries. The accusation that this entry is lacking context is similar to claiming the same from reading the end of a book, in that of course it won't make any sense unless you actually have read the parts that form the context.

The his experiments with the language and basis for his statements is investigated in detail in the previous entries. When I point that out, you claim to be unwilling to read them because the last entry did not make sense. How is this NOT like having dismissed the last chapter as not making sense as a stand-alone story and refusing to read the rest?

Maybe your confusion simply stems from your interpretation of this as a "first impression"-kind of text? Here is his first real blog about his first impressions: http://owensd.io/2014/06/09/swift-future-for-app-developers....

The text we're discussing on the other hand is to be understood as an commentary on the language after using for quite a while.

To me (and just to put this in perspective, I've written well in excess of 10k LOC of Swift during the summer) his issues make perfect sense - when seeing them with a somewhat experienced eye. For example, issues with Swift generics isn't immediately apparent. It's only after using them for a while that you can say that the missing features ARE indeed lacking for everyday usage, and this is not just a theoretical problem.

Similarly, the problems with Optional isn't really obvious from the beginning. (And optionals seemed like such a win initially. Built in Optionals! The language built to support it everywhere. Seamless interop with ObjC. Safe unwrapping! Syntax sugar for flatmap etc etc. And then it ended up being just as much a burden as it was a help)

This would suggest that the very reason that you see this as "an uniformed post with several questionable arguments" is actually because you have very little experience in the language. Consequently you see what you believe are meaningless or "questionable" arguments, simply because you believe someone wrote the blog article with similar [limited] experience with the language.

Since his issues aren't obvious at a glance, you conclude that they are false, never entertaining the idea that they represent a much deeper understanding of the language than you have achieved.

Re: My Swift Dilemma

#124
post #120
post #117

Earlier quoted context omitted.

There are actually many poor claims you made in your posts about "good tests". > I'm aware that addition is a toy example, but suppose we want to test our implementation: > Except for very simple verification, to exclude obviously broken implementations, I'd rule out testing specific values such as 3+4=7. And, like you said, performing an exhaustive exploration of all values is out of the question. > So I'd try prope…

Note I never claimed unit testing should be disregarded (I practice it and recognize its benefits), or that static types catch all errors, or that add(x,y) was anything but a toy example. Please note I didn't throw out add(3,4)==7 , but instead pointed out it's terribly inadequate as a test. Additional testing tools must be employed; unit testing alone of this kind is not enough. With property testing you're still no…

> Please note I didn't throw out add(3,4)==7, but instead pointed out it's terribly inadequate as a test. Additional testing tools must be employed; unit testing alone of this kind is not enough.

I don't follow what you are saying here at all... It's an API test with no external integration points, what other kind of tests besides unit tests would you have? Your `add(x, y) = add(y, x)` are still unit tests.

Also, you said that you would "rule out testing specific values such as 3+4=7". You have to test specific values; the contract of the function is:

    f(x, y) = z
Where z is the mathematical sum of x and y.

Specific value testing is the only way you can verify that claim for a given set of inputs.

>> The only thing that static typing provides, in regards to test cases, is this [example]

> This assertion is wrong. Static typing done well provides a lot of things "for free", such as restricting incorrect behavior.

This was taken out of context; this was in reference your to "generator" of test values for X and Y. The type signature alone is inadequate to generate test values.

Regardless,

    f :: [a] -> a
Is no harder to test in a dynamic language.

    let r = f([...])
    assert(r, correct_value)
    assert(r.type, correct_type)
It's up to the contract of the function to determine what, if any, validation needs to be done on the input. This is true regardless of type system. The only question is this: do you also check the type.

Again, type is only _one_ of the constraints that get applied to parameters. In the add function example, the other constraints are:

    1. x 
Plus the similar for TYPE_MIN. In the `addbase2` example, additional constraints are:

    1. x power of 2
    2. y power of 2
In this example, we still need to add verification for two-thirds of the constraints.

On the flip-side, with generics, especially with the type of generics we see in Swift (using the `f :: [a] -> a` example), you'll probably need to model constraints of the collection, the element type of the collection, and the type of indexer that is being used if you wish to make your function actually work.

And then your implementation only works for those that rigidly adhere to the type conformance, where as the dynamic one can work for any type that conforms to the protocol, whether loosely or explicitly.

This flexibility is very powerful, is not hard to code safely around, and requires significant less code gymnastics before you can even get your code compiling.

Re: My Swift Dilemma

#125

Earlier quoted context omitted.

I do agree with your points, but i think you've over generalised them into a degree that discussion is not only unnecessary but also childish. Let's restore the context back to Swift in iOS programming to match its targeting market, shall we? Could you come up with 1 use case which: .. generic is really useful. .. the problem hasn't solved by well recognised 3rd party lib/framework.(by "well recognized" i mean github…

> generic is really useful Container types are the most obvious. Lists, Arrays, Dictionaries, Vectors, Stacks. You'd be hard pressed to find code that doesn't use a container type of some kind.

Incidentally the problem is much smaller than in a statically typed language with containers.

Compare the difference between a Java list prior to generics and afterwards. Using it in Java was an orgy of object casts. This is not the case for dynamically typed languages.

In fact, the common approaches to containers valid in a statically typed language is largely wrong in a dynamic language.

Re: My Swift Dilemma

#126
post #124
post #120

Earlier quoted context omitted.

Note I never claimed unit testing should be disregarded (I practice it and recognize its benefits), or that static types catch all errors, or that add(x,y) was anything but a toy example. Please note I didn't throw out add(3,4)==7 , but instead pointed out it's terribly inadequate as a test. Additional testing tools must be employed; unit testing alone of this kind is not enough. With property testing you're still no…

> Please note I didn't throw out add(3,4)==7, but instead pointed out it's terribly inadequate as a test. Additional testing tools must be employed; unit testing alone of this kind is not enough. I don't follow what you are saying here at all... It's an API test with no external integration points, what other kind of tests besides unit tests would you have? Your `add(x, y) = add(y, x)` are still unit tests. Also, you…

I think you are underestimating the power of statically typed generics when using a language with a decent type system.

In your example:

    let r = f([...])
    assert(r, correct_value)
    assert(r.type, correct_type)
This doesn't test everything we need to know. For example, the following function passes your asserts (in pseudocode):

    f(a_list):
        if (a_list instanceof List[Int]):
            return 0
        else
            ... other stuff ...
whereas the original, statically typed version of the function with signature

    f :: [a] -> a
cannot ever do that. This is a profound insight. It cannot return zero "in the case of a list of ints". It doesn't know anything about its input if you don't tell it. And you shouldn't tell it, either, unless you have a very specific reason to do so.

Also, in programming language with decent static typing (that is, not Java or C++; I wouldn't know about Swift to comment), there is a huge additional difference between the two functions:

I can promise you my function doesn't write to disk, doesn't output to the screen, etc. You cannot promise the same with your function. Your function might work when it has access to the disk, as in your test environment, but fail on production where it does not. Ok, so you inspect the code to make sure your function (or any function it calls) don't do I/O. But I don't have to do this, because the type system tells me my function is side-effect free.

So now you have some pretty powerful assurances in favor of my statically typed function:

- It doesn't perform side effects. I don't know about the dynamic function.

- It doesn't produce any value out of thin air; it must work with the list I passed it, because it doesn't know anything else. It doesn't know how to create new values.

- As a consequence of the above, there are fewer possible implementations of my function than of yours, excluding no-ops.

This is a kind of testing "for free" that you don't have with dynamically typed languages. And it is pretty powerful.

Yes, you can cover a lot of cases with unit tests in a dynamic language, but why not let the computer do the boring work for you? It's what computers are there for. Focus on the interesting test cases instead.

Re: My Swift Dilemma

#127
post #85

Earlier quoted context omitted.

They said they made a version of it in Swift. The actual version deployed on the App Store was written in Objective-C.

Are you sure? That's not what I heard, but I could be wrong.

I tried to hunt down the statement but without success. If I didn't imagine it, then I'm fairly sure it was in the Apple dev forums. However, I did find a posting where someone said they hadn't found the Swift runtime libs bundled with the WWDC app.

So, either the WWDC app had the Swift runtime statically compiled into the app, or it was written in ObjC. Checking the size of binary of the app should give a hint - the runtime libs you need to bundle are huge.

Re: My Swift Dilemma

#128
post #126
post #124

Earlier quoted context omitted.

> Please note I didn't throw out add(3,4)==7, but instead pointed out it's terribly inadequate as a test. Additional testing tools must be employed; unit testing alone of this kind is not enough. I don't follow what you are saying here at all... It's an API test with no external integration points, what other kind of tests besides unit tests would you have? Your `add(x, y) = add(y, x)` are still unit tests. Also, you…

I think you are underestimating the power of statically typed generics when using a language with a decent type system. In your example: let r = f([...]) assert(r, correct_value) assert(r.type, correct_type) This doesn't test everything we need to know. For example, the following function passes your asserts (in pseudocode): f(a_list): if (a_list instanceof List[Int]): return 0 else ... other stuff ... whereas the or…

> I can promise you my function doesn't write to disk, doesn't output to the screen, etc. You cannot promise the same with your function.

WHAT?!

Your type signatures have absolutely no assurances with regards to side effects. They cannot even make a claim that the function is thread safe, let alone that it doesn't write to disk our output to the screen.

I'm baffled at why you think that is true:

    int foo(int bar):
      // network call here
      // write a log to disk here
      // change a global value here

      return happy_int
And you are woefully mistaken about about this claim as well: "it must work with the list I passed it, because it doesn't know anything else."

Many languages that actually have good generic type systems allow for type specialization. That means that I can provide different implementations for different types. So in the contrived example of you doing something completely different with my list of ints in the dynamic version is completely possible in many statically typed languages too.

Re: My Swift Dilemma

#129
post #128
post #126

Earlier quoted context omitted.

I think you are underestimating the power of statically typed generics when using a language with a decent type system. In your example: let r = f([...]) assert(r, correct_value) assert(r.type, correct_type) This doesn't test everything we need to know. For example, the following function passes your asserts (in pseudocode): f(a_list): if (a_list instanceof List[Int]): return 0 else ... other stuff ... whereas the or…

> I can promise you my function doesn't write to disk, doesn't output to the screen, etc. You cannot promise the same with your function. WHAT?! Your type signatures have absolutely no assurances with regards to side effects. They cannot even make a claim that the function is thread safe, let alone that it doesn't write to disk our output to the screen. I'm baffled at why you think that is true: int foo(int bar): //…

> Your type signatures have absolutely no assurances with regards to side effects. [example]

This might be true for Swift (which I suspect it is), but it's not true in the wider ecosystem of statically typed languages. A language with a type system with allows controlling side effects, such as Haskell, does indeed make such assurances. In Haskell, for a function to make network calls or output to disk, it must live within the IO monad (which can be seen by its type!). I'm discussing Haskell here because I'm more familiar with it, but there are alternative mechanisms in other languages.

Compare:

    f :: [a] -> a   -- I promise you this function doesn't do any I/O
with

    f :: [a] -> IO a  -- this function may do I/O
This is a powerful assurance right there! Of course, Haskell programs as a whole must live in the IO monad (a program without any kind of I/O is useless). But you're encouraged to write as much as the program as you can as pure functions, which can then be tested (unit tested or whatever you prefer) with the very useful knowledge that they cannot do I/O.

Next, generics systems. Languages with OOP and generics, such as Scala and, I suspect, Swift, let you do all sorts of naughty things within generic functions.

But doing generic programming like in Haskell is way safer in this regard. No, you are not allowed specialize the type in f :: [a] -> a. Doing so would be unsafe.

So let's go back to my claim: the above function cannot do anything else but produce a value out of the list I passed it. It doesn't know how to produce something else out of thin air. It cannot "inspect" the value of its type; it has no unsafe "instanceOf" operator. It cannot even apply any operation to the values of the list (except of course list operations), since I didn't declare the generic type had any. This is a very powerful assurance that a dynamic language cannot make, and one that simplifies the tests I have to write.

Because of this property, you are encouraged to write, whenever possible, functions that are as generic as possible. Sometimes you can't, but then you'll specify as little as possible, such as:

   sumAll :: Num a => [a] -> a  -- "a" is a number with operations +, -, etc.
And then you'll have additional operations available for your type a, but not as many as if you were writing this with a dynamically typed language with no assurances at all!

Once you realize this, you'll start thinking of generic programming as a tool that constrains the kind of errors you can make (because you have less choices to make, so to speak). And this has a huge impact on testing!

> So in the contrived example of you doing something completely different with my list of ints in the dynamic version is completely possible in many statically typed languages too.

Of course, many statically typed languages are no better than dynamic languages in this regard. I was talking about decent type systems. Not sure where you'll place Swift, though.

Even with languages which allow instanceOf checks, such as Java and I'm willing to bet most OO languages, the practice is frowned upon and wouldn't pass a code review. Unless, of course, there was no other way to solve the problem, but this really would limit the usefulness of generic programming.

Post reply on HN