Hacker News new | past | comments | ask | show | jobs | submit login
Interoperability Between Swift and C++ (github.com/apple)
123 points by DeusExMachina on Oct 17, 2020 | hide | past | favorite | 75 comments



All this effort for a highly specialized solution that only fixes one combination in a huge M:N matrix. Wouldn't it be better to start working on an "LLVM for language interoperability"? That way every language only needs to provide a connection to an intermediate specification, and it can connect to any language supporting that same specification.

Currently this is done through C APIs (and it works well enough TBH), but plain C APIs lack annotations for higher-level concepts (like lifetime, bounds checking, etc). Why not work on a modern language interoperability standard instead? Being able to mix many different smaller and specialized languages in a single project instead of trying to create the "one universal language to rule them all" also would speed up language evolution, because less effort is wasted copying features from one "universal language" into other "universal languages", and as a bonus, operating system APIs following that standard would become language-agnostic.


Related: There recently was a Twitter thread that you might be interested in: It's about the need for a new ABI which covers the middle ground of Rust, Swift, Zig, Nim, and other mid/low level languages, with lifetime annotations baked in so that the language can read them and manage memory without the need of developer's manual help.[0]

[0]: https://twitter.com/bitshiftmask/status/1286411475658178561


> Wouldn't it be better to start working on an "LLVM for language interoperability"? That way every language only needs to provide a connection to an intermediate specification, and it can connect to any language supporting that same specification.

My somewhat limited understanding is that the WASM interface types proposal is hoping to serve that sort of role. Note that in this case, the "web" part of "web assembly" is kind of a red herring; as I understand it, this proposal doesn't really have anything to do with the web and is more of an attempt to utilize WASM to create a sort of "universal ABI" that can replace the current de facto choice of using the C ABI everywhere.


it comes with some overhead though, which is useful in many circumstances as it also gives some sandboxing, but if your goal is universal interop then you probably want to have a solution that doesn’t incur a speed penalty.


We could call it something like "Common Language Infrastructure" or COM maybe.


And then remember that Objective-C is, essentially, COM with language support.


Unfortunately COM was never OS independent so that kind of killed it off. It was also runtime instead of compile-time.


Why the past tense? COM is alive and well, being the way the large majority of Windows APIs get introduced since Vista.

While COM is Windows specific, other implementations with similar ideas do exist.

IBM and Unisys mainframe language environment, gRPC, D-Bus, XPCOM, SOM, Android IPC, Fuschia IPC.


Indeed, COM has some right ideas. There's just too much influence from the mid-90's OOP hype in it.


What? COM is the most successful implementation of ECS, I thought it was fashionable nowadays.


If you want to make a cross language protocol that most languages can support, OOP concepts will need to be first class citizens.


Should I post the xkcd? Here goes: https://xkcd.com/927/


That's a grand idea, but there's good reasons to go to this effort for just C and C++.

There's a whole lot of code written in both; their toolchains are highly portable; for a long time they were seen as the only languages that gave programmers the ability to write high-performing code; and they are both unsafe by default.

Providing seamless bindings with a safe-by-default language like Swift is a way to move the whole systems programming field forward to a safe language, while maintaining compatibility with the enormous existing code base in the world.


Sure, but Swift is just one of many modern and (less modern) high-level languages, it's not just about connecting Swift to other languages than C or C++. There's also D, Rust, Nim, Zig, Python, Java, C#, even Javascript (when calling WASM modules), etc... those would all benefit from a more modern (and standardized!) approach to language bindings (even if the target languages would only be C or C++, but why stop there).

Apart from that "grand idea" I think a more realistic real-world solution for the C++ => Swift situation is to simply wrap the C++ API into a C API first, and then create a Swift API on top of the C API, instead of trying to somehow translate the C++ API directly to Swift. E.g. every C++ library should come with an optional C-API to simplify language bindings, or even just compiling that library into a DLL.


If we focus just on the programming cases where C++ has been heavily used, you can narrow that list down to D, Rust, Nim, and Zig. Dynamic languages or those with a heavy runtime like Python, Java, C#, and JavaScript are substantially different use cases.

Among those remaining, Swift has substantially more corporate backing, what with Apple relying on it for their massively successful iOS platform and Google trying to use it for TensorFlow.

C wrapper for C++ APIs has long been standard practice, but adds a substantial maintenance burden to C++ library authors and does not address semantics in the host language, which is a major part of this proposal. The whole proposal is based around the idea of seamless Swift to C++ interoperability. C++ has a lot of higher-level concepts that potentially can translate to similar higher-level concepts in other languages; round-tripping through a C API loses all of that.

