Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the meaning of a type declaration without definition in an object?

Tags:

Scala allows to define types using the type keyword, which usually have slightly different meaning and purpose depending on when they are declared.

If you use type inside an object or a package object, you'd define a type alias, i.e. a shorter/clearer name for another type:

package object whatever {
  type IntPredicate = Int => Boolean

  def checkZero(p: IntPredicate): Boolean = p(0)
}

Types declared in classes/traits are usually intended to be overridden in subclasses/subtraits, and are also eventually resolved to a concrete type:

trait FixtureSpec {
  type FixtureType
  def initFixture(f: FixtureType) = ...
}

trait SomeSpec extends FixtureSpec {
  override type FixtureType = String

  def test(): Unit = {
    initFixture("hello")
    ...
  }
}

There are other uses for abstract type declarations, but anyway they eventually are resolved to some concrete types.

However, there is also an option to declare an abstract type (i.e. without actual definition) inside an object:

object Example {
  type X
}

And this compiles, as opposed to e.g. abstract methods:

object Example {
  def method: String  // compilation error
}

Because objects cannot be extended, they can never be resolved to concrete types.

I assumed that such type definitions could be conveniently used as phantom types. For example (using Shapeless' tagged types):

import shapeless.tag.@@
import shapeless.tag

type ++>[-F, +T]

trait Converter

val intStringConverter: Converter @@ (String ++> Int) = tag[String ++> Int](...)

However, it seems that the way the type system treats these types is different from regular types, which causes the above usage of "abstract" types to fail in certain scenarios.

In particular, when looking for implicit parameters, Scala eventually looks into implicit scope associated with "associated" types, i.e. types which are present in the type signature of the implicit parameters. However, it seems that there is some limitation on nesting of these associated types when "abstract" types are used. Consider this example setup:

import shapeless.tag.@@

trait Converter

type ++>[-F, +T]

case class DomainType()

object DomainType {
  implicit val converter0: Converter @@ DomainType = null
  implicit val converter1: Converter @@ Seq[DomainType] = null
  implicit val converter2: Converter @@ (Seq[String] ++> Seq[DomainType]) = null

}

// compiles
implicitly[Converter @@ DomainType]
// compiles
implicitly[Converter @@ Seq[DomainType]]
// fails!
implicitly[Converter @@ (Seq[String] ++> Seq[DomainType])]

Here, the first two implicit resolutions compile just fine, while the last one fails with an error about a missing implicit. If I define the implicit in the same scope as the implicitly call, it then compiles:

implicit val converter2: Converter @@ (Seq[String] ++> Seq[DomainType]) = null
// compiles
implicitly[Converter @@ (Seq[String] ++> Seq[DomainType])]

However, if I change the ++> definition to be a trait rather than type:

trait ++>[-F, +T]

then all implicitly calls above compile just fine.

Therefore, my question is, what exactly is the purpose of such type declarations? What problems they are intended to solve, and why are they not prohibited, like other kinds of abstract members in objects?

like image 534
Vladimir Matveev Avatar asked Jan 10 '19 21:01

Vladimir Matveev


People also ask

What is the difference between a declaration and a definition?

Difference Between Definition and DeclarationDefinition allocates memory to an entity. A declaration does not allocate memory to the entities. Once you define an entity, you cannot keep repeating the definition process again and again.

What is the purpose of type declaration?

Purpose. A type declaration statement specifies the type, length, and attributes of objects and functions. You can assign initial values to objects. A declaration type specification (declaration_type_spec) is used in a nonexecutable statement.

What is the difference between variable declaration and definition?

Declaration means that variable is only declared and memory is allocated, but no value is set. However, definition means the variables has been initialized. The same works for variables, arrays, collections, etc.

What is data declaration definition?

A declaration establishes the names and characteristics of data objects used in a program. A definition allocates storage for data objects, and associates an identifier with that object. When you declare or define a type, no storage is allocated.


1 Answers

For a method (or value) there are only 2 options: either it has body (and then it is "concrete") or it doesn't (then it is "abstract"). A type X is always some type interval X >: LowerBound <: UpperBound (and we call it concrete if LowerBound = UpperBound or completely abstract if LowerBound = Nothing, UpperBound = Any but there is variety of cases between those). So if we'd like to forbid abstract types in objects we should always have way to check that types LowerBound and UpperBound are equal. But they can be defined in some complex way and generally such check can be not so easy:

object Example {
  type X >: N#Add[N] <: N#Mult[Two] // Do we expect that compiler proves n+n=n*2?
}
like image 146
Dmytro Mitin Avatar answered Oct 12 '22 13:10

Dmytro Mitin