How to make sure (in compile-time) that collection

2019-08-08 14:42发布

问题:

Let's say I have indexed collection (List or Map doesn't matter here):

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

It's hard to work with such (as every operation, like map, has to deal with index), so what I want to pass into business handler is:

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

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

handler(unzipped.seq)

But I need user to be restricted, to do only map, filter, collect, contains, forall, scanLeft and so on. But not flatMap (except filter-like), sort, ++ and so forth. So any bijection/surjection, but not injection-like. In a pinch, user can live without filter/flatMap, so having Functor, but not Monad could be fine for me - anyway acceptable List[Option[T]] => List[T] from my original requirements isn't complete Monad (M[M[T]] => M[T], T => M[T]).

toList, toSet are acceptable, but I also want to be sure that returning (from business handler) collection is based on original one. I can do this by adding path-dependent Tag (path-dependent type) to the type signature of original collection and require same-tagged collection as a return type (which can be cheated only with asInstanceOf). My first requirements can be satisfied by implementing my own Traversable.

So my own "wheel" to solve this is just a wrapper (with only subset of operations permitted + tags for making sure that collection is same):

 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)
   }
 }

Here is the results from 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)
                                                    ^

However, it shouldn't be so rare situation when you need such collection, so maybe Scalaz already has some implementation, something like NonEmptylist, but NonReorderedList.

I'm doing all this because I have one already ordered collection (1 -> "A", 2 - "B", 3 -> "C", 4 -> "D", 5 -> "E") as an input, which splits into (1 -> "A", 3 -> "C", 4 -> "D") and (2 -> "B", 5 -> "E"), they're processed separately and then merged back (original order should be preserved). Ordering with complex predicate takes some time (as it calls to the external service), so I can't just reorder collection twice with it - second reordering should be based on simple index.

P.S. I'm not looking for less type-safe alternatives as I already have such in my project :). My goal is to improve existing (in my project) code.

回答1:

Maybe this could be approached by using Nat and HList from shapeless? The idea is to model an element's index explicitly. (I asked a related question Achieving compile safe indexing with Shapeless.) For example,

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

This would provide some type safety (although I'm not sure about the performance implications.)