Introducing FoundationLite framework as a portable subset of Foundation

TLDR: Since Foundation multi-platform is an afterthought and since we cannot directly impact the way it evolves, maybe it's not the best choice to have it full with utility features that every platform will need. Having another library that is thought multi-platform first and that we can direct the way to evolve through evolution process might be better.


FWIW Foundation supposed to be swift-corelibs-foundation/README.md at main · apple/swift-corelibs-foundation · GitHub and its evolution is public swift-corelibs-foundation/Status.md at main · apple/swift-corelibs-foundation · GitHub.

But actually, Foundation is more a library for working with Apple OS(iOS, macOS) and SDKs rather than a toolbox for Swift because a lot of it’s type are bound with features Darwin. Backporting Foundation to other OSs is a bad idea because it API rely on a lot of Darwin feature since it bound with AppleOSs, its evolution is not modifiable and multi-platform support is an afterthought.

Having a project driven by the community is really a hard and slow this I understand the resistance to open the evolution of Foundation so we cannot influences(mainly slow) it evolution which might impact Apple SDKs and platforms. In order to make Swift move forward, it needs to have more independent and multi-platform enabled library.

Foundation and Swift don’t seem to share the same goals like multi-platform support and evolution openness. Those are crucial for adaptation and Foundation can’t do them maybe it should be shipped with the Apple SDKs instead of Swift.

I don’t think we should do a Foundation Lite but we should add what is missing to the stdlib or make a new cross-platform library that will handle these task.

We have already seen that SPM, NIO, libSyntax needed some features like Process, URL, Data and re-implementing it.

Maybe we can use some blessed library like express here Large Proposal: Non-Standard Libraries even if would prefer a first party solution.

BTW I know Apple is working hard to bring Foundation on Linux and maybe Windows and a few important features have been recently added but the teams working seems rather small.

Some features of Foundation are developed in the open RFP: OrderedSet.

6 Likes

It's not a slow process people fear - it's a solution that isn't tailored to macOS/iOS but just another sucking cross platform toolkit nobody wants to use.

Foundation does not seem like a Swifty solution and is not easy portable.
I rather think that it should be available only on macOS and that we (Swift) build our own cross-platform API, being it inspired by Foundation or other languages.
It would resolve issues that we have currently such as SwiftNIO not linking with Foundation because of dependencies.

What is so unswifty at Foundation? I don't understand why it is so hated by non apple users.

The fact that some of it doesn't work on non-Darwin systems?
I recently spent a whole day replacing a CSV library that worked locally but wouldn't in our docker container on CI (and production). The reason it didn't work was that some as NSData? casts work on macOS/iOS, but they don't on Linux.

1 Like

This type of bridging has been recently added to Swift Foundation.

I think that people stating that Foundation is not a cross-platform solution are simply saying that because:

  1. Swift Foundation is not fully completed.
  2. Foundation on Darwin is using a different Objective-C based implementation.
  3. Swift Foundation is not at feature parity with Darwin Foundation.
  4. Ignore the history and evolution of Foundation.

Foundation was designed to be a cross-platform basic framework (the Objective-C version has been working on NeXTStep, Darwin, Windows (I think iTunes is currently using it) and Linux (GNUStep)).

I do think that:

  1. Completing Swift Foundation should be a priority of the Swift Project.
  2. The Darwin Foundation Swift SDK overlay and Swift Foundation should be somewhat integrated to allow full cross-platform evolution.
  3. Foundation design should move to a Swift Evolution style. The design direction and roadmap should be published and updated by the Foundation Core Team.
  4. Publishing parts of the Objective-C Foundation source code would allow to make Swift Foundation more similar in implementation and performance to it.
  5. In the long-term it would be great to be able to unify Swift Foundation and Darwin Foundation so all Objective-C entry points would call to the Swift implementation that would be the same in all platforms.
14 Likes

I don’t think you understand the evolution process. Direction is given by the core team, who take our reviews in to consideration. Specifically, proposals are not accepted/rejected based on how many +1s they get.

