Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a :kind command to the REPL #2340

Merged
merged 1 commit into from May 17, 2013
Merged

Added a :kind command to the REPL #2340

merged 1 commit into from May 17, 2013

Conversation

folone
Copy link
Contributor

@folone folone commented Mar 31, 2013

Description

Because Scala supports working with higher kinded types, we might want to be able to inspects kinds as well as types. This pull request adds a simple :kind command, implemented by @eed3si9n in his "Introduction to Scalaz: day 4" post, to the Scala REPL. Here is an example output:

scala> :kind scala.Option
Option's kind is * -> *

scala> :k scalaz.Unapply
Unapply's kind is ((* -> *) -> *) -> * -> *

scala> import scalaz._
import scalaz._

scala> :k Monad // Finds locally imported types.
Monad's kind is (* -> *) -> *
This is a type constructor that takes type constructor(s): a higher-kinded type.

scala> :k Nonexisting
<console>:14: error: not found: value Nonexisting
              Nonexisting
              ^

scala> class Foo
defined class Foo

scala> new Foo { def empty = true }
res0: Foo{def empty: Boolean} = $anon$1@786aceba

scala> :k res0
Foo{def empty: Boolean}'s kind is *

Future improvements

As pointed out by @retronym here, variance and type bounds are also part of the kind and we might want to include them in command's output.

Acknowledgements

The function for :kind feature is originally implemented by @eed3si9n
I got a lot of help from @xeno-by and @retronym while working on this feature, thank you for that.

Happy Easter everyone!

@folone
Copy link
Contributor Author

folone commented Mar 31, 2013

I probably have to somehow assign this pull request to @paulp, but it looks like I don't have permissions to do that.

@retronym
Copy link
Member

Two things to address before we can consider this:

  1. Please add test cases. See test/files/run/constant-type.scala as an example. Use ./test/partest --update-check --show-log to create the .check file with expected output.
  2. I'd like to find a Scala syntax for expressing the kinds. What's wrong with: :k Option \n Option[A] ?

Oh, and happy Easter, too! 🐰

@eed3si9n
Copy link
Member

@retronym

What's wrong with: :k Option \n Option[A] ?

Using Scala syntax does add the ability to show variance and bounds and we might even get it for free from simply displaying the signature from Type. On the other hand, this would return kinds for Array as Array[A], which is different kind from Option[A], but they are both * -> *.

Anonymizing them both as F[A] I think is better. Not sure if there are conventions on the letters beyond the first two, but we could do something like: A, F, X, Y, Z, O, P, Q, ...; and number them as F[A1, A2].

@folone
Copy link
Contributor Author

folone commented Mar 31, 2013

@retronym @eed3si9n I'm also not sure if things like Unapply[TC[_[_]], MA] are more readable/understandable than ((* -> *) -> *) -> * -> *

@eed3si9n
Copy link
Member

@folone For the majority of Scala users (assuming without any Haskell background) F[A1, A2] and X[F[A]] are probably easier to understand than the curried notation of * -> * -> * and (* -> *) -> *.

Unapply[TC[_[_]], MA] in my proposed alphabet soup would be Y[X[F[A1]], A2], which is not bad once you mentally map X to be a functor typeclass.

@som-snytt
Copy link
Contributor

For the test, I was going to suggest overriding toString; but I don't know if that was an extraneous part of the test.

@seanparsons
Copy link

I agree it would be preferable to have a form that is something that we can use in Scala code.

@folone
Copy link
Contributor Author

folone commented Mar 31, 2013

PLS REBUILD ALL

@scala-jenkins
Copy link

(kitty-note-to-self: ignore 15697719)
🐱 Roger! Rebuilding pr-rangepos-per-commit, pr-checkin-per-commit for 7855b08f, c9314fb8, c4245ae6. 🚨

@eed3si9n
Copy link
Member

eed3si9n commented Apr 1, 2013

