Saturday, June 28, 2014

What's already built in?

How to find the standard library

When I started this, I knew that some of the functions I wanted would already exist, but I couldn't figure out how to find them.

I've since found it: In Xcode, type the word "false", right-click it, and Jump to Definition. The global variable false is defined in the same file as the whole rest of the standard library—all the functions, structs, and classes (and typealiases, globals, etc.) that exist. And there are even some useful comments in there.

It still won't tell you where the file actually is (if you try to use any of the navigation stuff, you end up navigating from the file you typed "false" into, not the stdlib file), but you can always Duplicate it to save a copy for future reference (e.g., if you want to have it available in emacs, not just Xcode).

More on Generator and friends

As I suspected: Generator is explicitly stated to be single-pass-and-consumed, and should also be a Sequence. A Sequence may or may not be reusable. A Collection is a Sequence that's guaranteed to be reusable, and also subscriptable. Probably not a coincidence that these are exactly the relationships between Iterator, Iterable, and Sequence in Python. (I'll get to Collection later; there's a lot more there.)

However, despite that comment, it's not enforced anywhere, or documented, that a Generator must be a Sequence; it's perfectly possible to define Generators that aren't Sequences, and in fact the stdlib defines quite a few of them, including the Generators for Dictionary and String. 

On top of that, while a Generator shouldn't start over if you call generate on it, and (contrary to what I originally thought) none of the stdlib Generator-Sequences do so, there seems to be a quirk (or, hopefully, bug) of the for statement that has the same effect. For example, if you define an Array a = [1, 2, 3], and a Generator g = a.generate(), then call g.next() to consume the first value, calling g = g.generate() will never get the first value back (good). However, a for loop—which is supposed to be equivalent to calling generate and then looping over next—does not consume some kinds of Generators. So for i in g {} will not advance g at all. I have no idea why that happens with, e.g., IndexingGenerator, but not my own types, although my suspicion is that it has to do with built-in (that is, compiler-supported) Generators vs. those written in plain Swift. Anyway, as far as I can tell, it shouldn't be happening with any of them.

Hopefully both of those problems will be fixed in the final 1.0 version. If not, that means you really can't use Generators in for loops; any itertools-style programs will have to use while-let loops all over the place. (And, if the first problem isn't fixed, they may even have to do an as? cast to Sequence followed before calling generate, because you can't rely on the generate method being present.)

It's also not documented anywhere that a Generator signals that it's done by returning nil, or that it should return nil forever after doing so the first time, etc., so that's still to some extent just a guess…

Meanwhile, there are a bunch of helper functions/types for creating Generators, which I wish I'd known about earlier.

IndexingGenerator: In Python, for historical reasons, anything indexable by successive integers starting from 0 is automatically iterable even if it doesn't provide __iter__, Swift of course didn't copy this, but it did add a type, IndexingGenerator, that makes it trivial to emulate. If you've got a Collection that you want to make iterable, just add a generate method that returns an IndexingGenerator(self), and you're done.

GeneratorOf: Given a next function (presumably a closure), this gives you a Generator that just calls that function over and over. Sort of like the two-argument form of iter in Python, except without a sentinel. You can also give this a Generator, in which case it wraps that Generator in some way that seems to tee some Generators (that is, it starts with the same state as the original Generator, but they can both be advanced independently from there) while delegating directly to others (so advancing either advances the other). From what I can tell, the same Generators that support for wrong also tee here, and vice versa. (But what about a generator created by GeneratorOf from your own next closure? I need to test that…)