To be clear, I like your idea - it's a grand one, and potentially solves the N+M problem once and for all. The real stumbling block for it is the C++ ABI and sheer mass of existing C++ code.


> E.g. every C++ library should come with an optional C-API to simplify language bindings, or even just compiling that library into a DLL.

hard to do without incurring additional dynamic allocations though. e.g. how would you make, say, an API that wraps construction / doing stuff / destruction of something that'd look like this :

    namespace mylib { 
    struct node { 
      std::vector<node> children;
      std::optional<float> value;
    }; 
    }
in C you'd likely have to have something that looks like

    mylib_node* mylib_node_alloc(void);
    void mylib_node_free(mylib_node*);

    // and then... 
    float* mylib_node_value(mylib_node*); // this ? 
    float mylib_node_value(mylib_node*, int* ok); // or this ? 

    void mylib_node_set_value(mylib_node*, float value, int set); // then there's this ?
    void mylib_node_set_value(mylib_node*, float* value); // or this ?
    void mylib_node_unset_value(mylib_node*); // or maybe this ? 
and all this cannot really be done without a dynamic allocation (which hopefully looks terrible to everyone), unless you fancy doing ugly ugly ugly stuff like

    struct mylib_node { char storage[sizeof c++ struct that was computed manually)]; }; 
so anything that'd allow other languages to understand at least C++ vocabulary would be a great boon in terms of perf, etc


You can do something like

    // Or replace with something like a Strong Type; https://www.fluentcpp.com/2017/05/05/news-strong-types-are-free/
    using NodeHandle = int;

    struct OptFloat { float Value; bool bInitialized = false; }; 

    NodeHandle CreateNode(NodeHandle* Children, int NumChildren, OptFloat Value);
    void DestroyNode(NodeHandle ToDestroy);

    void SetValue(NodeHandle ToSet,  OptFloat  NewValue);

Aside from that, you do raise a good point. If you implemented your C api over your implementation of node, you would be in for a bad time as the owning std::vector<node> won't keep valid your pointers valid.. This isn't suposed to be ragging on your code, it's really to point out the pitfalls of manual APIs.

> struct mylib_node { char storage[sizeof c++ struct that was computed manually)]; };

You can do;

    // IN C api, fill this in manually
    #define mylib_storagsize = ?

    struct mylib_node {char storage[MYLIB_STORAGESIZE]; };
    // or;
    struct mylib_node {void* storage; }
    // in node.cpp - the underlying C++ abstraction;
    static_assert(sizeof(mylib::node) == MYLIB_STORAGESIZE);

It's only _slightly_ better..


I think LLVM is too low level for accomplishing the goal of generating a Swift-like API for calling foreign code.

Also, C, Objective-C and C++ cover a large part of the need (certainly when looking at Apple’s code base for their OS)

I also think any other interoperability standard for native code that works well for a broad range of languages is a pipe dream. There simply are way too many language idioms. Even for managed runtimes, you get lots of friction, for example between languages with mutable state and languages without.


> I think LLVM is too low level for accomplishing...

What I mean with "LLVM for language interoperability" is not using LLVM itself (although it could be part of the wider LLVM project), but the idea of LLVM. LLVM decoupled language frontends and code-generation backends by defining a common intermediate layer. Suddenly it was possible for individuals to create new languages that automatically run on all CPUs and ABIs supported by LLVM. What's missing for those small new languages is the same effect for connecting those languages to all the other languages that already exist. Doing this for each other language is impossible for small teams. Doing this for a single common interface specification on the other hand is fairly trivial.