Created topic/kind2 branch under folone/scala: folone/scala@ca9edc1
This implements Scala syntax and variance. Not sure if I can get the bounds staying generic at API level.

scala> :k Int
Int's kind is A

scala> :k -v Either
Either's kind is F[+A1, +A2]
This is a type constructor: a 1st-order-kinded type.

scala> :k -v scalaz.Monad
Monad's kind is X[F[A]]
This is a type constructor that takes type constructor(s): a higher-kinded type.

scala> :k scalaz.Unapply
Unapply's kind is Y[X[F[A1]], A2]

@retronym
Copy link
Member

retronym commented Apr 1, 2013

I'm not really sure what makes the most sense. Just to add a bit more confusion:

scala> typeOf[Either[Any, Any]].typeConstructor.etaExpand
res10: $r.intp.global.Type = [+A, +B]Either[A,B]

scala> trait Monad[F[_]]
warning: there were 1 feature warning(s); re-run with -feature for details
defined trait Monad

scala> typeOf[Monad[Any]].typeConstructor.etaExpand
res11: $r.intp.global.Type = [F[_]]Monad[F]

scala> trait Unapply[TC[_[_]], MA]
warning: there were 2 feature warning(s); re-run with -feature for details
defined trait Unapply

scala> typeOf[Unapply[Any, Any]].typeConstructor.etaExpand
res12: $r.intp.global.Type = [TC[_[_]], MA]Unapply[TC,MA]

@paulp
Copy link
Contributor

paulp commented Apr 1, 2013

For any which go deeper than a single type constructor, how about something like this - I probably blew the logic since I'm jumping on a plane, but wave your hands and make it right, you get the gist.

scala> :k Unapply
Unapply[TC[_2], MA]
  where _1 =    [A1] => F[A1]   // (* -> *)
  where _2 = [_2[F]] => X[_2]   // (* -> *) -> *)

I like this less than what I pictured when I started it, but I think some sort of decomposition is unavoidable.

@eed3si9n
Copy link
Member

eed3si9n commented Apr 1, 2013

Unapply show down.

standard notation

((* -> *) -> *) -> * -> *

Eugene's Scala notation

Y[X[F[A1]], A2]

REPL's current notation for eta-expanded type constructor

[TC[_[_]], MA]Unapply[TC,MA]

Paul's notation

I'm interpreting Paul's suggestion to be "Use function-like syntax to express type constructor":

[_1, MA] => Unapply[_1, MA]
  where _1 = [_2] => X[_2]
  where _2 = [A1] => F[A1]

why mine is better

If you put trait in front, it compiles. Or better way of looking at it is, if the given expression appeared in a larger type, the entire type expression parses correctly as type variables. In other words,

scala> trait Foo[Y[X[F[A1]], A2]]
warning: there were 3 feature warning(s); re-run with -feature for details
defined trait Foo

Using _ means that you're not interested in using the type variable later. I don't think TC[_[_]] makes it any better than X[F[A1]].

The naming issue. Let's step back and ask the type of 1:

scala> :t 1
Int

Yea, REPL gives Int. Now what's the kind of Int?

scala> :k Int
Int's kind is A

A or in standard notation * can be the only answer here, not Int, because that's like answering 1's type is 1. Seemingly-intuitive, but not right, because it doesn't match 2's type. Similarly, Int's kind should match up that of Double's; Option's with List's, etc.

Keeping the original type variable names coud be confusing too. Some people use M instead of F. Others are using F to mean *. For example, Scalaz 7 codegens F for the type argument of both Functor and Monoid (to avoid confusion with A used for method type params?).

scala> :k scalaz.Monoid
Monoid's kind is F[A]

@folone
Copy link
Contributor Author

folone commented Apr 2, 2013

👍 I like @eed3si9n's notation. We could also consider adding other notation(s) to the verbose output. Something like:

scala> :k Unapply
Unapply's kind is Y[X[F[A1]], A2]

scala> :k -v Unapply
Unapply's kind is Y[X[F[A1]], A2]
In standard kind notation: ((* -> *) -> *) -> * -> *
In decomposed notation:
[_1, MA] => Unapply[_1, MA]
  where _1 = [_2] => X[_2]
  where _2 = [A1] => F[A1]
This is a type constructor that takes type constructor(s): a higher-kinded type.

@adriaanm
Copy link
Contributor

adriaanm commented Apr 2, 2013

For completeness, I should point out that kinds also track bounds and variance, as discussed in http://adriaanm.github.com/files/higher.pdf

@paulp
Copy link
Contributor

paulp commented Apr 2, 2013

Adriaan's comment is part of why I was devising my plane-aborted expanded notation. If we're seeking a "scala syntax" it must be capable of expressing type constructor variance and bounds, which means a bunch of stars won't suffice, and underscores will only take you so far.

@eed3si9n
Copy link
Member

eed3si9n commented Apr 2, 2013

@paulp Isn't "Scala syntax" essentially the same as the type parameter expression we write between the braces?

trait Foo[F[A <: Ordered[A]] <: Iterable[A]]

trait Foo[F[+A]]

@paulp
Copy link
Contributor

paulp commented Apr 2, 2013

The problems arise at the boundaries. I assume we're all familiar with the problem in this one:

trait Foo[CC[_] <: Traversable[_]]

The collision between an existential and a "don't care" placeholder type argument reaches tragic proportions if we'd like to come anywhere near optimal syntax for expressing kinds.

Even this can mean two different things

Foo[F[_]]

You have to know whether Foo's type argument is * -> * or * in order to know what F is doing there. But if the point of it is to communicate Foo's kind... I think we tolerate an unacceptable level of syntactic ambiguity.

package interpreter

