
Custom string delimiters in Swift 5 - ingve
https://ericasadun.com/2018/12/26/swift-5-gives-us-nice-things-custom-string-delimiters/
======
nixpulvis
I'm still learning Elixir, but the sigils approach to these kinds of problems
is interesting.

[https://elixir-lang.org/getting-started/sigils.html](https://elixir-
lang.org/getting-started/sigils.html)

~~~
inferiorhuman
Sure, that's not really unique to Elixir though. You've had things like that
in Ruby (e.g. %w, %W, %r, etc) and Rust (e.g. r").

~~~
nixpulvis
But you can make custom sigils.

~~~
kodablah
Still similar things are available in other languages, e.g.
[https://docs.scala-lang.org/overviews/core/string-
interpolat...](https://docs.scala-lang.org/overviews/core/string-
interpolation.html)

------
kibwen
The article mentions being inspired by Rust syntax, save for 'skipping the
ugly “r”', but neglects to mention that the leading r exists so that it's
possible to have raw strings that have zero hashes at either end. IOW, in
Rust, a string that allows escape sequences looks like "foo", and a string
that doesn't allow escape sequences looks like r"foo". And note that Rust
didn't invent this syntax, but rather took it from Python--though the addition
of optional hashes around the string delimiters allows this single syntax to
raw-encode not just backslashes, but also double-quotes (which Python has a
separate, triply-quoted string syntax for (and which Swift appears to have
used to denote multiline strings (which is just the default behavior of all
string literals in Rust))).

~~~
chrismorgan
Yes. In the article:

> _This system was inspired by the Rust programming language. Rust stacks one
> or more pounds at each end of a string (and prefixes the letter “r”) to
> create what it calls “raw strings”, that is strings without further escape
> sequence interpretation. You cannot incorporate interpolation or coded tabs,
> new lines, or returns._

“one or more” should be “zero or more”, and the bit about the “r” prefix
shouldn’t be parenthesised, as _it’s_ actually the part that makes it a raw
string. Also its mention of _interpolation_ is poor: Rust doesn’t have
interpolation on plain string literals, but things like the format!() macro
perform what is more or less interpolation on their first argument (which must
be a string literal), and raw strings work just fine there.

~~~
richardwhiuk
The key thing is that raw strings don't have escape sequences.

------
jakobegger
The only thing I want from Swift 5 is easier string handling. I just want an
easy and concise way to do things like split a filename into parts, extract
regex matches, etc. At the moment it's faster to write string handling code in
Objective C...

~~~
saagarjha
> split a filename into parts

This is not something strings should do; you should be using URL instead.
Foundation realized this long ago and has started deprecating string-based
path APIs because treating a path as a string exposes fundamental
misconceptions very quickly.

> extract regex matches

This is something that is being worked on/discussed, but has not been “solved”
yet.

~~~
jakobegger
> This is not something strings should do;

I didn't mean that String should have a "pathExtension" property like
NSString. I just want operations like "insert a number before the last period"
to be easier to do.

Also, there are a lot more things that are a pain with String. I mean, just
the fact that I can't pass a Substring to a function that expects a String
boggles my mind.

~~~
saagarjha
Yes, like I said, you should be using URL for this, which will make it
possible to extract the filename without the extension, modify it, and get the
path string from that (which is semantically what I believe you are trying to
do). Not only does this create self-documenting code, it also makes it harder
to fall into a the many “gotchas” that handling paths entails.

~~~
jakobegger
Yes, I know that, but I was just using filename manipulation as an example,
because it was the first thing that came to my mind. I now see it was a bad
example, since URL has a couple of APIs for file name handling.

Anyway, these types of operations come up all the time when working with
strings, and these operations are really really incovenient in Swift.

~~~
saagarjha
> Anyway, these types of operations come up all the time when working with
> strings, and these operations are really really incovenient in Swift.

Again, what are these operations? I think most of the issues people have with
String is due to hard to discover APIs rather than design issues with String.

~~~
jakobegger
Text parsing is something that comes up often (for me). For example parsing
CSV files, or parsing tab separated data from the clipboard, or parsing
various types of config files.

Swift has sweet solutions for a few special cases (eg. parse file names, parse
dates, parse JSON), but parsing generic text is not something that Swift is
good at.

NSString has a rich set of APIs for those tasks (NSScanner,
NSRegularExpression, and all the rangeOfString... methods). Swift has pretty
much nothing. (There is firstIndex(of:), but as far as I can see there is no
way to find the next index, which makes it pretty much useless for most
parsing tasks)

Maybe the problem is that I'm just not familiar enough with Swifts String, but
until now every time I've had to work with Strings I became very frustrated.

~~~
tinus_hn
I would strongly suggest that you don’t write a parser for .csv yourself, it’s
a terrible format.

------
travisgriggs
I really wish Swift closures were more uniform. I remember Alan Kay (or maybe
Dan Ingalls) was famed for saying “its ok to cheat, as long as you don’t get
caught” (re. Language implementation).

I really like swift, but there are times when I feel I need a magic decoder to
figure out which edge cases apply. My biggest frustration is that something
like this

    
    
        for view in self.subviews { view in
            ...
            return something
        }
    

Is very different from the naively similiar

    
    
        self.subviews.forEach { view in
            ...
            return something
        }
    

I just wish closures were a more first class part of the language like they
were in Smalltalk. If language written in 1980 could use closures/lambdas all
the way down even for things like if/else, and keep the deception pretty
complete, I don’t understand why swift can’t do similiar.

~~~
delinka
I don't understand your syntax on the for-in. Is there some magic on an array
that takes a closure and is nonobvious? Or is there a syntactic issue with the
code in your comment?

~~~
Groxx
tho I'm not familiar with swift: my guess here is that the first (for ... in)
shares a closure / var, so stuff like this classic JS mistake can happen:

    
    
        for i=0;i<10;i++ { setTimeout(() => console.log(i), 100); }
    

and you get 10 calls logging "9". (most languages I've touched do this, JS
just makes it worse since `var x` in the loop will not fix it)

The second won't do that, since the "view" var isn't "mutated" between calls.

~~~
aplummer
A good try from a JS perspective however:

1) view is immutable in both cases

