I’ve written here and there about my love of streams. I cover them a bit in the Swift Cookbook. Now that I’m updating it for Swift 3 via the Pearson Content Update Program, I have a little leeway to play around. I’m trying to expand the text with some fun new recipes.
Today, I wrote about “transformative streams”. A traditional stream, one that you write to with print(..., to:)
is just a destination you write to such as stdout, stderr, or a file. A transformative stream fundamentally changes the text flowing through it. You can constrain text to all lowercase, or spell check it, or even print to your platform’s local notifications system.
I decided in this case to build a translation stream. It looks like you see in the following screenshot, converting English text to a foreign language when you print to the stream.
As you see in this screenshot, my solution leverages Swift’s built in standard library print
function. Swift enables you to print to streams. When using my ItalianStream.shared
stream, material printed to the console is automatically translated to the language provided by that stream.
In my implementation, I wanted to be able to hit a few key points:
- Any specific translation functionality needed to be decoupled from the printing stream so I could swap in and out various API providers without disrupting the implementation.
- I wanted to be able to use many languages.
- I had to offer a re-usable
var
member, which is required for stream printing.
To accomplish these goals, I decided to go heavily generic and protocol-oriented. I wanted to be able to slot in any language using a genericized stream type, which in turn would provide a shared stream instance for printing. If it sounds a little pretzel-y, it is.
The built-in standard library TextOutputStream
protocol has precisely one requirement, a write
method that accepts a string and does something with it. In the code that follows, it translates the string and prints it out.
/// A text output stream that performs translation in /// the process of printing public struct TranslationStream<Language: EnglishTranslationProvider>: TextOutputStream { /// Writes the contents of the string to stdout after /// passing the string through an automatic translator public func write(_ string: String) { guard let translation = Language.translate(string) else { return } print(translation) } }
The tricky bit is that my TranslationStream
structure is generic. It requires a type that conforms to EnglishTranslationProvider
, allowing it to request a translation from that provider. Here’s what that protocol looks like:
/// A provider of string translations from English public protocol EnglishTranslationProvider { /// Returns a shared instance of the provider static var shared: TranslationStream<Self> { get } /// Returns a translated string static func translate(_ string: String) -> String? }
Each translation provider refers back to the TranslationStream
type to offer a shared
translation stream to use when printing (you see this in the screenshot as ItalianStream.shared
) and a translate
method that returns a rendered version of the string. For example, here’s the implementation of my French provider type:
/// A stream type that automatically translates from English to French public struct FrenchStream: EnglishTranslationProvider { /// Returns a shared instance of the provider public static var shared = TranslationStream<FrenchStream>() /// Returns a translated string public static func translate(_ string: String) -> String? { return FrenchStream.translate(string, language: "fr") } }
Simple, right? There really isn’t much to it. It implements the two required members but otherwise delegates translation to a default translate
method declared in a protocol extension.
My particular implementation takes a two-character language code and requests a translation from English via Google. I didn’t include the actual translate(_ string:, language:)
method here because there are many competing APIs, which you can easily hook into, and they generally require both API keys and fees.
The important bit I’m trying to get across here is not “oh look, this translates” but rather “oh look, you can transform text by printing to streams”. I thought the translation stream was a rather nice way to demonstrate how powerful this particular Swift feature could be.
In the following screenshot, I print (line 20) to a user notification.
Comments are closed.