Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Eliminating identity wrapper types from Scala APIs

Tags:

scala

Suppose I am trying to "abstract over execution":

import scala.language.higherKinds

class Operator[W[_]]( f : Int => W[Int] ) {
  def operate( i : Int ) : W[Int] = f(i)
}

Now I can define an Operator[Future] or Operator[Task] etc. For example...

import scala.concurrent.{ExecutionContext,Future}
def futureSquared( i : Int ) = Future( i * i )( ExecutionContext.global )

In REPL-style...

scala> val fop = new Operator( futureSquared )
fop: Operator[scala.concurrent.Future] = Operator@105c54cb

scala> fop.operate(4)
res0: scala.concurrent.Future[Int] = Future(<not completed>)

scala> res0
res1: scala.concurrent.Future[Int] = Future(Success(16))

Hooray!

But I also might want a straightforward synchronous version, so I define somewhere

type Identity[T] = T

And I can define a synchronous operator...

scala> def square( i : Int ) : Identity[Int] = i * i
square: (i: Int)Identity[Int]

scala> val sop = new Operator( square )
sop: Operator[Identity] = Operator@18f2960b

scala> sop.operate(9)
res2: Identity[Int] = 81

Sweet.

But, it's awkward that the inferred type of the result is Identity[Int], rather than the simpler, straightforward Int. Of course the two types are really the same, and so are identical in every way. But I'd like clients of my library who don't know anything about this abstracting-over-execution stuff not to be confused.

I could write a wrapper by hand...

class SimpleOperator( inner : Operator[Identity] ) extends Operator[Identity]( inner.operate ) {
  override def operate( i : Int ) : Int = super.operate(i)
}

which does work...

scala> val simple = new SimpleOperator( sop )
simple: SimpleOperator = SimpleOperator@345c744e

scala> simple.operate(7)
res3: Int = 49

But this feels very boiler-platey, especially if my abstracted-over-execution class has lots of methods rather than just one. And I'd have to remember to keep the wrapper in sync as the generic class evolves.

Is there some more generic, maintainable way to get a version of Operator[Identity] that makes the containing type disappear from the type inference and API docs?

like image 260
Steve Waldman Avatar asked Jun 15 '19 11:06

Steve Waldman


1 Answers

This more of long comment rather than an answer...

But, it's awkward that the inferred type of the result is Identity[Int], rather than the simpler, straightforward Int. Of course the two types apparent types are really the same, and so are identical in every way. But I'd like clients of my library who don't know anything about this abstracting-over-execution stuff not to be confused.

This sounds like you want to convert Indentity[T] back to T... Have you considered type ascription?

scala>def f[T](t: T): Identity[T] = t

scala>f(3)
// res11: Identity[Int] = 3

scala>f(3): Int
// res12: Int = 3

// So in your case
scala>sop.operate(9): Int
// res18: Int = 81
like image 180
Valy Dia Avatar answered Oct 17 '22 07:10

Valy Dia