GeneratorSequence: Given a Generator, this gives you another Generator, which tees or delegates to the original in exactly the same circumstances in which GeneratorOf would. Although this stores the Generator itself rather than its bound next method, as far as I can tell the effect is always identical anyway. I think the intention here may be to wrap a Generator that may not be a Sequence (which shouldn't be possible, but is) in one that definitely is a Sequence, but (a) that shouldn't be necessary if the stdlib were fixed, and (b) it still isn't necessary because GeneratorOf can always handle it.

SequenceOf: Given a Generator or a Sequence, this gives you a Sequence (that's not a Generator) that wraps it in the same way as the above two functions. Not sure why this is needed, or anything but a bad idea. If you use this on a single-pass generator, you're just fooling yourself into believing you have a non-Generator Sequence; when you then call generate on it multiple times, all of the resulting Generators will share state.

GeneratorOfOne: Given a single optional value, gives you a Generator that produces just that value then quits. Sort of like [value].generate(), but without needing to create an Array first. If you give it nil, the Generator is empty instead; not sure why that's useful, or why it doesn't just take a T instead of a T? in the first place. Also not sure why the argument is called elements rather than singular element.

CollectionOfOne: Given a single (non-optional) value, gives you a Collection that has only one element (and is indexed by the Bit type, not Integer… see below on that). I suspect the reason for this type is that the compiler can optimize away all the subscripting stuff when it sees this type better than when given a more general collection. (From a theoretical point of view, anywhere you could use a trivial monad instead of lowering a monadic function, you could implement that with CollectionOfOne in Swift; practically, you're not going to have monadic functions in the first place in Swift, and if you did, you'd write a trivial monad…)

EmptyGenerator: Returns a Generator of no elements. (Not as useless as it sounds—with no elements, you need somewhere to specify the type—it obviously can't be inferred from the elements or the function that produces the elements or anything else, right?—and EmptyGenerator<T>  is about as concise a way to say that as possible.)

EmptyCollection: Returns a Collection of no elements (which is indexed by Int, with start and end indices both 0).

Anyway, GeneratorOf seems like it might be able to replace some of the boilerplate structs I've been creating. But not all. In fact, in some cases, I probably need more structs (as explained later, after I get through Collection).

Warning: GeneratorOf<T> is a single type

When I first discovered and started playing with this stuff, I realized that GeneratorOf<T> being a concrete type meant that I could now use my chain(Sequence...) function on anything created by any of my functions using GeneratorOf, instead of only on identical functions. And this seemed great.

But then I realized that this was relying on an implementation detail of those functions. And, worse, an implementation detail that isn't shared by the standard collection types, or map and filter, etc. So, it's actually pretty misleading if chain(islice(foo, 1), repeat(x)) happens to compile. Chain needs to take any number of Sequences of any Sequence type (as long as they all share the same Element type—or, even better, convertible ones, but I don't think that's possible). If that can't be done, it's useless and shouldn't exist.

Collection and friends

As I mentioned earlier, Collection is much like Python's Sequence—it guarantees that the Collection can be iterated over as many times as you want without consuming it, and it also specifies that the Collection can be indexed with the subscript operator. There's also a MutableCollection, just like Python's MutableSequence, that inherits from Collection and adds mutating indexing. And there's an ExtensibleCollection, which doesn't inherit from Collection or define anything, but I think it's intended to be used for types with reserve and capacity.

But beyond that, Collection also has a lot in common with C++ Collections, as I'll explain in a second.

Subscripting uses a special method, subscript. Just like init, there's no func on it (and, for MutableCollection, no mutating either). But unlike init, there is a normal return type. And unlike any other method, that return type can be (but doesn't have to be) followed by a property description—{ get } or { get set }. This is how Swift does the equivalent of Python's separate __getitem__ and __setitem__ methods.

It's worth looking at C++ here. In C++, reference types (T& is a reference to an lvalue of type T), are used all over the place, and subscripting is one of those places. There's generally an operator[] const that returns a const T&, and an operator[] that returns a T&, so x[2] = 3 turns into x.operator[](2) = 3, where the left-hand side is a reference to the location x[2]. This is a tricky problem for collections that don't actually have addressable locations, like std::bitvector; they traditionally work around this by defining a Reference struct with a custom operator= that mutates the parent collection appropriately (which, before C++11, was a nice instance of the standard library violating its own standard requirements). On top of that, reference types add a whole lot of complexity to the language (especially once you start to consider what a reference to a reference means—and how generic functions can easily make bad assumptions about that—which is ultimately one of the main reasons C++11 was necessary). While they add a lot of power, in almost every case, there are easier ways to do the same thing—out and inout parameters, some way to do the equivalent of Python's __setitem__ and __setattr__, and maybe a few other special cases, and you've eliminated most of the need for references. So, that's what Swift is doing here with its special subscript method.

In both C++ and Python, while indexing can take any type you want, you pretty much always use integers (anything convertible to size_t in C++, anything with an __index__ method that returns an int in Python, or a size_t in the C API for Python extension methods). In Swift, subscript can take any type, and this is actually meant to be used. And this is how Swift handles the equivalent of C++ iterators.

In C++, iterators are an abstraction over C pointers. You get a pair of iterators from a collection with its begin and end methods, and then all of your subsequent code (including all the generic algorithms in the standard library, Boost, etc.) doesn't care where they came from. You just do *it to access the value, ++it to move to the next value, it != last to check if you've reached the end. This can be cool, especially since it means you can define iterators that do things like append onto the back of a vector, or pull characters off an input stream. But it also means that many iterators need a hidden reference back to their collection—and those that don't need that (e.g., because the collection is just a hunk of contiguous array storage or a graph of linked nodes) can easily invalidate iterators that other code is still using, leading to crashes or corruption.

In Swift, indexes are… well, indexes, things you pass to the subscript operator. So, instead of *it, you do coll[it], but otherwise, things are basically the same. They're just as closely bound to their collection class as C++ iterators, but don't try to hide that fact. This means you don't get the power of std::vector::back_inserter or std::istream_iterator, but Swift gets that power in another way (by using what Python calls iterators instead of what C++ calls iterators), and it also means Swift avoids the complexity and the safety problems of C++ iterators.

So, what do you need indexes for in the first place?

Swift has a tower of Index protocols, mirroring C++'s tower of iterator concepts, meaning it can accept things less powerful than randomly-accessible indexes. This means that (unlike in Python) a doubly-linked list can be a Collection, it's just one that can only be bidirectionally indexed rather than randomly.

It also means that you can write a generic function that uses one implementation if given something that can't be randomly indexed, but another (e.g., faster) one if given something that can be. And then you can compose those into higher-level functions, and eventually you end up with big chunks of code that work just fine for any collection type and make it easy to read off the performance for each one based on what kind of index it uses. (At least that's the theory; that's turned out to be a lot less important in C++ than Stepanov and Lee originally expected when they designed the STL, and since Swift doesn't have most of the STL algorithms, and the few it does have seem to be either methods or sequence-based instead of index-based, it's likely to be even less important in Swift…)

Briefly, a ForwardIndex defines only succ, ==, and !=; a BidirectionalIndex adds pred; a RandomAccessIndex (which all of the numeric types define) adds distanceTo, advanceBy, and sometimes comparisons and arithmetic. Actually, the protocols don't require any methods at all, but all of the concrete implementations define these methods. Also, while neither the protocols nor most of the concrete classes define an associated type DistanceType anywhere, various other functions seem to depend on it existing, so presumably you have to define it if you're writing your own index types. (That all seems a little off, but there are a couple other cases like that; I think the protocol definitions may just not be complete in the beta, and there's magic code to not check stdlib/builtin types.) As in C++, if you want to advance a ForwardIndex n times, you can do so by explicitly calling advance(idx, n), and likewise for distance(idx1, idx2); having separate free functions for those signals that you're going to get O(N) time rather than O(1).

(As a side note, I think in some cases the names are aliases for symbolic operator functions; they seem to have changed how things work multiple times and not finished cleaning up yet. Possibly defining succ gets you ++, uncheckedAdd gets you &+ and, if not defined elsewhere, +, etc. But it's not that straightforward. For example, Bit.one + Bit.one gives you "fatal error: Can't unwrap Optional.None". But there's no + method or function returning Bit?. What there is, is an uncheckedAdd that returns a (T, Bool) pair, where the T wraps around and the Bool is false if you overflow, and the &+ operator seems to use that to just return the first, while + seems to try to turn that into T? and then immediately unwrap it for some reason. Maybe there are multiple levels of translation from old to newer to newest API? Who knows. Maybe these functions aren't even being called, and never were, and someone just felt like defining a bunch of verbosely-named functions for no good reason. Anyway, the point is, don't rely on the fact that these names will be succ and pred and so on in the final release; they could be operators like ++ and -- instead.)

Beyond using things that are less powerful that randomly-accessible integer indexes, this flexibility also allows using something more powerful, like a Double (which could, say, interpolate between known values).

Plus, it allows something clever for Dictionary that Python can't do, thanks to static typing and overloading. A Swift Dictionary has an associated DictionaryIndex type, which is guaranteed to be different from its Key type, which means that you can do both by-key and by-index lookup on the Dictionary with no ambiguity. This could be even more interesting with a sorted-tree-based mapping (like C++'s std::map). In Python, because a dict is Iterable (yielding its keys), and has methods that give you key, value, and key-value pair views that are themselves Iterable, you can often get away with not being able to index it, but there are cases where it would be useful to get the element before the current one, etc., and there's just no way to do that.

Unfortunately, other than DictionaryIndex, there are really no examples of any of this being used. Everything else in the stdlib is indexed with Int, except for CollectionOfOne, which is indexed with Bit, which is just another kind of RandomAccessIndex that only has two values (the enum values zero and one).

Finally: In Python, if you write your own Integer-like type and want it to be usable as an index to lists and similar classes, you implement the __index__ method. You do the same thing in Swift by implementing ArrayBound, defining an associated type ArrayBoundType, and adding a method getArrayBoundValue. (Oddly, all of the standard number types define ArrayBoundType=self, and a getArrayBoundValue that returns self, but none of them inherit from ArrayBound.) Anyway, it looks like this is also usable for defining types that can be adapted to index values for other classes, not just arrays, although there aren't any examples of such a thing, so it's hard to say how it works.

Slicing

In Python, slicing (e.g., a[1:5]) works by calling the same __getitem__ method with a special value of type slice.

In Swift, the solution is basically the same: a[1..5] calls subscript with a special value of type Range.

(Python has different types for slice and range. The reason for this is negative indexing; -4:5:1 means all the indices starting 4 from the end and going until 5 from the start, and there's no way to know how many elements that is without knowing the length of the Sequence it's being applied to. So, the slice type has an indices method that takes a length and gives you back the positive start, stop, and step for that length. Because Swift doesn't have negative indexing—which sucks, but that's another story—it can use the same type for ranges and slices, and doesn't need a method to convert between the two.)

And this is one of those cases where static typing rocks. In Python, you get a single __getitem__ method. If you want to support slicing, you have to check isinstance(idx, slice) inside that method and do the appropriate thing. In Swift, there are two overloads for subscript; one takes IndexType, the other takes Range<IndexType>.

Swift also has a separate protocol, Sliceable, that defines this overload, along with MutableSliceable. It's a little strange that Sliceable doesn't inherit from Collection (especially since this means a Collection with Element type Range<T> could accidentally be used as a Sliceable with Element type T…). Even more so given that MutableSliceable does inherit from MutableCollection. Even more so that, while ArrayType inherits from MutableSliceable, concrete types like Array<T> and ContiguousArray<T> instead inherit from MutableCollection and Sliceable directly. (Then again, they also don't implement the subscript overloads as settable properties, and the fact that the former is defined as Array<T> and then extended as T[] implies that there's some compiler magic under the hood here…)

Also, for some reason, most of the Collection types that could trivially be Sliceable, like Repeat and ReverseView, are not. Some of them can slice anyway, some can't. (Compiler magic again?) And there doesn't seem to be anything that can take any randomly-accessible Collection and give you a Sliceable wrapper, which seems like it would be simple.

Numbers

Earlier, I was looking for something like Addable, and annoyed that it looked like I'd have to define it myself, guess all the appropriate stdlib types to add it to, and then extend them all before I could write a sum method.

There are actually a whole bunch of protocols for numeric and similar behavior, they're just not documented, or very well organized. So, here's a list:
  • LogicValue: getLogicValue (allows your type to be used in boolean contexts like if)
  • Equatable: == (but not !=)
  • Hashable: Equatable plus hashValue
  • Comparable: Equatable plus <=, >, >= (but not <)
  • IntegerArithmetic: Comparable plus +, -, *, /, %, toIntMax
  • BitwiseOperations: &, |, ^, ~, allZeros
  • SignedNumber: unary -
  • AbsoluteValuable: SignedNumber plus abs
  • FloatingPointNumber: nan/inf-related methods
  • Integer: RandomAccessIndex
  • SignedInteger: Integer
  • UnsignedInteger: Integer
The standard integral types (Int8/16/32/64, UInt8/16/32/64, Int, and UInt—the latter two are separate types from all of the specific-sized types, defined as equivalent to long and unsigned long in C) all implement Hashable, either SignedInteger or UnsignedInteger, RandomAccessIndex (even though that's already taken care of by the last one), BitwiseOperations, and, for signed types, SignedNumber. None of them claim to implement Equatable, Comparable, IntegerArithmetic, AbsoluteValuable, or ArrayBound. They do all implement all of the relevant methods. And casting to those types succeeds anyway. But you get garbage when you try to print the casted value, and none of the methods actually work if you try to call them on it.

The standard floating-point types (Float, Double, Float80; Float32 and Float64 are aliases for the first two) implement Hashable, Comparable, AbsoluteValuable and RandomAccessIndex. They do not claim to implement IntegerArithmetic or FloatingPointNumber; again, they implement the methods, casting appears to succeed anyway, you get garbage when you try to print the value, and none of the methods work.

Bool is (intentionally) not an integral, or even numeric, type; there are no types that are both a LogicValue and a number. But Bit is a similar type that inherits from Int and only has the values zero and one. (And, unlike all the actual numeric types, it implements IntegerArithmetic.)

So, none of this is really usable, at least in the beta; it looks like you do have to define your own protocols and extension them on.

And as for the Convertible protocol I was hoping for, there's this intriguing function: "numericCast<T, U>(x: T) -> U". It's defined four times in a row. But… how could you possibly use that? You can't explicitly specialize functions. And I can't find any situation in which the compiler can infer the U type. I suppose it's defined 4 times in a row because April is the 4th month, and they're April-fooling us?

No DefaultConstructible, ZeroConstructible, etc. either, needless to say.

What itertools-like functionality is already there?

There are four functions with the same name, and basic functionality, as the Python builtins: enumerate, filter, map, and reduce. The first three are all documented. And there are a few more than may or may not be interesting.

Looking at how these functions are implemented (or at least what types they use) may be a good guide for how to write more such functions. (Or, in my case, maybe how to scrap them and rewrite them…) Are they all using GeneratorOf, or defining custom structs? Do they have separate overloads for Generator and Sequence?

Unfortunately, they all seem to be different.
  • enumerate: Always takes a Sequence, returns a Generator, of a custom type.
  • filter: Takes a Collection and returns a forward-only Collection, or takes a Sequence and returns a (reusable) Sequence, both custom types (with View stuck in their names), along with a third custom type for a Generator.
  • map: Identical to filter, except with a Collection it returns a similarly-indexable Collection. (Another overload also takes an Optional and returns an Optional.)
  • PermutationGenerator: A single Generator struct without a wrapper function, which takes a Collection and a Sequence, but is so far from being understandable by a human or a compiler that I have no idea what they mean.
  • Repeat: A single random-access-indexable Collection struct without a wrapper function (meaning of course that it can't handle infinite sequence, and that end is not optional, making it a lot less useful).
  • reverse: Takes a Collection, returns a lazy bidirectional-only Collection of custom type.
  • Zip2: A Sequence struct with a separate Generator struct (which isn't a Sequence), which takes two Sequences and zips them into a Sequence of pairs, ending as soon as the shorter Sequence ends. (If you happen to have a Generator that isn't a Sequence—which shouldn't be possible, but this very function shows that it can happen—you can use the Generator, named ZipGenerator2 rather than Zip2Generator, directly to zip together two Generators into another Generator.) (Oddly, the variable names here refer to "Streams" rather than "Generators". I'm guessing after a dev meeting where the Python fans and C++ fans came to blows over what "iterator" is supposed to mean, someone suggested "stream" for the Python thing and "index" for the C++ thing and that stuck for a while, and then there was a Great Renaming, possibly to punish the leader of the Pythonista faction for some transgression like forgetting to restart the coffee, and someone missed this function.) 
It's pretty obvious why filter and map do different things with Collections (you can't randomly-access a collection that's being filtered on the fly). And those are probably the most carefully-designed functions. And, sadly, there's no CollectionOf helper that will give me a collection of the same element type and index type, much less a CollectionOrSequenceOf that will give me whichever is appropriate based on input. So, it looks like I may need more boilerplate, not less. A code generator is starting to look better and better…

On the other hand, it looks like I've been overly verbose with my associated type matching. For example, Sequence<T> defines init overloads for G: Generator where T == T, not where G.Element == T.

There are some non-itertoolsy functions that at least consume Sequences or Generators worth looking at:
  • join: Takes a Sequence for the parts to be joined. And a Collection for the join-string (which, obviously, doesn't have to be a string) and returns a Collection of the same type.
  • maxElement, minElement: Take a Sequence.
  • reduce: Takes a Sequence.
There are also some misleading functions that initially look itertoolsy (and that will get in the way, too, until Swift handles namespaces properly):
  • count: Just returns the size of a Range of RandomAccessIndexes, which hardly seems worth a builtin function.
  • countElements: Sort of like ilen, in that it returns the length of anything, even if it has to count the whole thing, except for Collections, not Sequences. (And I'm not sure how this works; it returns T.IndexType.DistanceType without putting any requirements on T that would make it have such a thing. The compiler won't let me write code like that.)
  • dropFirst: Like but_first, or islice(1), except that it only works on Sliceable types. So it's really just x[1..x.count]. I suppose the fact that Swift doesn't have open-ended slices (which sucks) makes this not completely useless, but only just barely.
  • dropLast: As with dropFirst, only works on Sliceable types, meaning it's just a minor convenience.
  • sort: Only works on arrays (not even randomly-accessible Collections?). Also, both mutates and returns the array, just to confuse people coming from other languages. There's also insertionSort for bidirectional Collections and quickSort for SignedInteger-indexed (?) Collections, with different APIs.
  • split: Takes a Sliceable, returns an Array.

Optional

Optional is a lot closer to the Haskell model than it appears. It really is just an enum of Some(T) and None, with some minor just syntactic sugar that maps T? to Optional<T> and some stuff to make None values show up as and compare equal to nil.

So, does that mean that you can distinguish None from Some(None) in a T??, exactly as you would with Nothing and Just Nothing in a Haskell Maybe Maybe t, instead of trying to monkey around with let-binding to avoid the ambiguous/confusing nil check? Hopefully. (Or I suppose you could fmap the == nil into the monad, if you want to get silly…)

Other stuff

There seems to be some thing like Python named tuples, or the record types that decay to tuples as seen in many functional languages. For example, enumerate's Element is defined as a typealias for (index: Int, element: Base.Element). As the docs show, you can assign this to a 2-tuple, and as with a normal tuple you can access the values as .0 and .1, but you can also access them as .index and .element. Is that in the docs?

Printing can also be customized in many different ways. (There's an OutputStream protocol, to customize from that side, but normally you want to do it from the other side.) So, if you implement Streamable, you'll get writeTo called with the OutputStream; if not, but you implement Printable, you'll get description called, and the string you return gets written to the OutputStream; if not, some default value gets generated and written to the OutputStream. You can also implement DebugPrintable and debugDescription will be called if you're being printed to the debug stream (and this will also be used as a fallback if you didn't implement Streamable or Printable.) There's also a StringInterpolatingConvertible, with one method that takes an array of strings of type Self, and another that takes an expr of some unspecified type T, and I assume this is for customizing string interpolation, but I don't know how to use it. (Normally, interpolation just does the Printable thing.)

There's a reflection API (Mirror and Reflectable), but any attempt to use this seems to hang.

The way you allow "coercion" for literals to your type is with CharacterLiteralConvertible, IntegerLiteralConvertible, StringLiteralConvertible, ArrayLiteralConvertible, and DictionaryLiteralConvertible. The first four have FooLiteralType as an associated type; Array has Element; Dictionary has Key and Value. Presumably the literal gets coerced to that type before your convertFromFooLiteralValue gets called with the result.

Some types seem to have a protocol that's only implemented by that one type. Besides separating interface and implementation, this also seems to be a way to make most of the methods non-generic (by using associated types instead, of course). Since I already know that Sequence and Generator work this way, I shouldn't have been surprised to find ArrayType, etc.

Despite the docs going out of their way to say that overloading is a feature of methods and not functions, it works exactly the same way in functions, and the stdlib uses it all over the place, and of course it's used to solve exactly the same things I wanted it for. For example, sum1 and sum can be overloads instead of having stupid separate names (just like, e.g., the two versions of quickSort with and without less, which have different requirements on T).

The comments make it clear that the designers of the Swift stdlib were looking at all of C++, Python, and Haskell for inspiration; e.g., for HeapBufferStorage they show the C++ template equivalent, while for Optional.map, the comment is "Haskell's fmap, which was mis-named".

One last interesting note is how farsighted the Unicode support is, given that the ObjC runtime is basically UCS-2 clumsily faking itself as UTF-16. Quoting the comments, "Character represents some Unicode grapheme cluster as defined by a canonical, localized, or otherwise tailored segmentation algorithm." If you want to deal with grapheme clusters directly, you can, but if you just want to think in terms of characters, you can do that. (For example, you can define ExtendedGraphemeClusterLiteralConvertible if you want; if not, you can just define CharacterLiteralConvertible and let the stdlib do the conversion to characters and then hand them off to you.)

No comments:

Post a Comment