2) GGP is returning in a void block so both sets of code will fail

3) there is really no difference except iteration vs function calls as far as
I can tell, you can iterate arrays and use higher order functions built into
arrays... which seems normal

4) you can only access the index in (1) by calling .enumerated() I suppose.

~~~
throw_away2
I think the difference OP is pointing out is that the return behaves
differently. In the forEach, the return exits the closure, but in for-in, it
returns from the enclosing function.

~~~
delinka
First, to make arguments that rely on syntax, one must use correct syntax. If
OP’s syntax were valid, that return in the for-in would indeed exit the
closure.

To address this argument: the fact that a ‘return’ within a loop exits the
enclosing function and a return from within a closure exits the closure is not
a Swift problem.

------
mistrial9
I am not familiar with the Swift world - a quick look at the site says
"Objective-C not present on Linux" .. I assume that means the GUI parts also ?

I am not sure how this plays out, if you think.. Apple gets the GUI builder
only on Apple products, but "community" builds the plumbing, while small
groups at Apple or elsewhere, are paid to make sure plumbing + GUI is
advancing .. ? If this is roughly true, what benefit does the non-Apple world
get here?

~~~
zapzupnz
The benefit is that Swift is a fantastic language that people want to use
outside of macOS and iOS development. Remember that languages and frameworks
aren’t the same thing.

You can still use Swift to plug into other GUI frameworks and libraries to
make apps on other platforms, and there are a few Swift frameworks for writing
web applications such as Vapor and IBM Kitura.

Swift on Linux lacks the Objective-C runtime and therefore the Objective-C-
based Foundation and Cocoa frameworks. In their place, the Swift project has
been creating a pure Swift replacement.

Linux is also an easy target for cross-platform development in Swift, given
that it’s a POSIX OS and a first—class host for LLVM. It’s a great testbed for
the pure Swift and cross-platform developmen and tooling before it eventually
moves to Windows as a first-class Swift platform.

~~~
saagarjha
> Swift on Linux lacks the Objective-C runtime and therefore the Objective-C-
> based Foundation and Cocoa frameworks. In their place, the Swift project has
> been creating a pure Swift replacement.

Clarification: Foundation is being reimplemented in Swift. Cocoa will continue
to be written in Objective-C and be macOS-only.

~~~
zapzupnz
Yes, I should have said “in their place _for non-Apple platforms_ ”.

------
anonacct37
Perl has some similar features and they tend to make syntax highlighting and
editor support more difficult. Will that be a problem with swift?

~~~
werdnapk
Ruby string interpolation (might be the exact same as Perl): %[string] or
%^string^ or %(string), etc. Basically % followed by any pair of the same
character around a string works.