Such an "hourglass shape" would also make sense for language interoperability. But instead of having a common intermediate bytecode representation, have a common intermediate API-definition + ABI specification (somewhat similar to existing IDLs, but without a separate IDL-language that must be manually maintained, but instead automatically derived from the "native"-language interfaces.

Also, it doesn't need nor should support all sorts of special language idioms, just a basic common set of "interface features". It would be very close to what we already have with the C API/ABI interoperability, just flavoured with additional annotations for some common higher-level concepts that languages have agreed on since C was invented (e.g. different types of references: unrestricted pointer, non-nullable pointer, ranged pointer, ownership annotations, etc...).

Once you have such a common set of annotations, languages can create more idiomatic (not perfectly idiomatic) API bindings automatically. It doesn't have to be perfect, just an evolution of the current state (basically "rich C-APIs" instead of "plain C-APIs"). And plain old C would still be part of the language mix that's supported, it would just ignore most of the high-level annotations.


I think that path gets you to the JVM or something similar.

Let’s say you use library A, get a string out of it, and want to pass that to a function in library B. That will only work if both libraries have the same idea about what a string is. The same argument applies to arrays, hash tables, etc.

Similarly, the wish to be able to have a serialization library that works across languages will add restrictions.

Also, do you support bare functions? If so, you’ll have to tweak things for Java. Do you support function overloading, and if so, how? Do you have multimethods? If not, you’re putting languages that do at a disadvantage. If you do, languages that don’t will be problematic.

The only examples I can think of where that works reasonably well are C/C++/Objective-C (with the C ABI as the intermediate), Java/scala (but note that scala was designed to work on the JVM, in the same way C++ and Objective-C was designed to work ‘on’ C), and C#/F# (again, F# was designed for .NET)

Swift is designed to work with Objective-C and, because of it, with C and, to a lesser extent, C++. This tries to make that a larger intent.


> That way every language only needs to provide a connection to an intermediate specification, and it can connect to any language supporting that same specification.

The intermediate specification would be another language, so it would have the same impedance mismatch as the other N languages do.

You have memory management issues, OOP inheritance issues, semantic differences. Just trying to do a interoperability layer for the different versions of character strings would be incredibly hard.

Microsoft did something similar to this with CLI, but most people will note that JScript.Net and VB.Net are really new languages that just are optimized for porting Javascript and Visual Basic code over. They also defined a proprietary extension to C++ that allowed it to deal with managed objects so that it could interoperate with CLI code - but this did not aim to expose unmodified C++ code. Rather, it allowed you to use proprietary extensions to manually build bridges between the two.


> The intermediate specification would be another language, so it would have the same impedance mismatch as the other N languages do.

Yes, but even though this is a limitation, every language only needs to care about connecting to this one intermediate "language". This is also why going through a C API already works quite well.

Also it's interesting to note that Microsoft's latest version of the WinRT APIs are providing a standard C++17 wrapper on top of COM APIs and don't require C++ language extensions anymore.


To the anger of Windows devs being told to wait for ISO C++23 or even ISO C++26, hoping that the standard gets the relevant papers for C++/WinRT VS tooling to catch up with C++/CX from 2012.

Right now it is hardly better than ATL/WRL, but so is life when Windows team wins its political battles with DevTools.


That already exists, and it’s called the „platforms C ABI“.

Every language that supports C FFI can talk with any other language that also supports it.

All these other efforts are just really higher level wrappers around that, but given that there are a million tools for generating the glue code to bind APIs using C for most languages in use it is hard for me to see the value here.


No, you're just wrong. What parent means is something like COM or GObject. The C ABI is too limited and still requires you to build things on top of it.

It's kinda like saying all you need is bytes. From the perspective of the kernel this might be fine because it isn't supposed to prescribe a particular method but user processes must still agree on a character encoding or a protocol they are going to run on top of those bytes.


For the platforms written in C.


> Wouldn't it be better to start working on an "LLVM for language interoperability"?

> trying to create the "one universal language to rule them all"

I feel this rebuttal of your proposal should be made, but I'm not sure how to articulate it more convincingly than you did.


To be fair though, for people developing for iOS it's a disproportionately huge point in the "huge M:N matrix".

My experience is that grand solutions can be attractive in theory but awful in reality.


While the assumption that you can make changes to swift’s stl is not that far-fetched, doing so to cpp’s is completely mental.

I’ve got a feeling that swift is becoming a very polluted mashup of features that come from parties with conflicting interests. AFAIK, internally at Apple, teams pull in different directions (e.g. to get SwiftUI), at the same time there’s this half-baked differentiable programming / swift4tf manifesto, then there’s the backend swift initiative with vapour.

This reminds of a hotch-potch that Scala’s framework and feature landscape is. The overhead of getting into it is quite substantial and for some, definitely not worth the investment if not just downright scary.


This. The part about polluted mashup.

I've developed and maintain 3 or so swift apps in the last 4 years.

I also assist a colleague who ports and maintains them in Kotlin.

At first I liked Swift. As a dynamic OO languages guy, I didn't like C++, and felt that Swift was a fresh approach to the non dynamic OO language. Over time, my enthusiasm has waned.

For a while, I drank the value /struct programming koolaid. Too much. It turns out, that references are really really handy when your modeling real world things. Time and time again I've pushed myself to go the protocol route with structs, only to pull it out and just use simple single inheritance with real class based objects.

The real trap I keep falling into is this. The Swift memory management story for objects is a fricking joke. First you have to constantly stress about ownership relationships, so that you don't get burned by cycles. And then you get to discover the various behind the scenes implicit refcointing rules around closures or passing methods directly in lieu of closures. And having to put [weak self] everywhere. So you react by using structs more. Because you believe it will be more efficient, etc. But structs come with their own problems. You put ids in your structs, because you need to assert identity of a given thing. Abstraction is more difficult. You get to dance with generics more.

I've played with SwiftUI, I get the basic dream. It demos well. I could use it for simple CRUD style apps (which is what all the tutorials seem to be for). I'm just not sure how well it will scale to more complicated user interactions.

As much as I don't like Android development and loathe Java, I'm forced to often just wish I could use Kotlin in iOS. It has its warts. And often there are too many ways to do one thing so that they can streamline a tiny amount of boilerplate away at the expense of consistency. But I fight it less.


> I’ve got a feeling that swift is becoming a very polluted mashup of features that come from parties with conflicting interests.

There some truth to that I guess, but I'd also add that a lot of these modern multiparadigm languages feel like they're converging feature-wise (with a few outliers). I feel like Swift still has a unique enough feel to it and I enjoy working with it a lot. I think Chris Lattner has also expressed some concern about the focus on features over everything else.


Yeah also having worked more with Rust, it seems that a lot of the features added to Swift recently could be covered by decent meta-programming support.

That said, automatic differentiation could be a game-changer and should absolutely be included in the core language.


AD in the core language? Why? Automatic differentiation would be prime candidate to be implemented as a metaprogramming library.


The choice may have been made because Swift doesn’t support meta programming well enough to do it well purely in a library. The plan they have/had (https://github.com/apple/swift/blob/main/docs/Differentiable...) claims “First-class language support for differentiation will enable convenient, extensible, and performant differentiable programming in Swift - more so than library-based approaches”.


I think OP is claiming that if Swift had better meta programming abilities that GP have suggested, differentiable programming would have been a prime example for a good use of the capability.


If the language defines a way for you to ask the program for the computation graph in order to differentiate -- couldn't you add meta programming functions on top of that computation graph?

Seems to me like there's at least an argument that it should be possible grow diff programming into a general purpose meta programming system rather than use general purpose meta programming as implementation mechanism for differentiable programming ...


Name some swift features that add too much complexity / conflict with existing features. Then your reasoning could become more than a mere heuristic


I think for a lot of us, the addition of view builders and function builders that ignored the process of adding features to the language just so the SwiftUI team could use less punctutation was a jumping the shark moment for Swift. On top of that SwiftUI has been a disaster compared to Apple's existing UI toolkits because it's less featured than UIKit, less reliable and unlike UIKit, there's no new hardware forcing its adoption.

None of this is new per se; weird Apple specific edges like @UIApplicationMain and @NSManaged have always polluted the language. But, it's hard to buy in on "Swift world domination" when server-side Swift is stillborn, Rust is more appealing as a better bare metal language, Catalyst and SwiftUI are weaker than AppKit and UIKit, the last time Swift got a useful language feature was guard let in Swift 2.


So far I've not found SwiftUI usable for my work at my (large well known) employer. I like some of the concepts, but it's insufficient to actually use for our apps. Instead my team built a component system on top of UIKit that gives us the benefits of easier to build UI, without the "holy" and incomplete design of SwiftUI. Having Swift has allowed this work to be easy to construct and extremely type safe, and gives us the benefits of being able to do more work with fewer people, which was sort of the goal of SwiftUI, but in a more compatible way.


I totally agree with your point about SwiftUI: that also was a moment where I started looking to other languages in terms of what I should invest my energy in for the next few years due to the problems in governance with the language.

On the point of "swift world domination", I still think this is largely a PR problem. I think Swift's identity as "compiled python" could make it a very good fit for many use cases where scripting languages are currently being used, due to it's clean, high level syntax and powerful type system. It still suffers a chicken-and-egg problem with respect to broader tooling and library support since it's viewed overwhelmingly as a language for making iOS apps.


"Swift world domination" is from Chris Lattner, Swift's author. He proposed that Swift could simultaneously be better at text manipulation than Perl, data science than Python, high-performance compute than C, more expressive than Ruby, etc... The result is a language ideal for no one and pushed heavily by Apple.

Swift as a "compiled python" loses to Go, and that not even Go's design goal (though it is a fairly common use of it).


> The result is a language ideal for no one and pushed heavily by Apple.

Does it have to be "ideal" to be worthwhile? I think it fits a similar use-case to something like C# but it's a lot nicer to work with. I don't understand why Swift shouldn't be perfectly usable for data science, or writing server-less functions for example: it would be a perfectly suitable stand-in for Python or Go in these cases, but with a much nicer type system in my opinion thanks to the first-class optional handling.

> Swift as a "compiled python" loses to Go, and that not even Go's design goal (though it is a fairly common use of it).

I totally disagree. Go is a jail. It's optimized for big teams with a lot of turnover, where you want to prevent complexity in the code-base by choosing a language with very limited facilities for abstraction.

Swift on the other hand has a really flexible syntax, which allows you to hide a lot of complexity behind simple interfaces. In that way, it can be a lot more like Python, where the code you're writing is very much at the domain level, and you don't have to think about the implementation details very often. There are many things you can express in swift which are no expressible in go.


YMMV but I found SwiftUI pretty good for a framework that only has two years.

It runs circles around storyboards when it comes to collaboration and traceability. Disregarding bugginess, the previews are a lot better than what was possible with IBDesignables.

The integration with UIKit is actually quite well made, and for most part not needed.

> there's no new hardware forcing its adoption.

While there is no explicit new hardware, there are several popular features that are only doable using SwiftUI: home screen widgets and complications. It will also become the language in which to write universal iOS/macOS programs.

> when server-side Swift is stillborn, Rust is more appealing as a better bare metal language

I agree with this, at least from Apple's part most effort is clearly done on "application level" and usefulness of Swift outside there seems more exploratory at the moment. Personally I'd like them to start adding higher, rather than lower level functionality, and steer towards scripting.


> I agree with this, at least from Apple's part most effort is clearly done on "application level" and usefulness of Swift outside there seems more exploratory at the moment. Personally I'd like them to start adding higher, rather than lower level functionality, and steer towards scripting.

This here makes sense. I can see Swift becoming more focused on the application-level side at the moment and in the future becoming a way of writing cross-platform GUI apps in Swift, more likely if it is with Qt5 with direct C++ interop with a SwiftUI-like DSL.

As a Rust user, the server-side use-case for Rust is far more developed unlike Swift and would much rather see Rust pushing for low-level development as well.

However, I've seen many attempts for a cross-platform GUI library for Rust for a long time and it has been proven to be difficult in terms of adoption, implementation and maintenance. It would take an enormous amount of effort to cover all three of these difficulties from scratch and using bindgen bindings to wrap and maintain it and I'm afraid for them it would be more trouble than it is worth. But who knows?

I just don't see a mature cross-platform GUI library coming from the Rust ecosystem anytime soon.


While I don't think adding C++ interoperability is one of the'mashup of features', one example of a feature that adds complexity and conflicts is the function builders (result builders)[0].

It doesn't interact well with other language features (e.x. if let wasn't supported for a long time), it isn't really extensible (transforms are specified by the language), and yet it adds a bunch of complexity in both the language, implementation and reading code.

It seems clear, at least to me, that this is something that happens if you try to make a feature 'generic' without a lot of time and design consideration, due to Apple's internal politics on SwiftUI. I like SwiftUI, and some features that support it are great (like custom attributes), but I firmly think that they should have taked the time to produce a better design.

[0]: https://github.com/apple/swift-evolution/blob/main/proposals...


Swift is a crescendo of special cases stopping just short of the general; the result is complexity in the semantics, complexity in the behaviour (i.e. bugs), and complexity in use (i.e. workarounds).

https://www.quora.com/Which-features-overcomplicate-Swift-Wh...

That was 2015, when Swift was far simpler than today.

Heck, they added almost all of Smalltalk syntax recently to address one special case of a special case.

https://blog.metaobject.com/2020/06/the-curious-case-of-swif...

And the method syntax that this is a special case of a special case of is many pages of spec.

And of course there's initialization, which was 14 pages of the Swift spec at the time, with crazy overlaps and gotchas. Unsurprisingly and predictably (as in: I predicted it), it wasn't enough, or rather far too specific, so for SwiftUI they had to create an entirely new way of creating/initializing: function builders.

https://blog.metaobject.com/2020/04/swift-initialization-swi...


I find it funny when people complain about Smalltalk syntax in Swift, since in my experience keyword arguments and trailing closures are two of the features which make Swift clear and nice to work with.


It would have been nice and simple if Swift had used Smalltalk syntax from the start. It's quite another thing to reject it at first, and then also bolt it on later alongside the non-Smalltalk syntax.


It’s wise for a language to be parsimonious with syntactic constructs at first, and then gradually introduce concepts which seem like they should obviously be there.

Multiple trailing closures fit a common use case and seem like a natural extension of the language.


> wise for a language to be parsimonious with syntactic constructs at first

Absolutely.

But Swift wasn't parsimonious at first. Not even close. It unwisely rejected the parsimonious case and chose tremendous complexity.

Keywords? Or parens? Both! And we'll throw in a bunch of other stuff as well. And the weird case of having two keywords, one with a colon and one without, because we decided to make the keywords the names of the parameters. Sometimes.

And then afterwards they noticed that the parsimonious choice that they had initially rejected in favor of their much more complex choice was needed in addition to all that initial complexity.


Have you worked with Swift at all? Because the features you're complaining about aren't really difficult to understand in practice, and they're a major part of what makes Swift really clear and nice to work with.

> Keywords? Or parens? Both!

Why should they be mutually exclusive?

Swift's named parameters are great! For instance they allow for default parameters, which is something I really miss in Rust for instance where I have to use the builder pattern everywhere.

> And the weird case of having two keywords, one with a colon and one without, because we decided to make the keywords the names of the parameters. Sometimes.

Swift's rules for parameter naming are very simple, consistent, and easy to understand. Given the signature:

    func foo(first second: Type)
`first` is the name used outside of the function, or the keyword for this argument, and `second` is the name used inside the function, or the parameter name. If the two names are the same, the second name can simply be elided. So for instance this signature:

    func foo(x: Int)
Is just syntactic sugar for this:

    func foo(x x: Int)
It's super simple and something which every swift developer can pick up within minutes of starting their first tutorial. I don't really understand why you seem to getting so agitated over this non-issue.


> Have you worked with Swift at all?

Yes, regretfully.

>> Keywords? Or parens? Both!

> Why should they be mutually exclusive?

Because of parsimony. The comment I was replying to made the claim that the original Swift design war parsimonious. It wasn't.

> Swift's named parameters are great!

Named parameters are great. Swift's implementation of named parameters is deficient, and not parsimonious. Yes, even that design is better than not having named parameters, but once you have named parameters, you don't need the parens.


> once you have named parameters, you don't need the parens.

Can you share an example of what this would look like in practice? I'm having trouble thinking of how this syntax would work.


You can look at Smalltalk, which is a great example where names parameters without parens works great. But that doesn't mean that named parameters with parens are bad, yet that's what GP is suggesting. (For context, he's the designer/implementor of Obj-Smalltalk that runs on the Obj-C runtime.)


Thanks, here's an example I found:

    var a, b, c;

    ...
    a = b.fooBar(1, 2);
    c = a.fooBarBaz(1, 2, a);
vs

    |a b c|
    ...
    a := b foo:1 bar:2.
    c := a foo:1 bar:2 baz:a.
    ...
From: https://live.exept.de/doc/online/english/programming/stForJa...

The Smalltalk syntax is hard for me to parse, but it's probably just a familiarity thing. I remember when I first learned Swift I wasn't fond of not ending lines with semicolons. Now I dislike having to type them in languages where they're required.


Familiarity and, since it is based on words, not parens, the actual names matter. You're going to have a hard time parsing with foo bar baz, just like you are going to have a hard time parsing a sentence with made up words.

   1 to: 20 do:[ :i | Transcript show:i. ]  
or in Obj-S

   1 to: 20 do:{ stdout println:$0. }
The keyword syntax makes a LOT of other syntax unnecessary, and allows you to create APIs that read like DSLs.

https://youtu.be/Ao9W93OxQ7U?t=628


How do you chain function calls in smalltalk syntax? I think delineating the argument set for a single function is the main job parens fulfill.


You've nailed one of the few issues with keyword message syntax in Smalltalk-80.

While binary and unary messages chain w/o problems, it's not possible to disentangle two keyword messages:

     receiver msg1:arg1 msg2:arg1
is indistinguishable from

     receiver msg1:arg1 msg1:arg2
So ST-80 chooses the latter. If you mean the former, you have to parenthesize.

But not always, it turns out. If you actually chain messages to the same receiver, you can use the semicolon to chain those messages:

   myRect x:20;
          y:10;
          width:100;
          height:100.
This looks like the single message x:y:width:height: but is actually four separate messages, all sent to myRect. It makes additional compound messages, particularly setters, less necessary.

In Objective-S[1], you can use the pipe ("|") to chain message-sends that go to the result of the previous message, rather than its receiver. For example:

   self dictionariesForQuery: "select {column} from {table}" | collect | at:column.
Since Objective-S also supports dataflow, the similarity to Unix pipes is intentional, and it pretty much works semantically as well: the result of the previous expression gets piped into the next expression. Very intention-revealing. In fact, I've found it to be so useful in communicating the structure of expressions that I use it even when it's not strictly necessary, for example the second instance in the example above.

> delineating the argument set for a single function is the main job parens fulfill.

Right. Which is one of the many reasons why the case of Swift is so mind-boggling: there some of the parameters go inside the parens, some outside. I think you can make a good case for either all in or all out (better for all-out, IMHO, but whatever). But partly in and partly out?

[1] http://objective.st/


> It unwisely rejected the parsimonious case and chose tremendous complexity.

Swift's keyword syntax isn't complex at all.

> Keywords? Or parens? Both!

I know you like Smalltalk, but keywords and parens are used together in a lot of languages (e.g. Python) so it's a tested idea.

> And the weird case of having two keywords, one with a colon and one without, because we decided to make the keywords the names of the parameters. Sometimes.

You don't have two keywords, you have an optional variable name. It's intuitive at the first time you see it - you're phrasing makes it seem much more complex than it really is.

I think you're trying to imply that Swift isn't an elegant language with it's syntax fitting in a postcard, but that doesn't mean it's 'tremendous complexity'. This whole comment is a bit too harsh to Swift, calling the legitimate differences between Swift and Smalltalk as 'complexity'.


>> It unwisely rejected the parsimonious case and chose tremendous complexity.

> Swift's keyword syntax isn't complex at all.

Yes it is, particularly compared to the parsimonious options. Either parens or Smalltalk-style keywords can do the job, so you absolutely, positively do not need both.

Particularly, if you are going to have keywords, you just don't need parens. So it's already more complex than it needs to be (not parsimonious). Just explaining the various omitted or extra keywords takes 4 pages in the Swift book, the entire Smalltalk syntax fits on a Postcard.

What is complex or not obviously depends on what your baseline is. If it's C++, Swift might seem simple (though many disagree).

> (e.g. Python) so it's a tested idea.

The question was not whether it was tested, but whether it was a parsimonious design, which it is not. It was also not a good design, because it led to a number of follow-on problems that they had to incrementally fix while adding even more complexity.

Python is not a good comparison because they didn't really have a choice: they already had the paren syntax, which they could hardly deprecate.

However, choosing that design without a backwards compatibility restriction is unconscionable, IMHO, particularly if you already have a different keyword design all over your code-base. That's cutting off your nose to spite your face.

Anyway, even the baseline keywords-in-parens is complex and more complex than it needs to be (because it isn't parsimonious), but then you get follow-on problems: blocks inside parens are really ugly, so you add trailing closure syntax. Yet another special case, which is very different from arguments inside the parens. But then you discover that sometimes you need more than one trailing closure, and then you need yet another special case, the open-keyword-trailing-block syntax, which is different yet again.

Of course, instead of having 3-4 different syntactic mechanisms, you could have chosen just the one and not needed any of the others.

> You don't have two keywords, you have an optional variable name. It's intuitive at the first time you see it

No, it's absolutely positively not the least bit intuitive. And it makes no sense, because internal variable names have no business leaking to the interface. If you want to rename the names of your variables inside your method, it changes the signature! Not even C does this.

> that doesn't mean it's 'tremendous complexity'

Although the argument could be made, I wouldn't, not by itself, no. However, this is just one example of countless others, see for example Rob Rix, whom I quoted before:

Swift is a crescendo of special cases stopping just short of the general; the result is complexity in the semantics, complexity in the behaviour (i.e. bugs), and complexity in use (i.e. workarounds).

I also talk more about it here:

https://blog.metaobject.com/2015/05/i-am-jealous-of-swift.ht...

> is a bit too harsh to Swift,

Nope. The comment I replied to made the indefensible claim that Swift was parsimonious. Which it's not. Not even close.


It just shows an ability to learn. There is nothing wrong with that.


Sure, morally speaking, I completely agree. It might mean the language ends up more awkward and complicated than it had to, though.


I am not familiar with the history of keywords in Swift. But I like both using keywords for function arguments, and to be able to leave them out. What other design would you propose?


I don't think that adopting a syntax that looks similar to Smalltalk means it has 'adopted Smalltalk syntax'. I like Smalltalk as a language, I do think that Swift would have been better if it's runtime model was more similar to Objective-C and Smalltalk, but it's not fair to say Swift 'added Smalltalk syntax to address a special case'.

I do agree in general that Swift can benefit from a reduction of special cases, but my personal feeling is that Swift is getting more consistent every version (at least in the features that already existed). YMMV, of course.


> I don't think that adopting a syntax that looks similar to Smalltalk means it has 'adopted Smalltalk syntax'.

How so? That is literally what they did. The trailing closure syntax is Smalltalk keyword syntax, and so Smalltalk keyword syntax is used for that one special case. Which exists in addition to the non-keyword trailing closure syntax and the non-trailing (regular argument) closure syntax.

> but it's not fair to say Swift 'added Smalltalk syntax to address a special case'.

Can you explain how it is "unfair" when that is exactly what happened? Smalltalk syntax is really compact, there just isn't that much there.


Well, the syntax is same - the ideas are different. Trailing closure syntax is basically 'you can remove the parens if there are trailing closures'. Which, if you apply that to multiple closures, you get the syntax that also looks like Smalltalk. Which is understandable, since Swift keyword calling syntax was always 'keyword followed with a colon' since Swift 1, which was inspired by Objective-C, which was inspired by Smalltalk. But then that's not adding Smalltalk syntax in a later stage, it was baked into the language since day 1. That multiple trailing closure syntax - that syntax can come out from anyone knowing Swift without any Smalltalk knowledge, it's a logical next step. So it feels a bit weird to call it 'adopting Smalltalk syntax' since I don't think Smalltalk influenced that syntax any more than other parts of the language.


> Well, the syntax is same

Exactly. Which is why I wrote "they adopted the syntax", not "they adopted the ideas". It would have been really wonderful had they adopted the ideas, and it would also have made their marketing slogan "Objective-C without the C" actually truthful.

> the ideas are different

Yes, and that's the problem.

> Trailing closure syntax is basically 'you can remove the parens if there are trailing closures'

Not really, no. The parens are still all there, you just move one (original trailing closure syntax) or more (new keyword trailing closure syntax) of the parameters outside the parens.

Which is horribly inconsistent just by itself. I mean, the point of the parens is that they delimit the arguments. If you have parens, then the arguments go inside the parens. Except for this one...did I say one, I meant these two special cases where the arguments do not go inside the parens.

If you are going to have some of the arguments be outside the parens, why not just go all in and have all of them "outside" by just getting rid of the parens and be (a) consistent and (b) dramatically simpler and (c) readable?

> which was inspired by Objective-C, which was inspired by Smalltalk.

> I don't think Smalltalk influenced that syntax any more ...

Which is it?

Anyway, the point of the article wasn't that they consciously decided to adopt Smalltalk syntax. The point is that they bungled this so badly that they ended up adopting the very Smalltalk syntax that they initially rejected, and the reason they ended up adopting it was that they couldn't make things work reasonably any other way. Except that had they done this from the beginning, they would have not run into any of these problems.


> I’ve got a feeling that swift is becoming a very polluted mashup of features that come from parties with conflicting interests. AFAIK, internally at Apple, teams pull in different directions (e.g. to get SwiftUI), at the same time there’s this half-baked differentiable programming / swift4tf manifesto, then there’s the backend swift initiative with vapour.

Cathedral and the Bazar..

You are basically complaining about how messy the bazar model is, and yet the bazar gave us Linux and in the end it won the race against all the others working over the Cathedral model and with millions of dollars invested in them.

Its fine to work at the bazar model, and it seems messy at first, but as long theres a 'cathedralization' phase from time to time it will work as a living entity, with constant organic evolution.


When interoperating between languages, there are several things to consider:

- API

- ABI, including threading, exception, function call, and memory models at the ABI level

- build specification

- packaging

This article mostly focuses on API, there seem to be some nods to the other three, but those seem like the harder and more pernicious problems to me.

For instance, how would the build system know that a library is being included that requires the swift runtime? Or even the C++ runtime. If any library was built against a different set of standard library interfaces (of any language), do any tools fail with a useful diagnostic?

That's not to say this isn't helpful research, but there's a lot of work to do before all of this is robust enough to be straightforward for end users.


This is only being talked about now, because ABI compability is already working.

The Swift compiler is in C++ targeting the LLVM and therefore with great interoperability with C and C++ through Clang.

The Swift compiler also ships with Clang, and you can link C and C++ projects that are dependencies of Swift packages using the Swift package manager with little effort.


If only having tools from the same toolchain guaranteed ABI compatibility worked! Something as apparently benign as using absl::string_view with different '-std=c++NN' flags in different otherwise unrelated C++ libraries is an ABI break.


I was recently surprised to find that it's not even possible to search for C++ on Apple's developer docs. When you try to, it replaces C++ with C.

https://developer.apple.com/search/?q=c%2B%2B


This is a bug in their app, because it incorrectly canonicalizes %2B to + then + to space and then removes leading/trailing spaces.


I thought it would be worth mentioning this relatively new, little known, open source project, called gluecodium: https://github.com/heremaps/gluecodium.


Does swift work 1:1 on windows or linux yet?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: