obo.dev

Литералы (Literal)

25 Dec 2022

Литералы

Последовательность символов, представляющая постоянные значения, хранящиеся в переменных, называется литералом.

Литерал — это представление значения в исходном коде, например число или строка.

Стандартные литералы

Swift предоставляет следующие типы литералов:

Название Предполагаемый тип по умолчанию Пример
Integer Int 123, 0b1010, 0o644, 0xFF
Floating-Point Double 3.14, 6.02e23, 0xAp-2
String String “Hello”, “”” . . . “””
Extended Grapheme Cluster Character “A”, “é”, “🇺🇸”
Unicode Scalar Unicode.Scalar “A”, “´”, “\u{1F1FA}”
Boolean Bool true, false
Nil Optional nil
Array Array [1, 2, 3], [“Den”, “Bob”]
Dictionary Dictionary [“a”: 1, “b”: 2]

Самое важное, что нужно понять о литералах в Swift, это то, что они определяют значение, но не определенный тип.

Когда компилятор встречает литерал, он пытается автоматически определить тип. Он делает это, ища каждый тип, который может быть инициализирован таким литералом, и сужая его на основе любых других ограничений.

Если ни один тип не может быть выведен, Swift инициализирует тип по умолчанию для этого вида литералов — Int для целочисленного литерала, String для строкового литерала и т.д.:

57 // Integer literal
"Hello" // String literal

В случае nil литералов тип никогда не может быть выведен автоматически и поэтому должен быть объявлен:

nil // ! cannot infer type
nil as String? // Optional<String>.none

Для литералов массивов и словарей связанные типы для коллекции выводятся на основе ее содержимого. Однако вывод типов для больших или вложенных коллекций — сложная операция, которая может значительно увеличить время, необходимое для компиляции кода. Вы можете ускорить процесс, добавив явный тип в свое объявление:

// Explicit type in the declaration
// prevents expensive type inference during compilation
let dictionary: [String: [Int]] = [
    "a": [1, 2],
    "b": [3, 4],
    "c": [5, 6],
    
]

Целочисленные литералы

Целочисленный литерал (Integer literals) может быть десятичной, двоичной, восьмеричной или шестнадцатеричной константой.

  • Десятичные литералы (Decimal literals): Каждое значение, объявленное в целочисленном литерале, имеет десятичный тип. Следовательно, ему не предшествует какое-либо число или символ, как в приведенных выше двоичных литералах.
  • Двоичные литералы (Binary literals): начинается с префикса 0b. Он представляет двоичные значения.
  • Восьмеричные литералы (Octal literals): начинаются с префикса 0o. Он используется для представления восьмеричных значений.
  • Шестнадцатеричные литералы (Hexadecimal literals): начинаются с префикса 0x. Он используется для представления шестнадцатеричных значений.

Десятичные литералы содержат цифры от 0 до 9. Двоичные литералы содержат 0 и 1, восьмеричные литералы содержат от 0 до 7, а шестнадцатеричные литералы содержат от 0 до 9, а также от A до F в верхнем или нижнем регистре.

Отрицательные целые литералы выражаются путем добавления знака минус (-) перед целочисленным литералом, как в -42.

Символы подчеркивания (_) разрешены между цифрами для удобства чтения, но они игнорируются и поэтому не влияют на значение литерала. Целочисленные литералы могут начинаться с ведущих нулей (0), но они также игнорируются и не влияют на основание или значение литерала.

Если не указано иное, предполагаемым типом целочисленного литерала по умолчанию является тип Int стандартной библиотеки Swift.

Вот несколько примеров целочисленных литералов:

let binaryInteger = 0b10001     // 17 in binary notation
print(binaryInteger)

let decimalInteger = 17         // 17 in decimal notation
print(decimalInteger)

let octalInteger = 0o21         // 17 in octal notation
print(octalInteger)

let hexadecimalInteger = 0x11   // 17 in hexadecimal notation
print(hexadecimalInteger)

Output
17
17
17
17

Литералы с плавающей точкой

