简体   繁体   中英

Force type parameter to be a trait

I have the following code;

object main
{
    def main(args: Array[String]): Unit = 
    {
        trait E
        {
            def test(): Unit = println("test :)")
        }

        class B[T](val x: Int) 
        {
            def inc(): B[T] with T = new B[T](x + 1) with T
        }

        class A[T](f : B[T] with T => Unit)
        {
            def apply(b: B[T] with T) = f(b)
        }

        val b = new B[E](0) with E
        val a = new A[E](b => b.test())(b)
    }
}

However, the line def inc(): B[T] with T = new B[T](x + 1) with T does not compile, giving the error that "class type required but T found" and "T needs to be a trait to mix in". I understand why this is the case, but I can't find a way to work around it! I haven't found a way to constraint T to be a trait, which makes me fear that this approach won't work...

To give some more background on why I'm trying to achieve this (just incase anyone can offer a better solution) I have a Parsec[S, U, E, A] class, which is built up of functions that accept a State[S, U, E] with E object. The idea is that U is a user given state, A is the result of the parser, S a stream of tokens and E is some extension of the state (for instance, one might wish to create a Parsec[Stream[String, Char], Int, IndentationSensitive, List[Expr]] etc etc. The U = Int would be something the user wanted to count for instance, and that shouldn't need to interfere with the state required for the Indentation sensitivity (which is two Ints) which would be provided by mixing in the IndentationSensitive trait. Then if the user wanted some other functionality they can keep mixing in more traits for the parsers.

So, is there anyway I can constraint the type parameter T in the code so that I can mix it into a B , or if not, is there a better way of accomplishing what I need?

If it really isn't clear what I'm trying to accomplish then this question on CodeReview illustrates the situation (in a lot more detail). But Parsec[S <: Stream[_, _], U, A] is replaced by Parsec[S <: Stream[_, _], U, E, A] and the same for State and all the other parts.

To give some more background on why I'm trying to achieve this (just incase anyone can offer a better solution) I have a Parsec[S, U, E, A] class, which is built up of functions that accept a State[S, U, E] with E object. The idea is that U is a user given state, A is the result of the parser, S a stream of tokens and E is some extension of the state (for instance, one might wish to create a Parsec[Stream[String, Char], Int, IndentationSensitive, List[Expr]]

In this case I'd just add a val extension: E field to State and change the functions to accept State[S, U, E] . If you really want, you can add an implicit conversion from State[S, U, E] to E , so the functions can access E 's members directly, but I probably wouldn't do it myself.

I've managed to find a solution to the problem, It's not ideal, but it does deal with a couple of other problems with the System. Namely, when we create a new State[S, U, E](input, pos, state) with E what is meant to happen with the variables added with E . They get lost and that's a killer.

Let's define a new type type StateBuilder[S <: Stream[_, _], U, E] = (Option[State[S, U, E] with E], S, SourcePos, U) => State[S, U, E] with E . This is a function that can construct a new State of the type we want, given a possible previous state and some new values for the state's "normal" parameters.

Now we can redefine the State as;

case class State[S <: Stream[_, _], U, E](stateInput: S, statePos: SourcePos, stateUser: U, build: StateBuilder[S, U, E])

And now we just need some of those StateBuilder[S, U, E] , which will get passed between states, but we need to feed it in when we create the initial state. That's fine, but it means the user needs to understand what they are (which is a little disadvantageous). An example builder for no extensions;

trait Default
object Default
{
    def build[S <: Stream[_, _], U](s: Option[State[S, U, Default] with Default], ts: S, pos: SourcePos, u: U): State[S, U, Default] with Default =
    {
        new State[S, U, Default](ts, pos, u, build) with Default
    }
}

and a more complicated one might be;

trait IndentationSensitive
{
    var stateLevel: Int = 0
    var stateRequiredIndent: Int = 0
}
object IndentationSensitive
{    
    def build[S <: Stream[_, _], U](s: Option[State[S, U, IndentationSensitive] with IndentationSensitive], ts: S, pos: SourcePos, u: U): State[S, U, IndentationSensitive] with IndentationSensitive =
    {
        val s_ = new State[S, U, IndentationSensitive](ts, pos, u, build) with IndentationSensitive
        s match
        {
            case Some(s) => 
                s_.stateLevel = s.stateLevel
                s_.stateRequiredIndent = s.stateRequiredIndent
            case None => 
                s_.stateLevel = 0
                s_.stateRequiredIndent = 0
        }
        s_
    }
}

In order to compose extensions, the user would need to hand construct the builder function, but that's not unreasonable for anyone to be able to work out how to do. It would be nice to be able to automatically build these for each type, but that's a different question.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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