Как убедиться (во время компиляции), что коллекция не была переупорядочена?

Допустим, у меня есть проиндексированная коллекция (здесь не имеет значения List или Map):

val zipped = List(1 -> "A", 3 -> "C", 8 -> "D")

С такими сложно работать (поскольку каждая операция, например map, должна иметь дело с index), поэтому я хочу передать в бизнес-обработчик следующее:

case class Indexed[T](seq: Seq[T], i: Seq[Int])

val unzipped = Indexed(List("A", "C", "D"), List(1,3,8))

handler(unzipped.seq)

Но мне нужно, чтобы пользователь был ограничен, чтобы делать только map, filter, collect, contains, forall, scanLeft и так далее. Но не flatMap (кроме filter подобных), sort, ++ и так далее. Так что любая биекция/сюръекция, но не инжекционная. В крайнем случае, пользователь может жить без filter/flatMap, поэтому наличие Functor, но не Monad может меня устроить - в любом случае приемлемое List[Option[T]] => List[T] из моих первоначальных требований не является полным Monad (M[M[T]] => M[T], T => M[T]).

toList, toSet приемлемы, но я также хочу быть уверен, что возвращаемая (из бизнес-обработчика) коллекция основана на исходной. Я могу сделать это, добавив зависящий от пути Tag (тип, зависящий от пути) к сигнатуре типа исходной коллекции и требующий коллекции с одинаковыми тегами в качестве возвращаемого типа (что можно обмануть только с asInstanceOf). Мои первые требования могут быть удовлетворены путем реализации моего собственного Traversable.

Итак, мое собственное «колесо» для решения этой проблемы - это просто обертка (с разрешенным только подмножеством операций + теги для обеспечения того, чтобы коллекция была одинаковой):

 trait NonReorderedListT {
     trait Tag
 }

 trait NonReorderedList[Tg <: NonReorderedListT#Tag, T] {
   type Tag = Tg 
   def map[U](f: T => U): NonReorderedList[Tag, U] //same tag
   //... other permitted operations should be here        
 }


 object NonReorderedList {

   class NonReorderedListImpl[Tg <: NonReorderedListT#Tag, T] private[NonReorderedList] (l: List[T]) extends NonReorderedList[Tg, T] { 
      def map[U](f: T => U) = new NonReorderedListImpl[Tag, U](l.map(f))
      //... other permitted operations should be here  
   }     


   def apply[T](l: List[T]) = {
     val tagC = new NonReorderedListT {} //container
     new NonReorderedListImpl[tagC.Tag, T](l)
   }
 }

Вот результаты Scala REPL:

defined trait NonReorderedListT
defined trait NonReorderedList
defined class NonReorderedListImpl
defined object NonReorderedList

scala>  val l = NonReorderedList(List(1,2,3))
warning: there was one feature warning; re-run with -feature for details
l: NonReorderedListImpl[tagC.Tag,Int] forSome { val tagC: NonReorderedListT } = NonReorderedListImpl@3620eab

scala> val res: NonReorderedList[l.Tag, Int] = l.map(_ + 1)
res: NonReorderedList[l.Tag,Int] = NonReorderedListImpl@34bddf43

scala>  val m = NonReorderedList(List(1,2,3))
warning: there was one feature warning; re-run with -feature for details
m: NonReorderedListImpl[tagC.Tag,Int] forSome { val tagC: NonReorderedListT } = NonReorderedListImpl@2d8c729f

scala> val res: NonReorderedList[l.Tag, Int] = m.map(_ + 1)
<console>:31: error: type mismatch;
 found   : NonReorderedListImpl[m.Tag,Int]
    (which expands to)  NonReorderedListImpl[tagC.Tag,Int]
 required: NonReorderedList[l.Tag,Int]
    (which expands to)  NonReorderedList[tagC.Tag,Int]
       val res: NonReorderedList[l.Tag, Int] = m.map(_ + 1)
                                                    ^

Тем не менее, это не должно быть такой уж редкой ситуацией, когда вам нужна такая коллекция, поэтому, возможно, у Scalaz уже есть какая-то реализация, что-то вроде NonEmptylist, но NonReorderedList.

Все это я делаю потому, что у меня есть одна уже заказанная коллекция (1 -> "А", 2 - "Б", 3 -> "В", 4 -> "Д", 5 -> "Е") в качестве ввод, который разбивается на (1 -> "A", 3 -> "C", 4 -> "D") и (2 -> "B", 5 -> "E"), они обрабатываются отдельно и затем слил обратно (первоначальный порядок должен быть сохранен). Заказ со сложным предикатом занимает некоторое время (поскольку он вызывает внешнюю службу), поэтому я не могу просто дважды переупорядочить коллекцию с ним - второе переупорядочивание должно быть основано на простом индексе.

P.S. Я не ищу менее типобезопасные альтернативы, так как они уже есть в моем проекте :). Моя цель - улучшить существующий (в моем проекте) код.


person dk14    schedule 04.08.2015    source источник
comment
Хорошо, я, вероятно, упускаю что-то ослепляюще очевидное... Если мне нужно, чтобы пользователь не возился с моим представлением данных, я скрываю его в классе и делаю общедоступными только разрешенные операции.   -  person jwvh    schedule 04.08.2015
comment
@jwvh, если вы прочитаете внимательнее - вы обнаружите, что это то, что я сделал + несколько других шагов, чтобы гарантировать, что пользователь не создаст другую коллекцию. Конечно, я мог бы закрыть конструктор, но на самом деле я тоже пользователь библиотеки и все еще хочу иметь возможность создавать коллекцию, но следить, чтобы она не была заменена другой. Поэтому я также сделал несколько шагов для этого (см. Выше), и это работает. Мой вопрос: это уже реализовано в scalaz или где-то еще, поскольку, вероятно, многие люди хотят, чтобы пользователи не переупорядочивались, поэтому я не хочу изобретать велосипед   -  person dk14    schedule 04.08.2015


Ответы (1)


Может быть, к этому можно подойти, используя Nat и HList из бесформенного? Идея состоит в том, чтобы явно смоделировать индекс элемента. (Я задал связанный с этим вопрос Достижение безопасного индексирования при компиляции с помощью Shapeless.) Например ,

import shapeless._
import Nat._

case class Entry[+N<:Nat](value: String, index: N)

val send: Entry[ _1] :: Entry[ _3] :: Entry[ _4] :: HNil= Entry("A", _1):: Entry("C", _3) :: Entry("D", _4) :: HNil
val keep= Entry("B", _2) :: Entry("E", _5)  :: HNil

Это обеспечит некоторую безопасность типов (хотя я не уверен насчет последствий для производительности).

person schrödingercöder    schedule 18.01.2018
comment
+1 Мне нравится идея использования Nat, а производительность самого HList для меня не имеет значения; однако одна практическая проблема заключается в том, что в моем случае было бы неплохо скрыть индексы и конкатенации — так просто, скажем, Entry("Z", _1) :: keep или написать функцию, полиморфную по N, например .map(_.copy(index = _3)). P.S. На самом деле, я ушел из этого проекта 3 года назад, и, судя по тому, что я слышал, мой NonReorderedList, казалось, выжил до сих пор, но мой бесформенный код (HLists и естественные преобразования) определенно нет :). - person dk14; 19.01.2018
comment
А, хорошо, я вижу. Спасибо за долгосрочную перспективу использования HLists — я понял, что действительно должен изолировать бесформенные биты в своем коде. - person schrödingercöder; 19.01.2018