Also, the thing about OOP doesn’t make sense. It’s one specific implementation strategy, which may or may not be the most appropriate one depending on your needs. Class hierarchies are of course supported in Swift, and I haven’t heard of any plans to change that.

1 Like

I trained as an electronic engineer and wrote lots of C programs for microcontrollers (the kind where you have so little RAM they basically don’t have a heap worth mentioning, and very little stack). It puts you in a mindset where you want to be really conservative about what you import.

My hope is that one day, I might be able to write Swift code for those platforms. There has really been a lot of development in recent years, and some of them even have C++ compilers (the resulting binary size is roughly the same as C - again, you just have to be careful about using the heap). Right now that’s a pipe-dream, of course. I don’t think the Swift standard library is written with such systems in mind, but as least from an API level it should be portable.

Having the language depend so heavily on a relatively large module like Foundation makes me feel like that will never be possible. Foundation has a lot of functionality which just doesn’t make sense on an MCU with no operating system, so I wouldn’t port it to those platforms or use it for those projects.

Generally, that’s fine. 99% of Foundation is stuff an MCU wouldn’t care about (URL_Session_s? Processes? Hah). The notable exceptions are ‘Data’ (which, as I said in a separate thread, I think should be in the standard library), and possibly JSON serialisation.

For the rest, I think Foundation is fine how it is. The APIs could be more swifty and hackable, but that’s a topic for another day.

12 Likes

Yes, I feel that Data should be in the Standard Library (as String and Array already are).

And, of course, Foundation requires more work to feel Swifty. Two simple examples:

  • All the path methods in FileManager should allow URL instead of some allowing String and others URL.
  • FileManager.fileExists(atPath:isDirectory:) should be dead by now and replaced by FileManager.directoryExists(at:)

Simple things like this would probably already been solved if Foundation Evolution was a thing.

3 Likes

It's not a "hate", and not about Foundation, but about Foundation in Swift.
Having to support Foundation and XCTest makes Swift dependent from ObjC, which is a pity.
It is and will limit some evolution on it.
Again issues we have with Foundation/SwiftNIO/URLSession seems like a good example of it.

1 Like

Swift had an aggressive evolution process not only thanks to the community, but mainly because it had few users and the cost to break code was low. This is not true for Foundation that has many decade of history.

You can't simply drop old methods from Foundation like we did with previous Swift languages features and API.

1 Like

Apple keeps marking methods as deprecated in Foundation in each new OS release as well as adding new API.

If Foundation Evolution was a thing we wouldn't remove any API from the binary libraries (or event touch the Objective-C version) but could add new API in the Swift overlay / Swift Foundation and mark methods as deprecated.

Also as Swift becomes a language with ABI stability and source compatibility, Swift Evolution cannot be as aggressive as it once was, but that doesn't make Swift Evolution deprecated.

Swift users are not supposed to have experience with Apple platforms and other languages. Not everyone can easily perform the back-and-forth "mental switches" between C-looking apis, Objective-C-looking apis, and Swift-looking apis, that one sometimes experiences when working with Foundation.

This makes me think about the major work that has been performed by Apple on application frameworks (CoreGraphics, UIKit, others), to make them good Swift citizens:

// if (CGRectContainsPoint(rect, point)) { ... }
if rect.contains(point) { ... }

// CGRect rect2 = UIEdgeInsetsInsetRect(rect1, insets)
let rect2 = rect1.inset(by: insets)

I wonder what the same process, applied to Foundation, could do to help users have a better opinion of Foundation. Of course, this would require a great deal of audit, design, and hard work.

1 Like

I did attempt to play my own game.

Let's look at a Swift 4.2 snippet (adapted from a FAQ sample code):

// Copy a read-only resource into a place where it can be modified.
// Perform this copy only once.
let fileManager = FileManager.default
let path = try fileManager
    .url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
    .appendingPathComponent("file.extension")
    .path
