7 ms·
Swift – Why Annoying Is Good
- mikeash 12y agoIt's been interesting and amusing to watch the reactions to Swift, especially among those without much exposure beyond ObjC or JavaScript or similar. Many people encounter difficulties and automatically blame the language, as a sort of reflexive action to blame whatever changed last. Sort of like the classic, "You removed that virus last week, and now the computer's on fire, so this is your fault." Regarding JSON, it is annoying to work with in Swift, but when you get down to it, it's a fault of JSON in general and the available parsers in particular. There ought to be ways to describe and validate the desired structure to the parser, or failing that, ways to tell the parser to fetch a value of a particular type and nicely produce an error if it doesn't match. Pretty much as the article says, it's hard to work with JSON correctly in any language, Swift just doesn't let you take the easy way out. In the ideal language, anything that's hard to write is so because the problem is hard. No language is ideal, but whenever we're working with any new language and something seems harder than it ought to be, we should at least stop and wonder if maybe the "easier" languages we're comparing it to are only easier because they let us do things wrong.
- drivingmenuts 12y ago> if let value = blah Language constructs like that seem wrong to me (it's the "let"). That's just one of my mental blocks against Swift.
- mikeash 12y agoI see this sort of thing a lot and I still can't understand it. Syntax is, to me, completely trivial. Power through it and get on with life. For an experienced programmer, looking at code should be like that scene in The Matrix where the guy points to a screen full of symbols and says he just sees "blonde, brunette, redhead." It goes the other way too: quite a few people have described disliking Objective-C's weird bracket syntax and much prefer Swift because of that, and I can't understand that either. It's just syntax.
- embwbam 12y agoIt's directly related to how familiar the syntax is. If a syntax is very foreign, it's difficult for your brain to read it, and it is uncomfortable. People don't like being uncomfortable :) The more syntax you learn, the less new forms confuse you.
- mattgreenrocks 12y agoExcept learning the syntax of a new language is trivial compared to, say, learning a language with new paradigms. Don't train your brain to run away from the sight of something that is temporarily uncomfortable. It's intellectually lazy and promotes a passive mindset. It's not like all your programming skills just fall apart once you leave your blub language.
- bunderbunder 12y agoI will say that I've always been uncomfortable with binding new values inside an if-statement's test. As far as syntax goes, it's a good thing that it's possible: I'm assuming here that in Swift every statement is an expression, which is a good thing, and such a feature naturally means that doing this sort of thing should be possible. But stylistically it just feels ugly to me. I don't know if Swift has an equivalent to either of the following, but I'd prefer either of them as a cleaner idiom: Null-coalescing a la C#: self.imageUrl = json["imageUrl"].string ?? "http://example.com/empty-avatar.png"; Pattern matching a la F#: self.imageUrl <- match json["imageUrl"].string with | Some(value) -> value | None -> "http://example.com/empty-avatar.png"
- mikeash 12y agoBoth of those work in Swift too. The ?? operator exists verbatim, and the matching can be done using a switch/case statement. Unfortunately, it is not true that every Swift statement is an expression. The if/let construct is actually a special case. That means that you can't do something like: if (let x = foo) && (let y = bar) { ... Which is too bad.
- 12y ago
- tel 12y agoYou're probably already aware of this, but Haskell's JSON parser (named Aeson) does exactly what you ask for. In particular, it can be operated in two modes: 1. Untyped mode. Here we parse out values of type, amusingly, Value which reflect the total, untyped structure of JSON. We can then express queries deep inside of that structure and see what get's pulled out. For instance, using the `lens-aeson` package v :: Value x :: Maybe Integer -- Maybe means it might fail x = v ^? key "foo" . key "bar" . key "baz" . ix 3 . _Integer -- looks a bit like XPath, right? 2. Typed mode. Now we suggest to Aeson that some of the types of our program have direct translation into or from JSON (or both) and let the parser produce these translations in a type safe way. For instance, should we have a type like data Point = Point { x :: Double, y :: Double } then we can introduce a translation like instance FromJSON Point where parseJSON = withObject "Point" $ \o -> Point <$> o .: "x" <*> o .: "y" which indicates that JSON objects of a syntax like `{"x": 1, "y": 1}` can be parsed into `Point`s. Technically, the parser designed there is minimal in that JSON objects with more fields can still be properly parsed, although that can be fixed. Finally, you can easily anoint your untyped mode XPath-like queries with typed fragments. This is especially powerful if your typed fragments include untyped JSON Value fragments inside of them. It means that we can use lenses to dive into an untyped JSON Value, parse some fragment of it into a Point in a type-sensitive fashion, then dive further into that Point. If Points contained more JSON values then that "further dive" could be untyped or typed again.
- mikeash 12y agoThanks, I wasn't actually aware of any of this. It's cool to see how other languages handle this stuff. Better JSON parsing has been a constant thing of mine and I'm hoping to build some good tooling for Swift once it stabilizes, so it's great to get ideas from other stuff that's out there.
- eridius 12y agoRust has a good approach here as well, that's related to Haskell's approach. In Rust, a type can implement the traits `Encodable` and `Decodable`, which are general traits that represent the ability to encode to and decode from some arbitrary format, using objects of type Encoder and Decoder. Each supported format then provides implementations of Encoder / Decoder. The benefit of this approach is the Encodable/Decodable implementations can be automatically derived in a lot of cases. For JSON specifically, the serialize::json module provides Encoder/Decoder implementations, as well as a streaming JSON parser type that can be used to consume JSON outside of the Encodable/Decodable system, and a Builder object that consumes the streaming parser to produce a generic untyped "Json" value that represents the entire structure of the Json. And of course it has some convenience functions that simplify common operations like encoding a value into a string. Given all this, the equivalent of the Haskell operations looks like: let v: Json = ...; // get the Json value // the following traverses the Json to extract the same path as the Haskell // and treats the result as a 64-bit integer. // note: this API is slightly old and has some unnecessary stuff in here, // such as the find method taking allocated strings instead of slices. let x: Option<i64> = v.find_path([&"foo".to_string(), &"bar".to_string(), &"baz".to_string()]) // this should eventually become .find_path(["foo", "bar", "baz"]) .and_then(|obj| obj.as_list()) .and_then(|l| l.as_slice().get(3)) // the .as_slice() will go away .and_then(|val| val.as_i64()); There's talk of adding support for `do` sugar which would make this much simpler as well. The typed version is much simpler. We can define the struct with a derived Decodable like so: #[deriving(Decodable)] struct Point { x: f64, y: f64 } And then actually decode some Json like let x: json::DecodeResult<Point> = json::decode(source_str); // DecodeResult contains either the result, or an error.
- mpweiher 12y ago> Regarding JSON, it is annoying to work with in Swift, but when you get down to it, it's a fault of JSON in general and the available parsers in particular. There ought to be ways to describe and validate the desired structure to the parser, or failing that, ways to tell the parser to fetch a value of a particular type and nicely produce an error if it doesn't match. That's what XML had and has. JSON became successful because it did not do this. Discuss. > In the ideal language, anything that's hard to write is so because the problem is hard. In my experience with Smalltalk, it made problems that I thought of as hard easy.
- eridius 12y ago> That's what XML had and has. JSON became successful because it did not do this. Discuss. XML tried to describe the structure in the XML itself. mikeash is referring to describing the structure in the code, and that makes a heck of a lot more sense. The code definitely should know the structure, or at the very least know what type of value it's expecting when reading values out of a JSON blob.
- stock_toaster 12y ago> That's what XML had and has. JSON became successful because it did not do this. Discuss. XML is more self describing. I think what people are talking about is more like protocol buffers or capnproto -- described by an "oracle" (like a shared description file). That makes validation trivial, and you get known types instead of "is this a '1' or a 1 or a true or a 'true' here?"
- mikeash 12y agoI assume you're talking about XML DTDs, which are completely different from what I'm suggesting. DTDs are part of the XML spec and are written in XML, making them extremely unwieldy. My suggestion for JSON validation is that it should be part of the local JSON API and that it should be written in the language you're using to call the JSON API. I'm also going to have to stick a stonking big [[citation needed]] on your claim that JSON became successful because it doesn't do this. It's about as believable as the common headlines of the form "Stock X Rises/Falls on Event" which are nonsensical post-hoc constructions and poster boys for "correlation does not imply causation".
- hellbanner 12y ago>>describe and validate the desired structure Something like this? https://github.com/abiggerhammer/hammer https://github.com/abiggerhammer/hammer
- munificent 12y ago> One new feature is Optional Typing. You can decide whether a variable is ever allowed to be null. Ugh, Swift is really confusing terminology here. Swift has an "option" type, like "Option" in Haskell and F#. It's a wrapper around an object that may or may not be present. You can use that to avoid null-everywhere. This is totally unrelated to "optional typing", which is a style of static type systems that support mixing typed and untyped code. Alas, Swift called their option type "Optional" and then talks about "an optional type".
- ch 12y agoThis is why I appreciate that Haskell calls this type 'Maybe'. I think this better captures the types semantics. I mean, an 'Option' or and 'Optional' makes it seem like you are given a choice... but a 'Maybe' implies more that what you are declaring exists only with some probability! Q. Will this function give a result? A. Maybe. Q. Will this function give a result? A. Option. The choice is obvious.
- krick 12y agoTotally agree. I wondered the same way about why they chose Option over Maybe in Rust. There're so many things in the world that actually can be called Option (I could imagine <select> children or name of some business object) but pretty much none of them are about being null or not. When Maybe is so intuitive that I'm now using this word for the similar cases even in dynamic languages.
- eridius 12y agoThe choice is only obvious because you tailored the question to the Haskell type name. But that's not the question you're asking. The question is "What result will this function give me?". The Haskell answer is "Maybe a". It will give me something that might be an `a`? The Swift answer is "Optional<T>". That makes a bit more sense than "Maybe". The name makes even more sense when you talk about function arguments instead of function result types. Q. What argument does this take? A. Maybe a. It takes an argument that might be an `a`? Q. What argument does this take? A. Optional<T> Still not natural english, but the interpretation here is a bit cleaner: It takes an optional value of type `T`.
- jorgeleo 12y agoTLDR: I just discover that strict type system are there for a reason
- mpweiher 12y agoWith all due respects to Stanley Elkin: No, annoying is not good. Annoying is annoying. Computer languages are there for me to get things done, not to get in my way. Attempts to force good behavior invariably fail. Burn some more midnight oil to figure out how the language/library can deal with it. I once saw someone write that the 1st and foremost goal of a language was to disallow mistakes. That's nonsense. If it were, all we'd have to do is make a non-functional programming language that can't do anything. So I guess sort of the next step in functional programming?
- ch 12y agoIf your NULL exception results in a 5am wake up call to debug... has that computer language now got in your way?
- mpweiher 12y agoDoes that happen to you? Hasn't happened to me.
- the_af 12y agoYes, it has happened to me that type errors (both NPE and ClassCastExceptions) caused problems in production that should have been prevented by a better type system.
- mpweiher 12y agoThe last back-end system I built[1] had 3 failures in a year. First was a JVM misconfiguration (JVM neophytes) so we ran out of memory. Second was a temporary network outage that caused us to get behind our feed without the network bandwidth to catch up. Third was user interface code that wasn't tested. Considering the system it replaced had more like 3 failures a day, the reliability was seen as "pretty good", so much that our relationship with ops was essentially we drop a jar, they tell us nothing happened. TDD for the win. Not sure how/where a better type system would have helped. [1] http://link.springer.com/chapter/10.1007%2F978-1-4614-9299-3_11#page-1 http://link.springer.com/chapter/10.1007%2F978-1-4614-9299-3...
- georgemcbay 12y agoGo also has a set of "annoyances" (some close to the same, some totally different from Swift) and while I was initially annoyed by some of them, over time I've grown to really love Go for it because there is a payoff to the annoyances (at least in Go, I don't have enough experience in Swift to say the same): If the code compiles, it probably works. Not 100%, of course, it is always possible you made some stupid logic error or an off-by-one, but in my experience compared to any other language I've used (including a lot of C, C++ and Java not to mention dynamic languages), Go code that compiles is SO MUCH more likely to be functionally correct, and I believe this is because it is such a nag about code correctness and eliminating any ambiguity as to what the programmer intended.
- riffraff 12y agowhat extra correctness does Go guarantee over Java? I can imagine less concurrency bugs, but ATM I don't recall any particular language feature that should prevent more bugs than any other statically typed language of the last decade.
- georgemcbay 12y agoWith Java vs Go the little bits of forced correctness are more subtle than they are compared to dynamic languages or some of the less "modern" static languages (and less to do with types, though I could attempt to start a raging debate over the merits of interfaces versus traditional OO), but they do still exist. One example off the top of my head: if bracing. Java: if (state) doThis(); doThat(); This type of error is not uncommon if you don't enforce really strict code-style standards or some sort of linting step that isn't part of the language proper. Programmer A writes an if statement with one line because that's all that's needed at the time, programmer B comes in to expand the logic and is either just having a bad day or is more of a Python programmer dabbling in Java and so doesn't automatically see the error. Then programmer B dev-tests the code, but only when state == true, so he still doesn't notice the problem because he was expecting both doThis and doThat to be called anyway. In Go this is an explicit compile-time error, all if statements require opening and closing curly braces. This is exactly the sort of thing, IMO, that might seem like an annoyance to someone who "knows what they are doing and just wants the compiler to stfu" (eg. younger, stupider me) but is, in the long run, actually really helpful for software correctness.