diff --git a/Documentation/Bibliography.md b/Documentation/Bibliography.md index bf9e9a7..fe5a2db 100644 --- a/Documentation/Bibliography.md +++ b/Documentation/Bibliography.md @@ -126,3 +126,10 @@ NB FreeCombine takes the message/acknowledgement protocol approach as primitive * [Lazy Functional StateThreads](https://www.microsoft.com/en-us/research/wp-content/uploads/1994/06/lazy-functional-state-threads.pdf) * [RankN Types in Haskell (i.e What's used in the ST Monad)](http://sleepomeno.github.io/blog/2014/02/12/Explaining-Haskell-RankNTypes-for-all/)) * [Lock Free Data Structures in Java](https://www.baeldung.com/lock-free-programming) +* [Linear Types Make Performance More Predictable](https://www.tweag.io/blog/2017-03-13-linear-types/) + +> Linear types can make fusion predictable and guaranteed. Fusion is crucial to writing programs that are both modular and high-performance. But a common criticism, one that we’ve seen born out in practice, is that it’s often hard to know for sure whether the compiler seized the opportunity to fuse intermediate data structures to reduce allocations, or not. This is still future work, but we’re excited about the possibilities: since fusion leans heavily on inlining, and since linear functions are always safe to inline without duplicating work because they only use their argument once, it should be possible with a few extra tricks to get guaranteed fusion. + +Stream fusion should not be required in FreeCombine bc Swift optimization should be able to take advantage of annotated inlining. FreeCombine needs to make more aggressive use of `@inlinable`. + +* [Retrofitting Linear Types](https://github.com/tweag/linear-types/releases/download/v1.0/hlt.pdf) diff --git a/Documentation/Notes.md b/Documentation/Notes.md index bdabcfe..049cca1 100644 --- a/Documentation/Notes.md +++ b/Documentation/Notes.md @@ -1,10 +1,10 @@ ### Analyze Push-based streams as normal streams _without_ Demand -1. Demand isomorphic to void +1. i.e. Push makes demand isomorphic to void ### Analyze Pull-based streams as normal streams _with_ Demand -1. Demand isomorphic to Bool +1. i.e. Pull makes demand isomorphic to Bool ### Hybrid push/pull diff --git a/Examples/Map.playground/Contents.swift b/Examples/Map.playground/Contents.swift new file mode 100644 index 0000000..f53c8f3 --- /dev/null +++ b/Examples/Map.playground/Contents.swift @@ -0,0 +1,20 @@ +import PlaygroundSupport +PlaygroundPage.current.needsIndefiniteExecution = true +import FreeCombine + +let numbers = [5, 4, 3, 2, 1, 0] +let romanNumeralDict: [Int : String] = [1:"I", 2:"II", 3:"III", 4:"IV", 5:"V"] +let cancellable = await numbers.asyncPublisher + .map { romanNumeralDict[$0] ?? "(unknown)" } + .sink({ result in + switch result { + case .value(let value): + Swift.print("\(value)", terminator: " ") + return .more + case .completion(let completion): + Swift.print("\n\(completion)") + return .done + } + }) + +await cancellable.result diff --git a/Examples/Map.playground/contents.xcplayground b/Examples/Map.playground/contents.xcplayground new file mode 100644 index 0000000..1c968e7 --- /dev/null +++ b/Examples/Map.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Examples/Map.playground/playground.xcworkspace/contents.xcworkspacedata b/Examples/Map.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..ca3329e --- /dev/null +++ b/Examples/Map.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/FreeCombine.xcworkspace/xcshareddata/swiftpm/Package.resolved b/FreeCombine.xcworkspace/xcshareddata/swiftpm/Package.resolved index b3b6338..52e0662 100644 --- a/FreeCombine.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/FreeCombine.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -3,10 +3,10 @@ { "identity" : "swift-atomics", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-atomics.git", + "location" : "https://github.com/CSCIX65G/swift-atomics.git", "state" : { - "revision" : "919eb1d83e02121cdb434c7bfc1f0c66ef17febe", - "version" : "1.0.2" + "branch" : "CSCIX65G/playgrounds", + "revision" : "3024f7a375583fea45220d39ed1700fb835d4154" } } ], diff --git a/README.md b/README.md index 7880084..afefa78 100644 --- a/README.md +++ b/README.md @@ -191,7 +191,9 @@ All streaming libraries are written in the [Continuation Passing Style (CPS)](ht Promise/Future systems are also written in CPS and as a result share many of the same operations. FreeCombine incorporates NIO-style Promises and Futures almost by default as a result of FreeCombine's direct implemenation of CPS. In FreeCombine's implementations of Publisher and Future, it is easy to read the relationship between the two directly from the type signatures. Futures can be thought of as "one-shot" streams, i.e. a stream which will only ever send exactly one element downstream, no more, no less. In this paradigm, Promises can be seen to be the exact one-shot representation of Subject from the "normal" streaming world. If you find the concept of a "one-shot" stream odd, it is worth noting that the Swift Standard Library already has an exactly analogous notion in the type [CollectionOfOne](https://developer.apple.com/documentation/swift/collectionofone). ## What makes FreeCombine "Free" -So what makes FreeCombine different from AsyncSequence (and its support in Apple's swift-async-algorithms package)? And what do we mean by _free_ anyway. FreeCombine is "free" in the sense that it is: +So what makes FreeCombine different from AsyncSequence (and its support in Apple's swift-async-algorithms package)? And what do you mean by _free_ anyway? + +FreeCombine is "free" in the sense that it is: * Protocol-free. * No use of protocols, only concrete types @@ -240,11 +242,14 @@ For a long time I've been exploring the idea of what Apple's Swift Combine frame Ideally, this material would become the core of an expanded course on Functional Concurrent Programming using Swift, but that course is still fairly far off. -Secondarily, this repo is my own feeble attempt to answer the following questions: +Secondarily, this repo is our own feeble attempt to answer the following questions: 1. Why does the use of protocols in things like Combine and AsyncSequence seem to produce much more complicated APIs than if the same APIs had been implemented with concrete types instead? 1. Why does Swift Concurrency seem to avoid the use of functional constructs like `map`, `flatMap`, and `zip` when dealing with generic types like Tasks, but to embrance them fully when dealing with generic types like `AsyncStream`? (not to mention more run-of-the-mill types like `Optional`, `Result`, and `Sequence`) -1. Which elements of Swift Concurrency should be regarded as `primitive` and which are `compositional`. +1. Why does AsyncSequence in Swift Concurrency have so many methods in common with Combine, yet the required parts of their protocols seem so different? +1. Why is it that EventLoopFuture from Swift NIO shares so many methods with Publisher from Combine and AsyncSequence from the Swift standar library, but that Future in Combine looks so different from EventLoopFuture? +1. Why does Swift Concurrency seem to avoid the notion of a Future and its accompanying methods altogether? +1. Which elements of Swift Concurrency should be regarded as `primitive` and which are `compound`, (i.e. formed by composing the primitive elements)? And what does composition of these elements mean, anyway? 1. If, in Swift, we decorate "effectful" functions with keywords like `throws` and `async`, does that mean we can expect other kinds of effects to introduce additional keywords on function declaration? 1. Is there a general way of dealing with effects in Swift and what might such a mechanism look like? 1. Why does Swift's Structured Concurrency not have a set of primitives similar to (say) Haskell or Java? In particular, why does it seem so difficult to use Structured Concurrency to write constructs like Haskell's [ST monad](https://hackage.haskell.org/package/base-4.3.1.0/docs/Control-Monad-ST.html), [MVar](https://hackage.haskell.org/package/base-4.16.2.0/docs/Control-Concurrent-MVar.html), or [TVar](https://hackage.haskell.org/package/stm-2.5.0.2/docs/Control-Concurrent-STM-TVar.html) or to implement the [Producer/Consumer pattern](https://www.baeldung.com/java-producer-consumer-problem) seen ubiquitously in Java concurrency?