0

If I use functional-style method chains for string manipulation, I can not use the usual machinery for getting the first or last few characters: I do not have access to a reference to the current string, so I can not compute indices.

Example:

[some, nasty, objects]
    .map( { $0.asHex } )
    .joined()
    .<first 100>
    .uppercased()
    + "..."

for a truncated debug output.

So how to I implement <first 100>, or do I have to break the chain?

Community
  • 1
  • 1
Raphael
  • 9,779
  • 5
  • 63
  • 94
  • Why can't you use the "usual machinery" in a map here? – Rob Napier Apr 07 '17 at 12:19
  • @RobNapier Map on which value? (Let's not get hung up with this toy example, if that's the issue.) Maybe I'm overthinking things; in that case I'd appreciate an answer. – Raphael Apr 07 '17 at 12:22
  • 2
    If [SE-0163](https://github.com/apple/swift-evolution/blob/master/proposals/0163-string-revision-1.md) gets accepted, then `String` will (once again) conform to `Collection`, meaning that you'll be able to just get the `prefix(_:)` :) – Hamish Apr 07 '17 at 13:20
  • @Hamish Nice, thanks! That said, the Collection API confuses me deeply. It almost never seems to work as I want it to (subcollections have different types; the whole business with indices and ranges is cumbersome). Maybe I should do some reading ... can you recommend a resource? The code documentation is definitely no help. :/ – Raphael Apr 07 '17 at 16:56
  • @Raphael I do agree the Collection API isn't as sleek as it could be (really it's just missing a few convenience methods for common indexing operations, e.g `index(atOffset:)`, which would just offset the `startIndex` by a given offset – but these can always be added as extensions). Usually, I don't find sub-collections to be cumbersome – they generally implement the same interface as the parent collection, and can always be easily converted back with an initialiser call (although that being said, they're annoying to work with in extensions due to.... – Hamish Apr 08 '17 at 21:49
  • the lack of `associatedtype` `where` clauses, so the compiler doesn't know various things about them, e.g they should be `Collection`s themselves, with elements and indices of the same type as parent collection – but thankfully this will all change soon with [SE-0157](https://github.com/apple/swift-evolution/blob/master/proposals/0157-recursive-protocol-constraints.md) & [SE-0142](https://github.com/apple/swift-evolution/blob/master/proposals/0142-associated-types-constraints.md)). Although, in general, I don't think the Collection API is too bad – it's fundamentally a simple concept, just a.. – Hamish Apr 08 '17 at 21:49
  • collection of elements that are accessible by an index, and can be non-destructively iterated over. The main difficulty only arises because it has such a broad range of instance members (and lots of 'subprotocols', e.g `RangeReplaceableCollection`). I'm afraid I can't recommend any resources for you though, I actually don't find the official documentation *too* bad (it's certainly better than it used to be!). You can always ask a question about a specific part of `Collection` that you feel the documentation has missed – and I'll be happy to (attempt to) answer :) – Hamish Apr 08 '17 at 21:49
  • @Hamish Yea... to I have a `startIndex` which I can offset using an integer to get the index I actually want to take an element from. So why on earth don't I get the good old `get(i)`, or maybe even an array-style subscript? And why can I sometimes use only one of open and closed ranges? Maybe my complaints are just incompleteness of the API as opposed to conceptual issues, but so far I have not learned what the advantage of the very verbose constructs are. Scala, for instance, has a very strong Collection API -- but I found it easy to use as well. Anyway, thanks for your elaborate comments! – Raphael Apr 08 '17 at 22:42
  • 1
    @Raphael I'm not aware of any cases where you can't subscript a Collection with a `ClosedRange` – there's a `subscript` [overload defined](https://github.com/apple/swift/blob/master/stdlib/public/core/CollectionAlgorithms.swift.gyb#L513) on `_Indexable` (which `Collection` internally derives from). Regarding not having a convenient `get(i)`-style method – I don't know the full rationale, but I'm aware that the concern is partly performance related, for example `for i in 0.. – Hamish Apr 09 '17 at 10:29
  • 1
    ... but would in fact be quadratic as `String.CharacterView` isn't a `RandomAccessCollection` – having to say `str.index(str.startIndex, offsetBy: i)`, while cumbersome, at least makes you more aware of this fact. Although as I said earlier, it's still not as sleek as it could be – I'm not sure if adding a `get(_:)`-style method would be the optimal solution, due to the fact that it would confuse the `Array` API slightly (do I say `array.get(i)` or `array[i]`?) – but regardless, it's definitely lacking *some* shorter way of getting an element at a given index offset. – Hamish Apr 09 '17 at 10:29

1 Answers1

1

I don't know of any API that does this. Fortunately, writing our own is an easy exercise:

extension String {
    func taking(first: Int) -> String {
        if first <= 0 {
            return ""
        } else if let to = self.index(self.startIndex, 
                                      offsetBy: first, 
                                      limitedBy: self.endIndex) {
            return self.substring(to: to)
        } else {
            return self
        }
    }
}

Taking from the end is similar.

Find full code (including variants) and tests here.

Raphael
  • 9,779
  • 5
  • 63
  • 94