if !fileManager.fileExists(atPath: path) {
    let resourcePath = Bundle.main.path(forResource: "file", ofType: "extension")!
    try fileManager.copyItem(atPath: resourcePath, toPath: path)
}
doStuff(path: path)

It does a lot:

  1. Look for a standard directory... (with support for Apple platforms, but not only)
  2. ... in user's domain (I lack the skills to know if domains are cross-platform, or purely useful for macOS server and its NeXT ancestors)
  3. URL manipulation
  4. URL to path conversions
  5. Check for file existence (without error handling)
  6. Load a resource's url from a bundle (I don't know if "bundle" is a portable concept, but I guess "resource" is)
  7. copy files (or directories)

What if I would attempt to "modernize" it?

let fileManager = FileManager.default
let url = try fileManager
    .url(for: .applicationSupportDirectory, inDomain: .user, options: .createIfNeeded)
    .appending("file.extension")
if try !fileManager.fileExists(at: url) {
    let resourceURL = Bundle.main.url(forResource: "file.extension")!
    try fileManager.copy(resourceURL, to: url)
}
doStuff(url: url)
  1. I've kept FileManager. This beast does a lot. I'm not there to throw the baby out with the bathwater.
  2. I've kept the notion of standard urls.
  3. I've kept the notion of domains, because I dare not removing them.
  4. Replaced the create: true boolean argument with options: .createIfNeeded
  5. Simplified appendingPathComponent to appending. But I guess this change is ill-advised, because you are not supposed to append strings with forward slashes, here.
  6. Never converted urls to paths
  7. Made FileManager.fileExists throwing
  8. Made resource url extraction legible
  9. Compacted FileManager.copy(_:at:)

I'm not that sure that it is much better looking, as a matter of fact. :wink:

1 Like

No disagreements that FileManager is a beast that does a lot (more than I even understand), but I do think it should be replaced :grimacing:. Even your updated version doesn't feel very swifty to me. It lacks a lot of the strongly typed features that so much of the language has in other areas.

Paths shouldn't just be generic strings/urls (I'm ok with the underlying storage being a string/url). There should be separate types for the various path types (ie: DirectoryPath, FilePath, etc) because each path type has different sets of functionality. Rather than interacting with paths via FileManager, I'd prefer to be able to do everything on a Path directly. For example, you shouldn't be able to incidentally try getting the subpaths of a FilePath, this is something that should be limited to DirectoryPaths.

Converting your example to what my ideal Swift Path library would be:

let supportDir = DirectoryPath.applicationSupportDirectory(inDomain: .user, options: .createIfNeeded)
let newFile: FilePath = supportDir + "file.extension"
if !newFile.exists {
    let resource: FilePath = Bundle.main.file(forResource: "file.extension")
    try resource.copy(to: newFile)
}
doStuff(path: newFile)

Of course, I'm not very familiar with Bundle nor do I have any idea what the FileManager domains stuff is.

2 Likes

As a server-side developer, I don't actually know what "application support directories" and "bundles" are, so I suspect these are not strictly portable concepts. :) As such, they could be maybe part of a library separate from one that does just basic file I/O.

1 Like

Open question: is it a good idea to let paths access the file system? There is a global state there.

The current API, where all accesses to the file system are done via a FileManager instance, avoids this problem. You can write tests against a virtual file system, for example.

I was thinking the same thing. Much of FileManager is Apple/iOS specific and would probably not be necessary for a potential stdlib Path library. The Apple/iOS specific stuff could be kept in Foundation and just extend the stdlib Path types with support for Bundles and whatever else is needed.

No, that would only lead to apple api being a special case and not the norm. Why should I as a mac coder support such a development? For what gain? That Linux guys are happy?

Looking around at some of the Swift Path libraries out there, they're almost all just wrappers around FileManager, but they seem to only really focus on giving more swifty access to the local file system. (Some do allow you to overwrite the FileManager used though)

You do bring up a good point. There probably should be functionality for testing against virtual file systems.

Perhaps by overwriting the root directory with a virtual one:

Path.root = DirectoryPath("/").virtual