[https://en.wikibooks.org/wiki/Ruby_Programming/Syntax/Litera...](https://en.wikibooks.org/wiki/Ruby_Programming/Syntax/Literals#The_.25_Notation)

------
Waterluvian
This kind of feels like, "so we put a huge ice cube in the ocean and finally
solved global warming."

Isn't it just yet another syntax for strings with another set of rules? What
happens when my string includes """# ? Why not just make """ behave this new
way and have one less type of string literal?

Why can't the solution be, "put the ASCII in a file to be read in." ?

~~~
dbaupp
""" can't behave like this, because it won't be able to include """ itself in
the string. The """# case is handled by using more #s:

    
    
       ##"""
       in string """# still in string
       """## // out of string
    

Swift has two types of string "..." and """...""", either of which can have
zero-or-more # at the start and end.

------
xisukar
Perl 6 (Raku) takes it a little further with what's known as the Q lang[1]. In
their most literal form possible, strings are created by using a Q followed by
any pair delimiting surrounding the text.

    
    
        Q[This is a literal string.]        #=> This is a literal string.
        Q!The most literal-est of all.!     #=> The most literal-est of all.
        Q<So I say 'Hello'>                 #=> So I say 'Hello'
    

From there, adverbs are used to sort of expand a literal string's behavior.
For example, `Q:q` (or its more verbose form, `Q:single`) means the string can
interpolate \\\, \qq[...] and escaping the delimiter with \\.

    
    
        Q:q[A simple string]                #=> A simple string
        Q:q{Backslash: \}                   #=> Backslash: \
        Q:q!Backslash: \\!                  #=> Backslash: \
        Q:q{This is a \} so not too early.} #=> This is a } so not too early.
     
     

In the other hand, `Q:qq` (or `Q:double`) means the string can interpolate
scalars (`$`), arrays (`@`), hashes (`%`), function calls (`&`), closure
(`{...}`) and backslashes.

    
    
        my $bool = True;
        my @bools = True, False;
        my %bools = False => 0, True => 1;
    
        Q:qq[Scalar: $bool]                        #=> «Scalar: True»
        Q:qq{Array: @bools[]}                      #=> «Array: True False»
        Q:qq (Hash: %bools{})                      #=> «Hash: False	0␤True  1»
        Q:qq!Function: %bools.keys.join(' and ')!  #=> «Function: False and True»
        Q:qq<Expression: { 1.0 + 2.0 == 3.0 }>     #=> «Expression: True»
    

In order to prevent unwanted interpolation such as in "username@email.xyz" or
"[https://es.wikipedia.org/wiki/%C3%87"](https://es.wikipedia.org/wiki/%C3%87"),
only scalar variables are interpolated normally. As demonstrated, the array
constructor `[]` is used to interpolate a whole array , the hash constructor
`{}` for a hash, etc.

Since both `Q:q` and `Q:qq` are so common in the language, they have more
usual and succinct forms: single quotes (`''`) for `Q:q` and double
quotes(`""`) for `Q:qq`. Furthermore the Q can be dropped so `q` or `qq`
(followed by a delimiter) can be used as well.

From here onwards, a small number of adverbs can be used on the quoting
constructs `q` or `qq` to further restrict/expand a string's behavior. For
example, for a string that only interpolates scalar ($) and closure ({...})
variables, the adverbs `:s` (or `:scalar`) and `:c` (or `:closure`) can be
used. This results in `qq:s:c` (or `qq:sc`, `qq:scalar:closure`). For example:

    
    
        my $var = 25;
        qq:s:c<$var: { 3**2 + 4**2 }> #=> «25: 25»
    

Another example is the use of `q` with the `:to` (or `:heredoc`) adverb which
seems to be the closest construct to Swift 5's custom string delimiters:

    
    
        my $billTheCat = q:to/BILL/;
        <------- ____
          &&&    /    \  __ _____,
            `-- |  o   \'  `  &&/
               `|      | o  },-'
                 \____( )__/
                 ,'    \'   \
         /~~~~~~|.      |   .}~~~\
          ,-----( .     |   .}--.
                | .    /\___/
                 `----^,\ \
                        \_/
        BILL
        
    

[1]
[https://docs.perl6.org/language/quoting](https://docs.perl6.org/language/quoting)

~~~
b2gills
That is slightly wrong; `:qq` implies `:q:b:c:s:a:h`, so `qq:s:c` is the same
as just `qq`.

You probably wanted to use either `q:s:c` or `Q:s:c`.

You can turn off features using the exclamation mark `qq:!a`

~~~
xisukar
Nice catch!

It's quite easy to get them wrong since `q` is just a `q` short to be `qq`. ;)

