Graphing Calculator was one of the delightfully “impossible” things I discovered in the hard disk of our brand new iMac back in 1998. I had just started using a TI-83+ at school, so seeing a 3D full-color animatable graphing calculator with a beautiful equation editor slotted directly into my brain as “this shouldn’t be possible! yet here it is!!” I have never found a real use for it, but always wish I could. I bought the iOS version when I found out about it to pay back the authors for my teen-aged delight.
If you need to include graphs or equations in a presentation or document, Graphing Calculator provides vectorized versions of both which scale and print beautifully.
It’s been a while, but I wrote a few different “slow start” functions for a load balancer and used Graphing Calculator to not only illustrate the behavior, but also show which parameters were tunable.
Applications / Utilities / Grapher is a different application (formerly Curvus Pro X from Arizona Software) which replaced the original Mac OS 7 Apple menu Graphing Calculator because I was very slow porting to Mac OS X.
> Since the ‘80s, I’ve intended to eventually open source my code. As I considered doing that with the C++ code base, I realized that would not be a useful contribution due to the decades of accumulated technical debt making the C++ code unmaintainable. I am confident now that the new code can be made into useful stand-alone Swift Packages for mathematical typesetting, editing, numeric and symbolic computation, and graphing.
I hope he decides to also open source the C++ version. It would be interesting to be able to see both versions, and to compare them.
Perhaps one of the greatest benefits of open sourcing both versions, that I can see, is that others could then look at the two versions of Ron’s code for inspiration on how to go about porting their own legacy applications from C++ to Swift.
Aside from that, it also provides historical context that future programmers and future programming language creators could learn from. Both in terms of good things and any of the lesser good sides.
> When SwiftUI works it is a nigh-magical delight, but when it behaves unexpectedly or when behavior outside the prescribed path is desired, it can be difficult to understand and work around its limitations.
The author is being kind. Once outside the prescribed path, you’re in deep trouble. And that’s the thing with declarative code. There must be a great escape hatch. The framework can’t foresee all possible use cases.
Apple has signaled that SwiftUI is where they are going and will continue iterating on it for many years to come. They are like old-school Microsoft in that way. Version one is certain to be incomplete, but they will stick with it over the long haul.
Apple certainly has done a much better job of picking a development strategy and iterating on it than Microsoft has over the past couple of decades.
They will continue to use UIKit / AppKit for their own apps. They will just make it so that doing so will require a private entitlement. 3rd party apps will be restricted to SwiftUI.
Apple is very much in the camp of maybe tolerating third parties, but only so long as they only ever do stuff in the bounds of what Apple wants them to do
Yeah, I looked up how it worked and decided this is too much magic, I am sticking with good old Cocoa. Also I don’t really like auto layout. It is springs and struts for me.
Well... there are other metrics besides code size in some sections, such as the total code size for all sections; the speed of compilation/linking; and ultimately, the user experience --- is it as fast or faster than C++ ?
When people make 'size' comparisons, I sometimes wonder if they'd best be done using lzip or something on the two code bases -- because the compressors are trying to remove redundancy in coding, and if one uses lots of whitespace in C++ to enhance readability, this should not punish C++ in the size department.
You generally want some 'system' ('language' seems too restrictive) to enable one to capture the algorithms rapidly and accurately in some notation (again, 'language' seems not quite the right word), and to as quickly as possible convert said notation into working binary (compiler, interpreter, JIT, etc all fair game) that satisfies the user need.
I claim (wrongly?) that these days, we generally only need a decent 'glue' notation that allows us to mash together existing libraries/classes/etc to produce the final product. Some of this glue would be sort-of templates, lispish macros, some would just be e.g. an FFT routine. But only by burying complexity in well-tested, lower-level chunks will we be able to build big systems that don't collapse.
Sorry, enough soapbox. I guess I'm suggesting that it's interesting to compare Swift to C++. It sort of begs the question: what is 'the best' way to get some functionality (e.g. this cool calc app) written?
C++ requires a lot more code because it is so clunky and inflexible. Swift is just a lot better language which is why you can get away with much shorter code for the same functionality.
I spent 15 years doing C++ and read so many books but always felt the language would get in the way. When switching to the Swift stuff just worked the way it should. No need to read tons of books to find clever tricks to get around inherent limitations of C++.
C++ was good for it’s time but it is strange that people think one cannot radically improve on what C++ is with newer languages. It is tied down with backwards compatibility with a 50 year old language.
That had lots of advantages, but downsides has caught up with advantages.
It's not hard for a rewrite to be shorter than the original code. (It's also not hard for the opposite thing to happen, even if you don't actually increase functionality, but that tends to be frowned upon).
Sorry, but boilerplate in C++ ? Did he ever try using templates ? C++ is one language where you can get rid of boilerplate with a truckload of strategies.
I suspect [Ancient C/C++] weighed down with tech debt and utilising a plethora of old-code frameworks to Swift is what let him save 30%. If he did the port to Modern C++ and modern libs, he would have likely saved 50%.
I'm surprised by this take. C++ requires you to duplicate signatures in header files, which that alone requires a good degree of boilerplate. Modern C++ offers some tools to be more terse such as keywords like auto, but often times the "proper" way of doing things is the more verbose way. This is a bit of a strawman, but
[[nodiscard]] constexpr int Add(const int& a, const int& b) const noexcept
{
return a + b;
}
That example has nothing to do with modules. It doesn't even use templates (where concepts would help at least) or the "noexcept(noexcept(...))" thing.
Modern C++ does help with most type specification, looping over containers, writing constructors and making value classes. It's definitely not all bad, but there's still a lot of boilerplate to C++.
Modules will definitely help but even modules unfortunately aren't perfect. You aren't allowed to have circular dependencies with modules, you need to forward declare. It's less boilerplate but still unfortunate imo.
Modern C++ is in some ways more boilerplate and more messy. I am so done with C++. Swift solves such a large list of C++ problems. Modern C++ solves very few of the problems I have experienced with C++ over the years.
I get that people stick to C++ because it is too costly to port. But it doesn’t make a lot of sense to stick to C++ if you got the option to use Swift, Rust or Go.
Swift avoids null pointers, undefined static initializations and de-initialization order, virtual call disasters when calling a virtual from a constructor. Protocol oriented programming solves a lot of problems you tend to run into in C++ when doing object-oriented programming.
Curious what boilerplate is common in C++ that isn’t needed in Swift. I find the repetitive boilerplate level of C++ pretty minimal, especially compared to languages like Java.
Template specification used to need a lot of decoration but with auto most of that is gone.
I think there are different ways to look at boilerplate, some of which languages like C++ and Rust are great at handling through their standard libraries, but there is also code boilerplate that needs language support handle well. defer and C-style for loops being the most well known constructs where the language provides syntax for something that otherwise would be difficult to express with just functions and lifetimes. Swift goes a step beyond by providing syntax for many other common patterns (aka boilerplate).
For example, a few areas where Swift reduces boilerplate and the syntax it provides to do so:
- a complete null/optional story: first class optional types (T? = Option<T>), null chaining (obj?.prop), and null coalescing (T ?? "default")
- error handling: fn() throws Err (= fn() -> Result<T,Err>), try, try?, try!, do/catch (out-of-band error handling), w/o stack unwinding, see nulls
- and struct/enum lifecycle management: constructors, getter/setter props, defaults, named functions, named tuples, etc. (this list is loong)
Individually each feature is small and could possibly be argued as bloat or overhead since you can accomplish the same with just regular functions and Rust-like code and abstractions, but combined they make the language feel more expressive (and more fun IMO) than would otherwise be possible and often without sacrificing performance. They also serve to reduce cognitive overhead in the same way using for loops is nicer than desugaring to while loops. This is nothing to say of the syntax itself, and a bunch of other small features; Swift really is a case where the whole is greater than the sum of of its parts.
That is still thin by the modern standard. More accurately speaking, C++ standard library has too many useless things and lacks too many useful things. Compare with D [1], Rust [2] or Zig [3].
It looks like the only big feature any of these standard libraries contain that C++ doesn't are things like networking and json/xml parsers. I would argue that those should not be part of a standard library, but you can still easily get those through additional C/C++ libraries.
I honestly don't know what else you would be looking for in a standard library. C++ provides out of the box:
- hash maps
- sets
- dynamic arrays
- fixed length arrays
- bit sets
- option types
- atomics
- mutexes
- futures
- strings
- queues/stacks/Deques
- tuples
- variants
- streams
- etc
In addition, they have a huge range of algorithms that operate on all of these containers. I'm really curious, what does the standard library not have that you think forces you to write boilerplate?
To be useful, option types and tuples need language support. C++ doesn't have it.
Similarly, to be useful, futures need runtime support i.e. asynchronous I/O. They also need language support like async-await, to generate these state machines. C++ has none of them.
The standard library still does not really support Unicode, people need third-party libraries like ICU just to manipulate strings: https://stackoverflow.com/q/42946335/126995
I believe Rust has the most conservative standard library among those modern languages and it still has many things that the C++ standard library simply don't have (yet):
- Unified result types. C++23 is finally going to have `std::expected`, but it will probably take another version or two to see the `std::expected`-only version of standard library.
- Iterator utilities. Again, C++23 will see a significant improvement in <ranges> but that still falls short of what Rust `Iterator` provides by default.
- Checked arithmetic. Rust and many other languages distinguish checked and unchecked arithmetic and put one of them (typically unchecked one) into the standard library. C++ doesn't have anything like that.
- Guarded mutex (that is, a portion of memory by design only accessible when locked) and concurrent queue. C++ does support concurrency out of the box, but its tools are pretty bare bones and this is one example.
Thanks for providing some specific examples! I still don't see how any of these would result in a 30% decrease in boilerplate. My main point was I feel like header files are the biggest piece of boilerplate and it seems weird to accuse the lack of features in the standard library as a reason for all the boilerplate.
Just a couple other points though, C++ provides fully customizable iterators. They don't have ranges, but ime the amount of times I need ranges vs just iterating over a container are relatively low. Also, is a lock guard in C++ not the same thing as a guarded mutex? This may be ignorance on my part because I'm not familiar with rust, but it sounds like the same thing to me. A lock guard will lock and automatically release a mutex once it goes out of the current scope.
You have a good point that featureful standard library doesn't necessarily result in less boilerplate, but I assumed that you kinda implied it by enumerating features. Frankly almost every feature you and I listed is not exactly relevant here---most of them can be easily refactored into external libraries ;-)
What I find most lacking from the C++ standard library is actually coherence. Different parts of standard library were developed separately from each other, and while adapters between them exists (e.g. stream iterators can be used with `std::format_to`) they are by definition boilerplates. Some parts are simply not relevant in the modern C++ programming and still have to be retained for compatibillity (e.g. `std::auto_ptr`). In this view C standard library is actually a lot more coherent than C++, even though C standard library itself is less powerful and featureful than C++.
> They don't have ranges, but ime the amount of times I need ranges vs just iterating over a container are relatively low.
I think this is simply because C++ iterators are less usable. All iterators can be technically rewritten as loops, so you may have internalized to avoid using iterators when iterators would be beneficial but difficult to use. I personally find myself using iterators a lot more in Rust than in C++.
> Also, is a lock guard in C++ not the same thing as a guarded mutex? This may be ignorance on my part because I'm not familiar with rust, but it sounds like the same thing to me. A lock guard will lock and automatically release a mutex once it goes out of the current scope.
I actually don't know the exact term (the name "guarded mutex/lock" was what I've used in the past), but I meant a mutex that is strongly coupled with some additional data so that the API prevents or at least discourages to access the data without acquiring the mutex. In a hypothetical design `std::mutexed<T>` will be `T` plus `std::mutex` and `std::lock_guard<std::mutexed<T>>` would have `operator *` which allows the access to `T`. In Rust [1] this is actually the default, and you never have a bare `std::mutex` (though it can be simulated with placeholder data, `Mutex<()>`).
> You have a good point that featureful standard library doesn't necessarily result in less boilerplate, but I assumed that you kinda implied it by enumerating features.
Gotcha, I appreciate the back and forth which helped me to clarify that point a bit better :)
> What I find most lacking from the C++ standard library is actually coherence.
I 100% agree. There are plenty of areas that C++ is very lacking, and I think this comment highlights that.
I initially started this thread off because a big complaint I hear among C++ users is the language is a bloated mess, which is why the parent comment saying that C++ had a thin standard library didn't ring true to me. But I think what you're saying here is closer to the truth. It has a lot of good features, but they're mostly after thoughts and poorly integrated. Whereas a language like Rust (or another modern language) has around the same amount of features builtin, but they're more focused, integrated, and planned :)
I think it makes sense to bundle with a standard module things which are quite OS independent and very commonly used like Python, Go, Swift, Julia and many other languages do: collections, common algorithms, File reading and writing, socket communication, async, concurrency, JSON and possibly simple XML. String manipulation, regular expressions.
What should clearly be outside is stuff like graphics library, GUI, HTML.
> collections, common algorithms, File reading and writing, socket communication, async, concurrency, JSON and possibly simple XML. String manipulation, regular expressions
C++ does provide all of this in the standard library except for sockets, Json and XML. I think once you get to things like Json and XML it doesn't make much sense to include that in the standard library. There are plenty of applications that don't need or use either of those, and it's a shame to bundle some configuration language when somebody may have another preference like yaml or something. There's nothing wrong with providing additional standard add-ons that include these, but I definitely don't think it makes a lot of sense to bundle it in with the standard.
This is a whole separate conversation, but my main point was there are a lot of better things to pick on C++ for. Most complaints I hear are that it includes too many useless features haha.
That makes 2 of us. When I've read "So much of the repetitive boilerplate code that was necessary for C++ melted away in Swift" my eyes rolled over. For me Swift repetitive boilerplate is on par with the one in C++. Clearly the author never touched Ada nor Python, if that's the number one reason he wanted another language to eliminate the repetitive boilerplate.
I suspect he just wanted to learn Swift and that's a good reason enough to make this rewrite, as he admits in the final paragraphs of the article: "I’ve enjoyed learning Swift and am much happier with the state of the code now"
I don't write C++ much anymore, but to take an example where we want to union two sets (I'm sure the C++ one could be better? I've adapted from multiple examples)
Note the value type deduction when declaring the vectors, the use of back_inserter to avoid having to resize the result (c) vector, the use of std::ranges to do away with all the begin + end crapola, and the way ranges and iterators are inter-mixed.
C++ is always going to be a bit more verbose in trivial cases, but it's not that bad, and exposes a lot more flexibility in complex ones.
I'm wondering what you needed to write unsafe code for? Could there be another way to get rid of reference counting for your use case?
Another question, have you tried using Accelerate framework to solve performance bottlenecks (or save yourself from having to write your own calc code)?
Most of the performance critical sections are numeric computation loops. I use the Unsafe APIs where profiling shows that the bounds-checking overhead is significant.
In principle, I could get rid of reference counting overhead by using value types or immutable data. I couldn't see a simple path to doing that without re-architecting everything (with no guarantee that the end result would not just have different performance issues.) For the moment, I'm awaiting compiler improvements before re-evaluating the tradeoffs. There is certainly room for the compiler to reason better on eliding retain/release. https://github.com/apple/swift/issues/58549
Yes, the code does use Accelerate where applicable. That is one component of the numeric evaluation. It addresses the lowest level of things like evaluate the sin function on every array element, or multiply there arrays element wise. Performance tuning is a game of whack-a-mole. There's always another bottleneck somewhere.
The math is internally represented as a tree for display and editing. Most of the performance critical code is the numeric evaluation when graphing. For that, the math is compiled to a linear byte code which vastly improves locality of reference and is an opportunity to apply optimizations such as common subexpression elimination and loop invariant code motion.
I hadn't followed the link in article originally to get to https://mobile.twitter.com/RonAvitzur/status/146102321572409.... So literally the process of parsing the expression and producing the byte code is the performance challenge now? Or is it also walking the bytecode to do anything?
My basic question would be: why not go back to flex/bison/yacc/whatever via C-FFI? (But I think it would still be bad, since you'll want to get to a Swift data structure for your ops and those will still have the Arc issues)
That thread describes me working through performance issues in the initial port eight months ago. Those cases perform adequately now. Parsing is not a bottleneck. Walking the bytecode remains a performance hotspot, as that is where all the numeric calculations occur, but no more or less so comparing the C++ and Swift implementations.
I did investigate maintaining the flex/bison parser, since its generated state machine C code is more robust than my handwritten recursive descent parser when presented with pathological input. However, as you say, since I need a Swift data structure in the end, there is little to be gained and a lot of complication bridging via a C-FFI.
Yes, the numeric evaluation is vectorized via the Accelerate Framework's vForce and vDSP APIs. That is a significant performance improvement. The numeric evaluation remains a hotspot with vectorization, as that is where the app does most of its work.
I wonder how a rewrite of the core algorithms in modern C++ and a Swift wrapper + UI would have compared. This also would have allowed easy porting of the core to other platforms.
Very difficult to do that in practice, because there’s currently no way to bridge C++ directly to Swift. You could bridge it via C, but any reasonable implementation would have to severely limit the amount of direct Swift interaction with the core code, or you’d be spending infinite time maintaining the bridge.
Was it in C++ originally not C? (It seems like a C FFI bridge would have been straightforward from). Or did it already go from C => Objective-C++ a while back?
(I read the article hours ago and didn't notice you'd posted. Btw, I think we would have gotten better conversation if you had said "Hey, this is me! AMA" as a top-level comment)
It has been in continuous development since 1985. Before the port, parts were in C, C++, Objective-C, and Objective-C++, as well as Lex, Yacc, GLSL, and Python. Will do the top-level comment.
> I love Swift’s syntax. So much of the repetitive boilerplate code that was necessary for C++ melted away in Swift, leaving only code necessary to represent the logic, making the meaning clearer.
To be honest, swift feels more like a bad parody on Scala.
The origin story is cool as heck, too. As discussed many many times on HN: http://pacifict.com/Story/