Подтвердить что ты не робот

Согласование образцов с бесформенным копродуктом

Можно ли использовать сопоставление образцов с бесформенными копродуктами?

import shapeless.{CNil, :+:}

type ListOrString = List[Int] :+: String :+: CNil

def f(a: ListOrString): Int = a match {
  case 0 :: second :: Nil => second
  case first :: Nil => first
  case Nil => -1
  case string: String => string.toInt
}

Это, конечно, не работает, поскольку a помещается как Coproduct.

Есть ли альтернативный способ использования копроизведений и поддерживать возможность сопоставления шаблонов?

4b9b3361

Ответ 1

В шаблоне можно использовать конструкторы Inl и Inr:

import shapeless.{ CNil, Inl, Inr, :+: }

type ListOrString = List[Int] :+: String :+: CNil

def f(a: ListOrString): Int = a match {
  case Inl(0 :: second :: Nil) => second
  case Inl(first :: Nil) => first
  case Inl(Nil) => -1
  case Inr(Inl(string)) => string.toInt
}

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

def f(a: ListOrString): Int = a match {
  case Inl(0 :: second :: Nil) => second
  case Inl(first :: Nil) => first
  case Inl(Nil) => -1
  case Inl(other) => other.sum
  case Inr(Inl(string)) => string.toInt
  case Inr(Inr(_)) => sys.error("Impossible")
}

Я также лично просто нахожу переход к соответствующим положениям в сопроводительном материале с Inr и Inl немного противоречивым.

В общем случае лучше сбрасывать копродукт с полиморфной функцией:

object losToInt extends shapeless.Poly1 {
  implicit val atList: Case.Aux[List[Int], Int] = at {
    case 0 :: second :: Nil => second
    case first :: Nil => first
    case Nil => -1
    case other => other.sum
  }

  implicit val atString: Case.Aux[String, Int] = at(_.toInt)
}

def f(a: ListOrString): Int = a.fold(losToInt)

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

Ответ 2

Я только что представил Shapeless запрос на перенос здесь, который может хорошо работать для ваших нужд. (Обратите внимание, что это просто запрос на тяну, и он может подвергнуться ревизии или быть отклонён... но не стесняйтесь брать машину и использовать ее в своем собственном коде, если вы сочтете это полезным.)

Из сообщения о фиксации:

[...] a Копродукт c типа Int: +: String: +: Boolean: +: CNil может складываться в двойную форму следующим образом:

val result = c.foldCases[Double]
               .atCase(i => math.sqrt(i))
               .atCase(s => s.length.toDouble)
               .atCase(b => if (b) 100.0 else -1.0)

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

Кроме того, в отличие от прямого складывания по Копродукту с рисунком сопоставление по инжекторам Inl и Inr, этот тип класса гарантирует, что результирующая складка является исчерпывающей. Также возможно частично fold a Coproduct (если дело обрабатывается в указанном порядке подписи типа Coproduct), что позволяет постепенно сбрасывайте Копродукт.

В вашем примере вы можете сделать это:

  def f(a: ListOrString): Int = a.foldCases[Int]
    .atCase(list => list match {
      case 0 :: second :: Nil => second
      case first :: Nil => first
      case Nil => -1
      case other => other.sum
    })
    .atCase(s => s.toInt)