object UsingIO: | |
trait Connection | |
trait User | |
case class IO[-R, +A](): | |
def *>[R1 <: R, B](that: => IO[R1, B]): IO[R1, B] = IO() | |
def foo(): IO[Connection, Unit] = IO() | |
def bar(): IO[User, Unit] = IO() | |
def explicit(): IO[Connection & User, Unit] = foo() *> bar() | |
// no compiler error | |
def inferred() = foo() *> bar() | |
// Both Scala compiler & IntelliJ properly infer the type | |
def inferredUsage(): IO[Connection & User, Unit] = inferred() | |
object UsingContextFN: | |
trait Connection | |
trait User | |
type Transactional[T] = Connection ?=> T | |
type Secure[T] = User ?=> T | |
def foo(): Transactional[Unit] = () | |
def bar(): Secure[Unit] = () | |
def explicit(): Transactional[Secure[Unit]] = | |
foo() | |
bar() | |
// IntelliJ infers the Unit type, Scala compiler reports an error | |
def inferred() = | |
foo() // compiler error | |
bar() |
@odersky Explicit types - definitely, as a "best practice" or maybe even a compiler-enforced requirement for public methods (but probably this would be part of sth like wart-remover than of the scala compiler). Still, how do we get these types? Having them inferred by the IDE, or suggested by the compiler is a very common scenario. It's good to have the types but it's much better if we don't have to write them down :) So I'd insist that for a "capability" feature (however implemented), inference is a must-have.
As for this particular implementation - using context functions - maybe you'd need some kind of marker trait specifying that the implicit requirement should propagate during type inference phase? Maybe that's complete nonsense, I'm not that familiar with the inferencing mechanism in Scala, apart from being a user :)
Good example! Yes, indeed, we need the explicit types here. Not sure what can be done about it. I guess even if inference would work, I would find
inferred
objectionable since I have no clue what its semantics is. So yes, explicit types are more verbose, but they also help understanding.