简体   繁体   English

在编译时(Scala)中使用可变参数时是否可以控制函数中参数的数量?

[英]Is it possible to control the number of argument in a function when using varargs in compile time(Scala)?

Let us suppose, we have to create the OddList[+T] which contains only odd number of elements.假设,我们必须创建仅包含奇数个元素的OddList[+T] Now can we do something like this现在我们可以做这样的事情吗

OddList(1,2) //Works fine
OddList(1,2,3) //Compilation error

if there is no condition of odd/even then we would simply do as below如果没有奇数/偶数的条件,那么我们只需执行以下操作

object OddList{
 def apply[T](eles: T*) = ...
}

But can we control number of arguments that could be passed?但是我们可以控制可以传递的参数数量吗?

I'm not aware of any way of limiting this using some kind of built-in mechanism.我不知道使用某种内置机制来限制这种情况的任何方式。 A macro can be a possible solution.宏可能是一种可能的解决方案。 If you want to keep it simple, the only thing I can think of is that of asking for pairs:如果您想保持简单,我唯一能想到的就是要求配对:

object OddList {
 def apply[T](elems: (T, T)*) = ???
}

I also think that there might be some confusion with regards to naming, since odd numbers are 2n+1 and even ones are 2n (because you can split them evenly by two -- see Wikipedia ).我还认为在命名方面可能会有些混淆,因为奇数是2n+1 ,偶数是2n (因为你可以将它们平均分成两份——参见Wikipedia )。 As such, you can define the following:因此,您可以定义以下内容:

object EvenList {
 def apply[T](elems: (T, T)*) = ???
}

object OddList {
 def apply[T](elem: T, elems: (T, T)*) = ???
}

Probably not the most elegant solution, but definitely simple and easy to implement.可能不是最优雅的解决方案,但绝对简单且易于实施。

If this approach works for you, you can create quite flexible smart constructors as follows:如果这种方法适合您,您可以创建非常灵活的智能构造函数,如下所示:

object Even {
  def apply[CC[_], A](
      elems: (A, A)*
  )(implicit factory: collection.Factory[A, CC[A]]): CC[A] = {
    val builder = factory.newBuilder
    for ((first, second) <- elems) {
      builder += first
      builder += second
    }
    builder.result()
  }
}

object Odd {
  def apply[CC[_], A](elem: A, elems: (A, A)*)(implicit
      factory: collection.Factory[A, CC[A]]
  ): CC[A] = {
    val builder = factory.newBuilder
    builder += elem
    for ((first, second) <- elems) {
      builder += first
      builder += second
    }
    builder.result()
  }
}

assert(Even[Set, Int]((1, 2), (3, 4)) == Set(1, 2, 3, 4))
assert(Odd[List, String]("1", ("2", "3")) == List("1", "2", "3"))

You can play around with this code here on Scastie .您可以在 Scastie 上使用此代码。

As a further improvement, I believe you can also create types to wrap the two possible returned items so that you can reason about collection size parity at compile time (if that is a requirement), but this is not covered by this example.作为进一步的改进,我相信您还可以创建类型来包装两个可能的返回项,以便您可以在编译时推断集合大小奇偶校验(如果这是必需的),但这不在本示例中。

One question first off: Are you including Nil as an element?首先提出一个问题:您是否将Nil作为一个元素包括在内?

Because (1 :: 2 :: Nil).size is 2 /even, your other example is 3 /odd因为(1 :: 2 :: Nil).size2 /even,你的另一个例子是3 /odd

At compile time , shapeless has Sized (see this answer: Scala, enforce length of Array/Collection parameter ), but that seems to be for static, concrete values.compile timeshapeless具有Sized (请参阅此答案: Scala,forced length of Array/Collection parameter ),但这似乎是针对静态的、具体的值。

At runtime you can throw an IllegalArgumentException when the constructor is called运行时,您可以在调用构造函数时抛出IllegalArgumentException

    case class OddList[T]( elements: T* ) {
        private val numElements: Int = elements.size
        private val hasOddNumElements: Boolean = numElements % 2 != 0
        require(hasOddNumElements, s"There are an even [$numElements] number of elements!")
    }

    OddList( 1, 2 ) // Runtime exception
    OddList( 1, 2, 3 ) // Works fine

Thanks @Dylan and everyone who asked to explore to macros way.感谢@Dylan 和所有要求探索宏方式的人。 I don't know if there are better ways, I would love to know but macros way works for me, at least for now.我不知道是否有更好的方法,我很想知道,但宏方法对我有用,至少现在是这样。 I tried the below code and it worked.我尝试了下面的代码并且它有效。

trait OddArgHolder[T]{
  val args: Seq[T]
}
case class OddArgs[T](args: Seq[T]) extends OddArgHolder[T]

object OddArgHolder{
  import scala.language.experimental.macros
  import scala.reflect.macros.whitebox.Context

  def applyMacro[T](c: Context)(method: c.Expr[T]*): c.Expr[OddArgHolder[T]] = {
    import c.universe._

    if(method.size % 2 == 0){
      c.abort(c.enclosingPosition, "Only odd number of arguments allowed")
    } else {
      val met = method.map(m => c.typecheck(m.tree))
      c.Expr(c.parse(s"OddArgs( $met )"))
    }

  }

  def apply[T](method: T*): OddArgHolder[T] = macro applyMacro[T]

}


  println(OddArgHolder(1,2))
 //Compiletime output: 
 //Only odd number of arguments allowed
 //  println(OddArgHolder(1,2))

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM