Почему деконструкция кортежа по возвращаемому значению приведения вызывает ошибку? - PullRequest
0 голосов
/ 02 декабря 2018

Допустим, у меня есть массив целых чисел, и я хочу получить сумму всех четных чисел и сумму всех нечетных чисел.Например, для массива [1,2,3] сумма всех нечетных чисел равна 4, а сумма всех четных чисел равна 2.

Вот как я это делаю:

array.reduce((odd: 0, even: 0), { (result, int) in
    if int % 2 == 0 {
        return (result.odd, result.even + int)
    } else {
        return (result.odd + int, result.even)
    }
})

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

let (oddSum, evenSum) = a.reduce((odd: 0, even: 0), { (result, int) in
    if int % 2 == 0 {
        return (result.odd, result.even + int)
    } else {
        return (result.odd + int, result.even)
    }
})

Это выдает мне ошибку:

Значение типа кортежа '(Int, Int) 'не имеет члена' odd '

в операторах return.

Почему деконструкция кортежа приводит к тому, что универсальный тип выводится по-разному?Часть деконструкции должна просто сказать, что делать с результатом.Вызов метода должен быть интерпретирован сам по себе, а затем сопоставлен с шаблоном (oddSum, evenSum).

. Чтобы исправить это, я должен изменить первый параметр на (0, 0), что делает материал в замыкании оченьнечитаемым.Я должен обозначать нечетную сумму как result.0, а четную сумму как result.1.

1 Ответ

0 голосов
/ 20 декабря 2018

TL;DR

Такое поведение вызывает сожаление, но «работает как положено» из-за комбинации:


Почемудеконструкция кортежа приводит к тому, что универсальный тип выводится по-разному?Часть деконструкции должна просто сказать, что делать с результатом.Вызов метода должен быть интерпретирован сам по себе, а затем сопоставлен с шаблоном (evenSum, oddSum).

Средство проверки типов делает двунаправленный вывод типа, что означает, что используемый шаблон может влиять на то, как назначенное выражениеТип проверен.Например, рассмотрим:

func magic<T>() -> T { 
  fatalError() 
}

let x: Int = magic() // T == Int

Тип шаблона используется для вывода, что T равен Int.

Так что же происходит с шаблоном деконструкции кортежа?

let (x, y) = magic() // error: Generic parameter 'T' could not be inferred

Средство проверки типов создает две переменные типа для представления каждого элемента шаблона кортежа.Такие переменные типа используются внутри решателя ограничений, и каждая из них должна быть связана с типом Swift, прежде чем систему ограничений можно будет считать решенной.В системе ограничений шаблон let (x, y) имеет тип ($T0, $T1), где $T{N} - переменная типа.

Функция возвращает общий заполнитель T, поэтому система ограничений выводит, что T конвертируется в ($T0, $T1).Однако нет никакой дополнительной информации о том, с чем могут быть связаны $T0 и $T1, поэтому система дает сбой.

Хорошо, давайте дадим системе способ связать типы с этими переменными типов, добавив параметр вфункция.

func magic<T>(_ x: T) -> T {
  print(T.self)
  fatalError()
}

let labelledTuple: (x: Int, y: Int) = (x: 0, y: 0)
let (x, y) = magic(labelledTuple) // T == (Int, Int)

Теперь это компилируется, и мы можем видеть, что родовой заполнитель T выводится как (Int, Int).Как это произошло?

  • magic имеет тип (T) -> T.
  • Аргумент имеет тип (x: Int, y: Int).
  • Результирующий шаблон имеетвведите ($T0, $T1).

Здесь мы можем видеть, что система ограничений имеет две опции:

  • Привязать T к непомеченному типу кортежа($T0, $T1), заставляя аргумент типа (x: Int, y: Int) выполнить преобразование кортежа, удаляющее его метки.
  • Привязать T к помеченному типу кортежа (x: Int, y: Int), заставляя возвращаемое значение выполнитьпреобразование кортежа, которое удаляет его из меток так, чтобы его можно было преобразовать в ($T0, $T1).

(это скрывает тот факт, что универсальные заполнители открываются в переменные нового типа, но этолишняя деталь здесь)

Без какого-либо правила отдавать предпочтение одному варианту над другим, это неоднозначно.К счастью, система ограничений имеет правило предпочитать немаркированную версию типа кортежа при привязке типа.Поэтому система ограничений решает связать T с ($T0, $T1), и в этот момент и $T0, и $T1 могут быть связаны с Int, поскольку (x: Int, y: Int) необходимо преобразовать в ($T0, $T1).

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

func magic<T>(_ x: T) -> T {
  print(T.self)
  fatalError()
}

let labelledTuple: (x: Int, y: Int) = (x: 0, y: 0)
let tuple = magic(labelledTuple) // T == (x: Int, y: Int)

T теперь привязывается к (x: Int, y: Int).Зачем?Поскольку тип шаблона теперь просто имеет тип $T0.

  • Если T будет связан с $T0, то $T0 будет привязан к типу аргумента (x: Int, y: Int).
  • Если T связан с (x: Int, y: Int), то $T0 также будет связан с (x: Int, y: Int).

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

Итак, как это применимо?к вашему примеру?Ну, magic это просто reduce без дополнительного аргумента замыкания:

  public func reduce<Result>(
    _ initialResult: Result,
    _ nextPartialResult: (_ partialResult: Result, Element) throws -> Result
  ) rethrows -> Result

Когда вы делаете:

let (oddSum, evenSum) = a.reduce((odd: 0, even: 0), { (result, int) in
    if int % 2 == 0 {
        return (result.odd, result.even + int)
    } else {
        return (result.odd + int, result.even)
    }
})

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

  • Привязать Result к немаркированному типу кортежа ($T0, $T1), заставив аргумент типа (odd: Int, even: Int) выполнить преобразование кортежа, которое лишит его меток.
  • Привязать Result кпомеченный тип кортежа (odd: Int, even: Int), заставляющий возвращаемое значение выполнить преобразование кортежа, которое удаляет его из меток так, что оно может быть преобразовано в ($T0, $T1).

И из-за правила в пользуне помеченная форма кортежа, решатель ограничений выбирает привязать Result к ($T0, $T1), что разрешается к (Int, Int).Удаление декомпозиции кортежа работает, потому что вы больше не вводите тип ($T0, $T1) в систему ограничений - это означает, что Result может быть привязан только к (odd: Int, even: Int).

Хорошо, но давайте снова рассмотрим замыкание.Мы явно обращаемся к членам .odd и .even в кортеже, так почему же система ограничений не может выяснить, что привязка Result к (Int, Int) не является жизнеспособной?Это связано с тем, что закрытие нескольких операторов не участвует в выводе типа .Это означает, что тело замыкания решается независимо от вызова reduce, поэтому к тому времени, когда система ограничений поймет, что привязка (Int, Int) недействительна, будет слишком поздно.

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

let (oddSum, evenSum) = a.reduce((odd: 0, even: 0), { (result, int)  in
  return int % 2 == 0 ? (result.odd, result.even + int)
                      : (result.odd + int, result.even)
})

Или, если вы измените шаблон для использования соответствующих меток кортежей, Как указал Мартин , тип шаблона теперь (odd: $T0, even: $T1), что позволяет избежать введения немаркированной формы в систему ограничений:

let (odd: oddSum, even: evenSum) = a.reduce((odd: 0, even: 0), { (result, int) in
  if int % 2 == 0 {
    return (result.odd, result.even + int)
  } else {
    return (result.odd + int, result.even)
  }
})

Другой вариант,как указывает Алладиниан , это должно явно аннотировать тип параметра закрытия:

let (oddSum, evenSum) = a.reduce((odd: 0, even: 0), { (result: (odd: Int, even: Int), int) in
  if int % 2 == 0 {
    return (result.odd, result.even + int)
  } else {
    return (result.odd + int, result.even)
  }
})

Обратите внимание, однако, что в отличие от двух предыдущих примеров, это приводит к тому, что Result привязывается к (Int, Int) из-за шаблона, вводящего предпочтительный тип ($T0, $T1).Что позволяет этому примеру компилироваться, так это тот факт, что компилятор вставляет преобразование кортежа для переданного замыкания, которое повторно добавляет метки кортежа для его параметра.

...