Search code examples
scalagenericstype-conversiongeneric-programming

Scala Multiple type parameter that are defined later


I am trying to port a generic library for Haskell to Scala. However, I am currently not really happy with how I solved the generic Crush function in Scala.

I defined to following treats to handle Crush functions.

trait FRep[G[_],F[_]]{
    def frep[A](g1 : G[A]) : G[F[A]]
  } 

trait Crush[B,A]{ 
    def selCrush : Assoc => A => B => B
}

Next, I wanted to define the crush function, but here I ran into problems. The problem is that I need this FRep trait to represent the Crush function, but the G(Generic) in frep only allows 1 parameter. I solved this using lambda types, but I still have some problems to define a function. This is the approach I wanted to work:

def crush[B,A,F[_]](asc : Assoc)(f : A => B => B)(z : B)(x : F[A])(implicit rep : FRep[({type AB[A] = Crush[B,A]})#AB,F]): B = {
    def fCrush = new Crush[B,A]{ 
      override def selCrush= _ => f
    }
    return(rep frep(fCrush).selCrush(asc)(x)(z))
}

This obviously gave an error, because the A parameter in the crush function is not the same as the A lambda type in the implicit rep variable, which needs to be same in order to use the Crush function. This is the error I received:

<pastie>:677: error: type mismatch;
 found   : x.type (with underlying type F[A])
 required: A
       return(rep frep(fCrush).selCrush(asc)(x)(z))

So, the solution I came up with is to split the crush function into more parts, such that I could use the same A for the crush function. This is the current solution that compiles:

class CrushFunction[B,F[_]](asc : Assoc)(z : B)(implicit rep : FRep[({type AB[A] = Crush[B,A]})#AB,F]){
    def crush[A](f : A => B => B)(x : F[A]) : B = {
        val crushVal = new Crush[B,A]{
            override def selCrush:  Assoc => A => B => B = _ => f
        }
        return(rep.frep(crushVal).selCrush(asc)(x)(z))
    }
}

So, my question is: Is there a nicer way to solve this?


Solution

  • Actually the only thing wrong with your first solution is that you left out the dot between rep and frep. I would also advise you not to use return explicitly, and not to shadow names of type parameters (A):

    def crush[B,A,F[_]](asc : Assoc)(f : A => B => B)(z : B)(x : F[A])(implicit rep : FRep[({type AB[X] = Crush[B,X]})#AB,F]): B = {
        def fCrush = new Crush[B,A]{ 
          override def selCrush: Assoc => A => B => B = _ => f
        }
        rep.frep(fCrush).selCrush(asc)(x)(z)
    }
    

    Now you may ask yourself: why this error message if all that's wrong is a missing dot? Well, scala supports infix notation. It parses a b c d e as a.b(c).d(e). But you wrote something like this: a b(c).d(e). This confuses the parser. You can see what happens when you append //print to your code in the REPL (version 2.11.8 or higher I think) and press the TAB key.

    I filtered out all the cruft for you. rep frep(fCrush).selCrush(asc)(x)(z) gets parsed as:

    rep.frep[A](fCrush.selCrush(asc)(x)(z))
    

    In that expression x is indeed required to be of type A instead of F[A].

    Honestly: to me it seems a bit like a bug that scalac managed to parse your dotless code, completely ignoring a parenthesis in the process. Let's call it an unfortunate syntax quirk :-)