Литерал с плавающей точкой (Floating point literals) имеет целочисленную часть, десятичную точку, дробную часть и экспоненту. Вы можете представлять литералы с плавающей точкой в ​​десятичной или шестнадцатеричной форме.

По умолчанию литералы с плавающей запятой выражаются в десятичном виде (без префикса), но они также могут быть выражены в шестнадцатеричном виде (с префиксом 0x):

  • Десятичные литералы с плавающей точкой (Decimal floating-point literals) состоят из последовательности десятичных цифр, за которыми следует либо десятичная дробь, либо десятичный показатель степени, либо и то, и другое. Десятичная дробь состоит из десятичной точки (.), за которой следует последовательность десятичных цифр. Показатель степени состоит из префикса e в верхнем или нижнем регистре, за которым следует последовательность десятичных цифр, указывающая, на какую степень числа 10 умножается значение, предшествующее e. Например, 1,25e2 представляет 1,25 x 10^2, что равно 125,0. Точно так же 1,25e-2 представляет собой 1,25 x 10^-2, что равно 0,0125.
  • Шестнадцатеричные литералы с плавающей точкой (Hexadecimal floating-point literals) состоят из префикса 0x, за которым следует необязательная шестнадцатеричная дробь, за которой следует шестнадцатеричный показатель степени. Шестнадцатеричная дробь состоит из десятичной точки, за которой следует последовательность шестнадцатеричных цифр. Показатель степени состоит из префикса p в верхнем или нижнем регистре, за которым следует последовательность десятичных цифр, указывающая, на какую степень числа 2 умножается значение, предшествующее p. Например, 0xFp2 представляет 15 x 2^2, что равно 60. Точно так же 0xFp-2 представляет 15 x 2^-2, что дает 3,75.

Отрицательные литералы с плавающей запятой выражаются путем добавления знака минус (-) перед литералом с плавающей запятой, как в -42.5.

Символы подчеркивания (_) допускаются между цифрами для удобства чтения, но они игнорируются и поэтому не влияют на значение литерала. Литералы с плавающей запятой могут начинаться с ведущих нулей (0), но они также игнорируются и не влияют на основание или значение литерала.

Если не указано иное, предполагаемым типом литерала с плавающей запятой по умолчанию является тип Double стандартной библиотеки Swift, который представляет 64-битное число с плавающей запятой. Стандартная библиотека Swift также определяет тип Float, который представляет 32-битное число с плавающей запятой.

Вот несколько примеров литералов с плавающей точкой:

let decimalDouble = 12.1875
print(decimalDouble)

let exponentDouble = 3.14e2
print(exponentDouble)

let hexadecimalDouble = 0xFp10
print(hexadecimalDouble)

// Output
// 12.1875
// 314.0
// 15360.0

Строковые литералы

Строковый литерал (String literal) — это последовательность символов, заключенная в начальную двойную кавычку и закрывающую двойную кавычку.

Разница между строковым (String) и символьным (Character) литералом заключается в том, что строковый литерал содержит последовательность символов, а символьный литерал содержит один символ. Пример показан ниже:

var sampleCharacter: Character = "S"
print(sampleCharacter)

var sampleString: String = "My name is Sam"
print(sampleString)

// Output
// S
// My name is Sam

Предполагаемый тип строкового литерала по умолчанию — String.

Многострочные литералы строк

