Общая функция «карта» для кортежей Scala?

Я хотел бы отобразить элементы кортежа Scala (или тройки,...), используя одну функцию, возвращающую тип R. Результатом должен быть кортеж (или тройка,...) с элементами типа R.

Хорошо, если элементы кортежа относятся к одному типу, сопоставление не проблема:

scala> implicit def t2mapper[A](t: (A,A)) = new { def map[R](f: A => R) = (f(t._1),f(t._2)) }
t2mapper: [A](t: (A, A))java.lang.Object{def map[R](f: (A) => R): (R, R)}

scala> (1,2) map (_ + 1)
res0: (Int, Int) = (2,3)

Но можно ли также сделать это решение универсальным, т. е. отображать кортежи, содержащие элементы разных типов, одинаковым образом?

Пример:

class Super(i: Int)
object Sub1 extends Super(1)
object Sub2 extends Super(2)

(Sub1, Sub2) map (_.i)

должен вернуться

(1,2): (Int, Int)

Но я не смог найти решения, чтобы функция отображения определяла супертип Sub1 и Sub2. Я пытался использовать границы типов, но моя идея не удалась:

scala> implicit def t2mapper[A,B](t: (A,B)) = new { def map[X >: A, X >: B, R](f: X => R) = (f(t._1),f(t._2)) }
<console>:8: error: X is already defined as type X
       implicit def t2mapper[A,B](t: (A,B)) = new { def map[X >: A, X >: B, R](f: X => R) = (f(t._1),f(t._2)) }
                                                                    ^
<console>:8: error: type mismatch;
 found   : A
 required: X
 Note: implicit method t2mapper is not applicable here because it comes after the application point and it lacks an explicit result type
       implicit def t2mapper[A,B](t: (A,B)) = new { def map[X >: A, X >: B, R](f: X => R) = (f(t._1),f(t._2)) }

Здесь X >: B кажется переопределяет X >: A. Разве Scala не поддерживает границы типов в отношении нескольких типов? Если да, то почему бы и нет?


person Stefan Endrullis    schedule 26.10.2010    source источник
comment
Это похоже на случай для HLists. См., например, apocalisp.wordpress.com/2010/10/15/   -  person mkneissl    schedule 27.10.2010


Ответы (5)


Я думаю, это то, что вы ищете:

implicit def t2mapper[X, A <: X, B <: X](t: (A,B)) = new {
  def map[R](f: X => R) = (f(t._1), f(t._2))
}

scala> (Sub1, Sub2) map (_.i)                             
res6: (Int, Int) = (1,2)

Более «функциональный» способ сделать это с двумя отдельными функциями:

implicit def t2mapper[A, B](t: (A, B)) = new { 
  def map[R](f: A => R, g: B => R) = (f(t._1), g(t._2)) 
}       

scala> (1, "hello") map (_ + 1, _.length)                                         
res1: (Int, Int) = (2,5)
person Tom Crockett    schedule 26.10.2010

Я не гений типа scala, но, возможно, это работает:

implicit def t2mapper[X, A<:X, B<:X](t: (A,B)) = new { def map[A, B, R](f: X => R) = (f(t._1),f(t._2)) }
person Debilski    schedule 26.10.2010

Более глубокий вопрос здесь: «Почему вы используете для этого Tuple?»

Кортежи неоднородны по своей структуре и могут содержать самые разные типы. Если вам нужна коллекция связанных вещей, вам следует использовать ...drum roll... коллекцию!

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

person Kevin Wright    schedule 26.10.2010
comment
Для моих целей коллекции слишком гибки из-за переменного количества элементов. Этот фрагмент кода является частью внутреннего DSL, написанного на Scala, где я хочу убедиться, что во время компиляции пользователь указывает функции, обрабатывающие ровно 2 аргумента (кортежа). Кроме того, если бы я использовал коллекции вместо кортежей в своих определениях закрытия, они стали бы более подробными, поскольку я больше не могу использовать сопоставление с образцом (случай (a, b)). - person Stefan Endrullis; 26.10.2010
comment
Что касается сопоставления с образцом: вы можете сопоставлять коллекции: List(1,2,3) match { case List(a,b,c) =› ... } . - person mkneissl; 27.10.2010
comment
@mkneissl: Верно, но тогда вы теряете безопасность типов из-за количества элементов: List(1,2,3,4) match { case List(a,b,c) => ... } не работает во время выполнения. - person Mechanical snail; 26.06.2012

Для случая, когда две применяемые функции не совпадают

scala> Some((1, "hello")).map((((_: Int) + 1 -> (_: String).length)).tupled).get
res112: (Int, Int) = (2,5)

Основная причина, по которой я предоставил этот ответ, заключается в том, что он работает для списков кортежей (просто измените Some на List и удалите get).

person samthebest    schedule 30.11.2013

Этого легко добиться с помощью shapeless, хотя перед созданием карты вам придется определить функцию сопоставления. :

object fun extends Poly1 {
  implicit def value[S <: Super] = at[S](_.i) 
}

(Sub1, Sub2) map fun // typed as (Int, Int), and indeed equal to (1, 2)

(Мне пришлось добавить val перед i в определении Super, таким образом: class Super(val i: Int), чтобы к нему можно было получить доступ извне)

person Alex Archambault    schedule 20.07.2014