trait Kind {
type T <: scala.reflect.api.Universe
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are conventions to which we should attempt to adhere. This T should be called either U (for Universe) or G (for Global.)

@eed3si9n
Copy link
Member

eed3si9n commented Apr 3, 2013

Since we're discussing implementation, we have a loose consensus on attempting Scala syntax as kind notation? If so could we roll back the last commit "Addressed some of issues, raised by @paulp" and merge folone/scala: folone/scala@ca9edc1 from topic/kind2 branch first? That's where Scala syntax notation is and it does address some of the points raised by Paul.

@folone I'd also like to get a chance to review the review before incorporating it.

@folone
Copy link
Contributor Author

folone commented Apr 3, 2013

@eed3si9n sure, go ahead. I actually should have done this in another branch.

@adriaanm
Copy link
Contributor

adriaanm commented Apr 5, 2013

If you don't mind I'm going to try to replace my hack logic with your inferKind this weekend.

That would be the ideal outcome of my manipulative reference to that old branch.
There's already a Kinds.scala in the compiler, by the way.

@eed3si9n
Copy link
Member

eed3si9n commented Apr 5, 2013

@paulp I guess if I had the full name I could ask g.rootMirror too:

scala> g.rootMirror.staticClass("scalaz.Monad")
res3: g.ClassSymbol = class Monad

But now it won't work for imported names:

scala> import scalaz._
import scalaz._

scala> g.rootMirror.staticClass("Monad")
scala.reflect.internal.MissingRequirementError: class Monad not found.

@folone
Copy link
Contributor Author

folone commented Apr 5, 2013

@eed3si9n this problem is exactly why I added two cases as a workaround here. This is how I get the fully qualified name for your case.

@eed3si9n
Copy link
Member

eed3si9n commented Apr 5, 2013

@folone By using exprTyper.typeOfExpression(expr.trim) it's treating the name as a term, so it currently requires companion object for imported names to work?:

scala> class Foo[A]
defined class Foo

scala> :k Foo
<console>:40: error: not found: value Foo
              Foo
              ^

scala> case class Bar[A]()
defined class Bar

scala> :k Bar
Bar's kind is F[A]

@eed3si9n
Copy link
Member

eed3si9n commented Apr 8, 2013

PLS REBUILD ALL

inferKind is ported. This solves kind with an elegant if statement:

if(!tpe.isHigherKinded)
  ProperTypeKind(tpe.asSeenFrom(pre, owner).bounds, tpe)
else { ... }

Thanks @retronym for solving the ClassInfoType mystery.

The verbose output now includes enhanced standard notation with bounds and variance:

scala> :k -v Either
scala.util.Either's kind is F[+A, +B]
* -(+)-> * -(+)-> *
This is a type constructor: a 1st-order-kinded type.

scala> :k -v scala.collection.generic.Sorted
scala.collection.generic.Sorted's kind is F[K, +This <: Sorted]
* -> *(Sorted) -(+)-> *
This is a type constructor: a 1st-order-kinded type.

I also added a limited String => Type facility. This allows me to write several types that was not supported previously:

scala> class Foo
defined class Foo

scala> :k Foo
Foo's kind is A

scala> :k Int => Int
scala.Function1's kind is F[-T1, +R]

@scala-jenkins
Copy link

(kitty-note-to-self: ignore 16032252)
🐱 Roger! Rebuilding pr-rangepos-per-commit, pr-checkin-per-commit for 7855b08f, c9314fb8, c4245ae6, ca9edc14, 4cc2222a, 3245d85e, 8999a60b, 5d7e1431. 🚨

@ghost ghost assigned adriaanm Apr 10, 2013
@eed3si9n
Copy link
Member

@adriaanm I took the type out of the kind. I was ignoring bounds that's the same as tpe, so now it comes out like this for proper types:

scala> :k Int
scala.Int's kind is A >: Int <: Int

scala> class Foo
defined class Foo

scala> :k Foo
Foo's kind is A >: Foo <: Foo

I don't know if this is correct/helpful.

@adriaanm
Copy link
Contributor

sorry, i got swamped and lost track of this

I'd expect Int and Foo's kind to simply be *
I don't think bounds are relevant for concrete types. For abstract types, they are, of course.

type X <: String would have kind *(Nothing, String), or *(String) since we usually only use upper bounds

@paulp
Copy link
Contributor

paulp commented Apr 19, 2013

I also expect Int and Foo's kind to be *, if anyone was waiting with bated breath for me to chime in.

def starNotation: String

private[internal] def buildString(sym: Symbol, variance: Variance, env: scala.collection.mutable.Map[Int, Int]): String
protected def typeAlias(x: Int): String =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you import mutable at the top of the file, and refer to mutable.Map? I am not into the super long paths being inlined.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mutable-map-as-method-parameter is almost invariably the wrong thing. Efficiency is no issue here, which removes the only imaginable defense. Please limit mutant wandering as much as possible.

:kind command diplays the kind of types and type constructors in Scala
syntax notation.

    scala> :kind (Int, Int) => Int
    scala.Function2's kind is F[-A1,-A2,+A3]
@eed3si9n
Copy link
Member

Added a new commit reflecting @paulp's code review. I've accepted all points that were raised.

Mainly, Kind now tries to avoid getting into the business of converting other objects (Variance, Type, and TypeBounds) into String, and instead call the appropriate method on them to do the conversion. For that purpose, scalaNotation and starNotation methods were added to TypeBounds. Also, the logic that was passing a mutable map around to build Scala notation is replaced with series of immutable state transformation.

Other changes:

  • Proper types no longer contains bounds (Int's kind is A)
  • Type constructors now contains bounds too (scala.collection.generic.ImmutableSortedMapFactory's kind is X[CC[A,B] <: scala.collection.immutable.SortedMap[A,B] with scala.collection.SortedMapLike[A,B,CC[A,B]]])
  • Type variable names are used if bounds exists, otherwise they are normalized to prescribed alphabets: A, F, X, Y, Z...

The commits were growing, so I squashed them and rebased it off the current master.

@eed3si9n
Copy link
Member

Any chance :kind gets into 2.11.0-M3?

@adriaanm
Copy link
Contributor

LGTM

Thanks for patiently slogging through the PR maze. There are some refinements to be made before using the Kinds data structure more generally, but let's get this in already!

(Here's my -- very late -- review:

  • only proper kinds track bounds
  • tycon kinds track them indirectly by the kinds they compose into function kinds
  • also, I prefer "parameter" to "argument" when referring to the abstraction and not the application
    )

adriaanm added a commit that referenced this pull request May 17, 2013
Added a :kind command to the REPL
@adriaanm adriaanm merged commit bf3c44c into scala:master May 17, 2013
@eed3si9n
Copy link
Member

Nice! Thanks all for the feedback.

eed3si9n added a commit to eed3si9n/scala that referenced this pull request May 20, 2017
Fixes scala/bug#8529

The :kind implementation added in scala#2340 was buggy and misleading in part because we could not find a robust way to turning type expression strings into Type. See also http://stackoverflow.com/questions/15678616/getting-type-information-inside-scala-repl-via-imain/15694003#15694003. The problem was that we didn't know how to pick up types defined in the REPL, so scala#2340 faked it by treating the expression as a term.

That's the major source of problems reported in scala/bug#8529. This change improves the String to type symbol conversion by using `exprTyper.typeOfTypeString` repeatedly with `[Nothing]`. It's hacky, but a whole class of "expression" can now be called out as not being a type.

Before:

    scala> :kind 5
    scala.Int's kind is A

After:

    scala> :kind 5
    <console>:1: error: identifier expected but integer literal found.
    def $ires23: 5 = ???
                 ^

More importantly, it will no longer mix up companion objects with actual types.

Before:

    scala> :kind -v Predef.Pair
    scala.Predef.Pair's kind is A
    *
    This is a proper type.

After:

    scala> :kind -v Predef.Pair
    scala.Tuple2's kind is F[+A1,+A2]
    * -(+)-> * -(+)-> *
    This is a type constructor: a 1st-order-kinded type.
eed3si9n added a commit to eed3si9n/scala that referenced this pull request May 20, 2017
Fixes scala/bug#8529

The :kind implementation added in scala#2340 was buggy and misleading in part because we could not find a robust way to turn a type expression string into a Type. See also [Getting type information inside scala repl via IMain](http://stackoverflow.com/questions/15678616/getting-type-information-inside-scala-repl-via-imain/15694003#15694003). The problem was that we didn't know how to pick up types defined or imported in the REPL, so scala#2340 faked it by treating the given type expression as a term.

That's the major source of problems reported in scala/bug#8529. This commit improves the String to type symbol conversion by using `exprTyper.typeOfTypeString` repeatedly with `[Nothing]`. It's hacky, but a whole class of "expression" can now be called out as not being a type.

Before:

    scala> :kind 5
    scala.Int's kind is A

After:

    scala> :kind 5
    <console>:1: error: identifier expected but integer literal found.
    def $ires23: 5 = ???
                 ^

More importantly, it will no longer mix up companion objects with actual types.

Before:

    scala> :kind -v Predef.Pair
    scala.Predef.Pair's kind is A
    *
    This is a proper type.

After:

    scala> :kind -v Predef.Pair
    scala.Tuple2's kind is F[+A1,+A2]
    * -(+)-> * -(+)-> *
    This is a type constructor: a 1st-order-kinded type.
eed3si9n added a commit to eed3si9n/scala that referenced this pull request May 20, 2017
Fixes scala/bug#8529

The :kind implementation added in scala#2340 was buggy and misleading in part because we could not find a robust way to turn a type expression string into a Type. See also [Getting type information inside scala repl via IMain](http://stackoverflow.com/questions/15678616/getting-type-information-inside-scala-repl-via-imain/15694003#15694003). The problem was that we didn't know how to pick up types defined or imported in the REPL, so scala#2340 faked it by treating the given type expression as a term.

That's the major source of problems reported in scala/bug#8529. This commit improves the String to type symbol conversion by using `exprTyper.typeOfTypeString` repeatedly with `[Nothing]`. It's hacky, but a whole class of "expression" can now be called out as not being a type.

Before:

    scala> :kind 5
    scala.Int's kind is A

After:

    scala> :kind 5
    <console>:1: error: identifier expected but integer literal found.
    def $ires23: 5 = ???
                 ^

More importantly, it will no longer mix up companion objects with actual types.

Before:

    scala> :kind -v Predef.Pair
    scala.Predef.Pair's kind is A
    *
    This is a proper type.

After:

    scala> :kind -v Predef.Pair
    scala.Tuple2's kind is F[+A1,+A2]
    * -(+)-> * -(+)-> *
    This is a type constructor: a 1st-order-kinded type.
eed3si9n added a commit to eed3si9n/scala that referenced this pull request May 21, 2017
Fixes scala/bug#8529

The :kind implementation added in scala#2340 was buggy and misleading in part because we could not find a robust way to turn a type expression string into a Type. See also [Getting type information inside scala repl via IMain](http://stackoverflow.com/questions/15678616/getting-type-information-inside-scala-repl-via-imain/15694003#15694003). The problem was that we didn't know how to pick up types defined or imported in the REPL, so scala#2340 faked it by treating the given type expression as a term.

That's the major source of problems reported in scala/bug#8529. This commit improves the String to type symbol conversion by using `exprTyper.typeOfTypeString` repeatedly with `[Nothing]`. It's hacky, but a whole class of "expression" can now be called out as not being a type.

Before:

    scala> :kind 5
    scala.Int's kind is A

After:

    scala> :kind 5
    <console>:1: error: identifier expected but integer literal found.
    def $ires23: 5 = ???
                 ^

More importantly, it will no longer mix up companion objects with actual types.

Before:

    scala> :kind -v Predef.Pair
    scala.Predef.Pair's kind is A
    *
    This is a proper type.

After:

    scala> :kind -v Predef.Pair
    scala.Tuple2's kind is F[+A1,+A2]
    * -(+)-> * -(+)-> *
    This is a type constructor: a 1st-order-kinded type.
eed3si9n added a commit to eed3si9n/scala that referenced this pull request May 21, 2017
Fixes scala/bug#8529

The :kind implementation added in scala#2340 was buggy and misleading in part because we could not find a robust way to turn a type expression string into a Type. See also [Getting type information inside scala repl via IMain](http://stackoverflow.com/questions/15678616/getting-type-information-inside-scala-repl-via-imain/15694003#15694003). The problem was that we didn't know how to pick up types defined or imported in the REPL, so scala#2340 faked it by treating the given type expression as a term.

That's the major source of problems reported in scala/bug#8529. This commit improves the String to type symbol conversion by using `exprTyper.typeOfTypeString` repeatedly with `[Nothing]`. It's hacky, but a whole class of "expression" can now be called out as not being a type.

Before:

    scala> :kind 5
    scala.Int's kind is A

After:

    scala> :kind 5
    <console>:1: error: identifier expected but integer literal found.
    def $ires23: 5 = ???
                 ^

More importantly, it will no longer mix up companion objects with actual types.

Before:

    scala> :kind -v Predef.Pair
    scala.Predef.Pair's kind is A
    *
    This is a proper type.

After:

    scala> :kind -v Predef.Pair
    scala.Tuple2's kind is F[+A1,+A2]
    * -(+)-> * -(+)-> *
    This is a type constructor: a 1st-order-kinded type.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
8 participants