Если вам нужно создать строку, которая поддерживает многострочный вид, используйте литерал многострочной строки — последовательность символов, обернутых в три двойные кавычки ("""):

let quotation = """
The White Rabbit put on his spectacles. "Where shall I begin,
please your Majesty?" he asked.

"Begin at the beginning," the King said gravely, "and go on
till you come to the end; then stop."
"""
print(quotation)

// Output
// The White Rabbit put on his spectacles. "Where shall I begin,
// please your Majesty?" he asked.
//
// "Begin at the beginning," the King said gravely, "and go on
// till you come to the end; then stop."

Многострочный литерал строки включает в себя все строки между открывающими и закрывающими кавычками. Строка начинается на первой строке после открывающих кавычек ("""), а заканчивается на строке предшествующей закрывающим кавычкам ("""), что означает, что ни одна из строк ниже ни начинается, ни заканчивается символом переноса строки:

let singleLineString = "These are the same."
let multilineString = """
These are the same.
"""

Когда ваш исходный код включает в себя символ переноса строки внутри литерала многострочной строки, то этот символ переноса строки так же появляется уже внутри значения этой строки.

Если вы хотите использовать символ переноса строки для того, чтобы сделать ваш код более читаемым, но вы не хотите чтобы символ переноса строки отображался в качестве части значения строки, то вам нужно использовать символ обратного слэша (\) в конце этих строк:

let softWrappedQuotation = """
The White Rabbit put on his spectacles. "Where shall I begin, \
please your Majesty?" he asked.

"Begin at the beginning," the King said gravely, "and go on \
till you come to the end; then stop."
"""
print(softWrappedQuotation)

// Output
// The White Rabbit put on his spectacles. "Where shall I begin, please your Majesty?" he asked.
//
// "Begin at the beginning," the King said gravely, "and go on till you come to the end; then stop."

Для того, чтобы создать литерал строки, который начинается и заканчивается символом возврата каретки (\r), напишите пустую строчку в самом начале и в конце литерала строки, например:

let lineBreaks = """

This string starts with a line break.
It also ends with a line break.

"""
print(lineBreaks)

// Output
//
// This string starts with a line break.
// It also ends with a line break.
//

Многострочная строка может иметь отступы для соответствия окружающему ее коду. Пробел до закрывающей группы двойных кавычек (""") сообщает Swift, сколько пробелов нужно игнорировать в начале каждой строки. Если же вы напишите дополнительные пробелы напротив какой-либо строки к тем, которые стоят напротив закрывающих кавычек, то эти дополнительные пробелы уже будут включены в значение строки.

let linesWithIndentation = """
    Эта строка начинается без пробелов в начале.
        Эта строка имеет 4 пробела.
    Эта строка так же начинается без пробелов.
    """
print(linesWithIndentation)

// Output
// Эта строка начинается без пробелов в начале.
//     Эта строка имеет 4 пробела.
// Эта строка так же начинается без пробелов.

В примере выше, не смотря на то, что весь литерал многострочной строки имеет отступ, первая и последняя строка будут начинаться без пробелов. Средняя же строка будет иметь отступ, так как она начинается с дополнительными четырьмя пробелами относительно закрывающей группы двойных кавычек.

Специальные символы в строковых литералах

Специальные символы используются в строковых литералах для формирования изменений строковых литералов, известной как управляющая последовательность (escape sequence).

Строковые литералы не могут содержать неэкранированные двойные кавычки (), неэкранированную обратную косую черту (\), возврат каретки или перевод строки.

Экранирование осуществляется путём добавления обратной косой черты (\) перед экранируемым символом.

Строковые литералы могут включать в себя следующие специальные символы:

  • экранированные специальные символы
    • \0 (нулевой символ - Null character),
    • \\ (обратный слэш - Backslash),
    • \t (горизонтальная табуляция - Horizontal tab),
    • \n (новая строка - Line feed),
    • \r (возвращение каретки - Carriage return),
    • \" (двойные кавычки - Double quotation mark),
    • \’ (одиночные кавычки - Single quotation mark),
  • произвольные скалярные величины Юникода (Unicode scalar), записанные в виде \u{n} , где n1-8-значное шестнадцатиричное число со значением, равным действительной точке кода Юникода.

Приведенный ниже код показывает все эти четыре примера специальных символов.

  • wiseWords константа содержит два экранированных символа: двойные кавычки,
  • DollarSign, blackHeart и sparklingHeart константы показывают скалярный формат Юникода:
let wiseWords = "\"Imagination is more important than knowledge\" - Einstein" // "Imagination is more important than knowledge" - Einstein
let dollarSign = "\u{24}"        // $,  Unicode scalar U+0024
let blackHeart = "\u{2665}"      // ♥,  Unicode scalar U+2665
let sparklingHeart = "\u{1F496}" // ?, Unicode scalar U+1F496

Ещё пример:

let stringL = "Hello\tWorld\n\nHello\'Swift\'"
print(stringL)

// Output
// Hello    World
//
// Hello'Swift'

Так как многострочные строки имеют три двойные кавычки вместо одной, то вы можете включить двойную кавычку внутрь многострочной строки без знака экранирования. Для того, чтобы включить символы """ в многострочную строку, вам нужно экранировать хотя бы одну из кавычек, например:

let threeDoubleQuotes = """
Escaping the first quote \"""
Escaping all three quotes \"\"\"
"""
print(threeDoubleQuotes)

// Output
// Escaping the first quote """
// Escaping all three quotes """

Интерполяция и конкатенация строк

Значение выражения можно вставить в строковый литерал, поместив выражение в круглые скобки после обратной косой черты (\). Это называется интерполяция. Интерполированное выражение может содержать строковый литерал, но не может содержать неэкранированную обратную косую черту, возврат каретки или перевод строки.

"1 2 3"
"1 2 \("3")"
"1 2 \(3)"
"1 2 \(1 + 2)"
let x = 3
"1 2 \(x)"

В этом примере все строковые литералы имеют одинаковое значение - "1 2 3".

Строковые литералы, объединенные оператором +, объединяются во время компиляции (compile time). Это называется конкатенация (объединение) строк. Например, значения textA и textB в приведенном ниже примере идентичны — конкатенация выполняется не во время выполнения (runtime).

let textA = "Hello " + "world"
let textB = "Hello world"

print(textA == textB)

// Output
// true

Еще пример:

// Swift program to concatenate two strings
  
// Initializing a string
let string1 = "Obodev is one of the "
  
// Initializing another string
// by directly concatenating two strings
let string2 = "best learning " + "platforms."
  
// Concatenate strings
let result = string1 + string2
  
// Print the resulting string
// after concatenation
print("result:", result)

// Output
// result: Obodev is one of the best learning platforms

Необработанные строковые литералы (Raw String Literals)

Необработанные строки позволяют нам отключить все динамические функции строковых литералов (такие как интерполяция и интерпретация специальных символов, таких как \n), в пользу простого обращения с литералом, как с необработанной последовательностью символов. Необработанные строки определяются путем окружения строкового литерала знаками решетки # (хэш-символами или «хэштегами», как их называют дети):

let rawString = #"Press "Continue" to close this dialog."#

Строка, разделенная расширенными разделителями, представляет собой последовательность символов, заключенных в кавычки и сбалансированный набор из одного или нескольких знаков решетки (number signs) - #. Строка, разделенная расширенными разделителями, имеет следующие формы:

#"characters"#

#"""
characters
"""#

Специальные символы в строке, разделенной расширенными разделителями, отображаются в результирующей строке как обычные символы, а не как специальные символы. Вы можете использовать расширенные разделители для создания строк с символами, которые обычно имеют особый эффект, такой как генерация интерполяции строк, запуск управляющей последовательности или завершение строки.

В следующем примере показан строковый литерал и строка, разделенная расширенными разделителями, которые создают эквивалентные строковые значения:

let string = #"\(x) \ " \u{2603}"#
let escaped = "\\(x) \\ \" \\u{2603}"
print(string)

// Output
// \(x) \ " \u{2603}

print(escaped)

// Output
// \(x) \ " \u{2603}

print(string == escaped)

// Output
// true

Хотя эта особенность, которая теоретически никогда не должна быть нужна, но можно добавить больше хеш-символов вокруг вашей строки, чтобы сделать более уникальные разделители строк.

Например, все они создают одну и ту же строку:

let zero = "This is a string"
let one = #"This is a string"#
let two = ##"This is a string"##
let three = ###"This is a string"###
let four = ####"This is a string"####

Причина, по которой это существует, заключается в том, что строки заканчиваются только тогда, когда вы этого хотите, поэтому в маловероятном случае, когда вам нужно написать "# в строке, вы не столкнетесь с проблемами.

Например, чтобы решить задачу, вам нужно написать строку типа My dog said "woof"#gooddog. Здесь не оставлено пробела после кавычки в слове “гав” и сразу же использовал хэш-символ. Используя необработанную строку с одинарным разделителем, Swift увидит это как признак конца строки, поэтому вместо этого вам нужно будет написать это:

let str = ##"My dog said "woof"#gooddog"##

Если вы используете более одного знака # для формирования строки, разделенной расширенными разделителями, не размещайте пробелы между знаками #:

print(###"Line 1\###nLine 2"###) // OK

// Output
// Line 1
// Line 2

print(# # #"Line 1\# # #nLine 2"# # #)
// Error: Expected expression in list of expressions
// Error: Invalid escape sequence in literal

Многострочные строковые литералы, создаваемые с помощью расширенных разделителей, имеют те же требования к отступам, что и обычные многострочные строковые литералы.

Чем полезны необработанные строки?

Предложение Swift Evolution для необработанных строк содержит три примера того, где необработанные строки являются хорошей идеей. В частности, код, который:

  • Заменяется экранирование. Экранирование активно вредит просмотру и проверке кода.
  • Уже экранировано. Экранированный материал не должен предварительно интерпретироваться компилятором.
  • Требуется легкий перенос между исходным кодом и кодом в обоих направлениях, будь то тестирование или просто обновление исходного кода.

В качестве примера рассмотрим регулярные выражения. Представьте, что у нас есть такая строка:

let message = #"String interpolation looks like this: \(age)."#

В этом примере используются необработанные строки, чтобы мы могли показать, как выглядит интерполяция строк, а не использовать ее на самом деле — строка \(age) появится в тексте, а не будет заменена значением переменной с именем age.

Необработанные строковые литералы особенно полезны, когда мы хотим встроить строки, которые должны содержать специальные символы, такие как кавычки или обратную косую черту.

Вот еще один пример, связанный с тестами, в котором мы используем необработанный строковый литерал для определения строки JSON для кодирования экземпляра пользователя:

class UserTests: XCTestCase {
    func testDecoding() throws {
        let json = #"{"id": 37, "name": "John"}"#
        let data = Data(json.utf8)
        let user = try data.decoded() as User

        XCTAssertEqual(user.id, 37)
        XCTAssertEqual(user.name, "John")
    }
}

В то время как необработанные строки по умолчанию отключают такие функции, как интерполяция строк, есть способ переопределить это, добавив еще один знак решетки сразу после обратной косой черты, ведущей к интерполяции. Например, если вы хотите использовать интерполяцию строк, теперь вам следует использовать \#(имя_переменной), а не просто \(имя_переменной), например:

let name = "Taylor"
let greeting = #"Hello, \#(name)!"#
print(greeting)

// Output
// Hello, Taylor!

Вы также можете использовать их с многострочными строками, например:

let message = #"""
This is rendered as text: \(example).
This uses string interpolation: \#(example).
"""#

И ещё пример:

extension URL {
    func html(withTitle title: String) -> String {
        return #"<a href="\#(absoluteString)">\#(title)</a>"#
    }
}

Наконец, необработанные строки также особенно полезны при интерпретации строки с использованием определенного синтаксиса, особенно если этот синтаксис в значительной степени зависит от символов, которые обычно необходимо экранировать в строковом литерале, например, в регулярных выражениях. Определяя регулярные выражения с использованием необработанных строк, экранирование не требуется, что дает нам выражения, которые настолько удобочитаемы, насколько они могут быть получены:

// This expression matches all words that begin with either an
// uppercase letter within the A-Z range, or with a number.
let regex = try NSRegularExpression(
    pattern: #"(([A-Z])|(\d))\w+"#
)

Логические литералы

** Логические (Булевы) литералы** (Boolean literals) бывают двух типов:

  • Значение true, представляющее истину.
  • Значение false, представляющее ложь.
let sample1: Bool = true
print(sample1)

let sample2: Bool = false
print(sample2)

// Output
// true
// false

Nil литерал

Nil литерал имеет одно значение nil, представляющее отсутствие значения.


Литералы в Playgrounds

Помимо стандартных литералов, перечисленных выше, есть несколько дополнительных типов литералов для кода в Playgrounds:

Название Предполагаемый тип по умолчанию Пример
Color NSColor / UIColor #colorLiteral(red: 1, green: 0, blue: 1, alpha: 1)
Image NSImage / UIImage #imageLiteral(resourceName: “icon”)
File URL #fileLiteral(resourceName: “articles.json”)

В Xcode или Swift Playgrounds на iPad эти литеральные выражения автоматически заменяются интерактивным элементом управления, который обеспечивает визуальное представление указанного цвета, изображения или файла.

// Code
#colorLiteral(red: 0.7477839589, green: 0.5598286986, blue: 0.4095913172, alpha: 1)

// Rendering
🏽

Пример color-literal-picker

Этот элемент управления также упрощает выбор новых значений: вместо ввода значений RGBA или путей к файлам вам предоставляется палитра цветов или селектор файлов.

В большинстве языков программирования есть литералы для логических значений, чисел и строк, а во многих есть литералы для массивов, словарей и регулярных выражений.

Сокращение этих основных строительных блоков упрощает чтение и написание кода.


Как работают литералы

Литералы похожи на слова: их значение может меняться в зависимости от окружающего контекста.

["h", "e", "l", "l", "o"] // Array<String>
["h" as Character, "e", "l", "l", "o"] // Array<Character>
["h", "e", "l", "l", "o"] as Set<Character>

В приведенном выше примере мы видим, что литерал массива, содержащий строковые литералы, по умолчанию инициализируется массивом строк. Однако, если мы явно приводим первый элемент массива к типу Character, литерал инициализируется как массив символов. В качестве альтернативы, мы могли бы преобразовать все выражение в Set<Character>, чтобы инициализировать набор символов.

Как это работает?

В Swift компилятор решает, как инициализировать литералы, просматривая все видимые типы, реализующие соответствующий протокол литеральных выражений (literal expression protocol).

Литерал Протокол
Integer ExpressibleByIntegerLiteral
Floating-Point ExpressibleByFloatLiteral
String ExpressibleByStringLiteral
Extended Grapheme Cluster ExpressibleByExtendedGraphemeClusterLiteral
Unicode Scalar ExpressibleByUnicodeScalarLiteral
Boolean ExpressibleByBooleanLiteral
Nil ExpressibleByNilLiteral
Array ExpressibleByArrayLiteral
Dictionary ExpressibleByDictionaryLiteral
  • ExpressibleByStringLiteral для строковых литералов,
  • ExpressibleByUnicodeScalarLiteral для строковых литералов, содержащих только один скаляр Unicode,
  • ExpressibleByExtendedGraphemeClusterLiteral для строковых литералов, содержащих только один расширенный кластер графем.

Чтобы соответствовать протоколу, тип должен реализовать требуемый инициализатор. Например, для протокола ExpressibleByIntegerLiteral требуется init(integerLiteral:).

Что действительно хорошо в этом подходе, так это то, что он позволяет вам добавлять инициализацию литералов для ваших собственных пользовательских типов.


Поддержка инициализации литералов для пользовательских типов

Поддержка инициализации с помощью литералов, когда это уместно, может значительно улучшить эргономику пользовательских типов, заставляя их чувствовать себя встроенными.

Например, если вы хотите поддерживать нечеткую логику (англ. fuzzy logic), в дополнение к стандартным булевым значениям, вы можете реализовать Fuzzy тип следующим образом:

struct Fuzzy: Equatable {
    var value: Double

    init(_ value: Double) {
        precondition(value >= 0.0 && value <= 1.0)
        self.value = value
    }
}

Нечеткое значение (Fuzzy value) представляет собой значение истинности, которое колеблется от полностью истинного до полностью ложного в числовом диапазоне от 0 до 1 (включительно). То есть значение 1 означает абсолютно верно, 0,8 — в основном верно, а 0,1 — в основном неверно.

Для более удобной работы со стандартной булевой логикой мы можем расширить Fuzzy, приняв протокол ExpressibleByBooleanLiteral:

extension Fuzzy: ExpressibleByBooleanLiteral {
    init(booleanLiteral value: Bool) {
        self.init(value ? 1.0 : 0.0)
    }
}

На практике, не так много ситуаций, в которых уместно инициализировать тип с помощью логических литералов. Поддержка строковых, целочисленных литералов и литералов с плавающей запятой встречается гораздо чаще.

Этот код не меняет значения по умолчанию true или false. Нам не нужно беспокоиться о нарушении существующего кода только потому, что мы ввели концепцию “полуправды” в наш код (“представление действительно выглядело анимированным… может быть?” - “view did appear animated… maybe?”). Единственные ситуации, в которых true или false инициализируют значение Fuzzy, — это когда компилятор может вывести тип как Fuzzy:

true is Bool // true
true is Fuzzy // false

(true as Fuzzy) is Fuzzy // true
(false as Fuzzy).value // 0.0

Поскольку Fuzzy инициализируется одним значением Double, разумно разрешить инициализацию значений также литералами с плавающей запятой. Трудно представить себе ситуацию, в которой тип поддерживает литералы с плавающей запятой, но не поддерживает целочисленные литералы, поэтому мы должны поступать так же (однако обратное неверно; существует множество типов, которые работают с целыми числами, но не с числами с плавающей точкой):

extension Fuzzy: ExpressibleByIntegerLiteral {
    init(integerLiteral value: Int) {
        self.init(Double(value))
    }
}

extension Fuzzy: ExpressibleByFloatLiteral {
    init(floatLiteral value: Double) {
        self.init(value)
    }
}

После принятия этих протоколов тип Fuzzy теперь выглядит и ощущается как полноправный член стандартной библиотеки Swift.

let completelyTrue: Fuzzy = true
let mostlyTrue: Fuzzy = 0.8
let mostlyFalse: Fuzzy = 0.1

(Теперь осталось только реализовать стандартные логические операторы!)

Если вы хотите оптимизировать удобство и производительность разработчика, вам следует подумать о реализации любых литеральных протоколов, подходящих для ваших пользовательских типов.

Пользовательская интерполяция

В качестве примера предположим, что мы хотим сохранить заданную строку, опционально применив к ней префикс и суффикс. В идеале мы хотели бы просто интерполировать эти значения, чтобы сформировать окончательную строку, например:

var textStorage = ""

func save(_ text: String, prefix: String?, suffix: String?) {
    let text = "\(prefix)\(text)\(suffix)"
    textStorage += text
}

Однако, поскольку и префикс, и суффикс являются необязательными (опциональными), простое использование их описания не даст желаемого результата — и компилятор даже выдаст нам предупреждение:

String interpolation produces a debug description for an optional value

Хотя у нас всегда есть возможность развернуть каждую из этих двух опций перед их интерполяцией, давайте посмотрим, как мы можем сделать обе эти вещи за один раз, используя пользовательскую интерполяцию. Мы начнем с расширения String.StringInterpolation новой перегрузкой appendInterpolation, которая принимает любое необязательное значение:

extension String.StringInterpolation {
    mutating func appendInterpolation<T>(unwrapping optional: T?) {
        let string = optional.map { "\($0)" } ?? ""
        appendLiteral(string)
    }
}

Приведенная выше метка параметра unwrapping: важна, поскольку именно ее мы будем использовать, чтобы указать компилятору использовать этот конкретный метод интерполяции — например:

var textStorage = ""

extension String.StringInterpolation {
    mutating func appendInterpolation<T>(unwrapping optional: T?) {
        let string = optional.map { "\($0)" } ?? ""
        appendLiteral(string)
    }
}
func save(_ text: String, prefix: String?, suffix: String?) {
    let text = "\(unwrapping: prefix)\(text)\(unwrapping: suffix)"
    textStorage += text
}
save("Text", prefix: "Prefix", suffix: nil)
print(textStorage)

// Output
// PrefixText

Хотя это всего лишь синтаксический сахар (syntactic sugar), вышеприведенное выглядит очень аккуратно! Однако это лишь малая часть того, что могут сделать пользовательские методы интерполяции строк. Они могут быть как универсальными, так и неуниверсальными, принимать любое количество аргументов, использовать значения по умолчанию и многое другое, что могут делать «обычные» методы.


Еще полезные ссылки

Также информацию по литералам можно получить на странице официальной документации.

Ссылки на официальную документацию: