hckrnws
This is pretty cool. In FP there's a pattern called "functional core, imperative shell" [0]; here, you can build your "functional core" in Haskell and your imperative shell in a language which - depending on your preferences, some might say - is a little more suitable for building an imperative shell. Because it uses JSON as the "wire format" between the two languages, you can then easily build out a different imperative shell for whichever platforms you want to support.
[0] https://www.destroyallsoftware.com/screencasts/catalog/funct...
I’ve done something like this before to call Haskell from C++ (in [0]), so that I could build my GUI using Qt. It worked pretty well, except that I ran into various difficult-to-resolve linking problems on both Windows and Linux. After a year or two of trying to maintain it, I gave up and switched to a protocol where both sides pass JSON over stdin/stdout. This particular piece of software doesn’t require a huge amount of communication or shared data, so it works well enough.
The really nice thing about the original interop code, though, is that GHC’s new WASM backend uses essentially the same foreign function interface to export functions to JavaScript. So with only some minor modifications, I was able to get the same program working on a webpage [1], which I think is pretty cool.
[0] https://github.com/bradrn/brassica, and see in particular the old version of https://github.com/bradrn/brassica/blob/93d4353c5736018ec094...
[1] At the risk of DDOS’ing my poor little home server: https://bradrn.com/brassica/
I've been doing similar things with Swift and Racket:
* https://defn.io/2023/03/19/racketfest-talk-2023/
* https://defn.io/2023/02/04/racket-foreign-callouts-to-swift/
I also started out using JSON to exchange data between the languages, but eventually wiring everything manually became an annoyance so I came up with a little Racket DSL[1] that automatically serializes and deserializes between the two languages (at runtime in Racket & using code generation in Swift). So far, I've built two apps with this approach, and I think it's great:
* https://github.com/Bogdanp/Franz (macOS, Windows, Linux)
* https://github.com/Bogdanp/remember (macOS, iOS)
There's really no need to make an unsafe temporary allocation for a simple type like Int. You can just assign it a value, and if you only need it once, just pass it through with &. For example:
var size = buf_size
c_birthday(rawPtr.baseAddress, data_len, res_ptr.baseAddress, &size)
In this case, since you know your size is fixed, and it's actually quite large (1 MB) it is never going to fit on the stack anyways. So there is no need to use a function that will try to do that: just use Array's init(unsafeUninitializedCapacity:initializingWith:) initializer instead, and you will bridge it into Swift's lifetime management without having to do the whole closure dance. Finally, since your API is unlikely to modify the buffer you pass it in, try using withUnsafeBytes instead of withUnsafeMutableBytes (and correctly annotating the pointer with const in your bridge).Thanks saagarjha. Those are very actionable, good, suggestions. I will look into updating the packages which do this under the hood to do that.
The article doesn't really explain the approach taken, to serialize data at the function call boundary.
Haskell supports a C API/ABI directly, as does Swift. E.g., Swift String can be shared as cstring, i.e., a null-terminated byte array, with no allocations needed. Since both languages support the (per-platform) C API/ABI, that would seem to be the best option, with home-grown serialization a risk (or perhaps a fallback where necessary).
In most cases, Swift simply handles the C-type without needing any special bridging. In some cases of ambiguity, you can add attributes on the C declarations to tell the Swift compiler how to handle the data and call.
Haskell also supports a range of C-ABI annotations, but that's on the Haskell language side, likely invisible to Swift.
A super-helpful contribution would be to see what range of types bridge without annotations and what additional types can be handled with annotations.
But since the author did go to all the effort of marshaling, they must have discovered unavoidable limitations. It would be most helpful to know what those were!
Edit: here's a tutorial for swift calling Haskell from 5 years ago, before the huge changes in Swift 5.9 for C/C++ interoperability:
The blog is so neat and pretty!
That’s nice, except for swift’s abysmal json encoding / decoding performance.
Is that an up-to-date critique? Because iOS 17/MacOS Sonoma have the rewritten JSON decoder:
> The code in our GitHub repository 20 has indeed shipped as part of iOS 17 and macOS Sonoma. That includes our new, dramatically faster implementations of JSONDecoder and Calendar.
https://forums.swift.org/t/any-updates-on-the-new-foundation...
That comment was from Nov 2023, and if you look at the GitHub repo, you’ll see that the JSON part of Foundation has commits within even the last month (perhaps getting ready for Swift 6.0?).
Correct. Swift doesn't have any concept of JSON in its standard library; JSON[Encoder|Decoder] are provided by the Foundation framework, which (until Sonoma/iOS 17) used an Objective-C implementation of NSJSONSerialization.
Didn't know this shipped in ios 17. The comment mentions "JSONDecoder", do you know if it's only the decoder, or the encoder too ?
Problem with iOS is this constant need for objc compatibility and transparent bridging, which really messes things up a lot..
Newer libraries tend to be written to use Swift value types rather than classes, avoiding the bridging penalties you mentioned.
Both.
I always flinch when I see Xcode written with a capital C!
The post is interesting otherwise.
Makes sense, if I was Apple I wouldn’t want to emphasize the “code” part of Xcode either.
I’ve used a lot of IDEs in my day, and it was hands down the worst.
If you haven’t used it recently, you really need to update your view.
Xcode is pretty good these days.
My CS professor would be proud
Crafted by Rajat
Source Code