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

Printing results #37

Closed
paulp opened this issue Mar 10, 2015 · 20 comments
Closed

Printing results #37

paulp opened this issue Mar 10, 2015 · 20 comments

Comments

@paulp
Copy link

paulp commented Mar 10, 2015

Can you please provide a hook for how to print repl results? Pretty-printing is by default defined for most standard library types, as well as case classes and case objects. For other types not supported, it falls back to using toString says the doc. That's all undesirable for me.

I've gone to great lengths not to let toString infect everything in my library. It's essentially impossible to get out in front of scala's call to scala.runtime.ScalaRunTime.replStringOf because sbt is proguarded and bundled with a stripped down version. But Ammonite doesn't have to be as hostile to decent printing. I have a method like

object psp { def show[A: Show](x: A): String = implicitly[Show[A]] show x }

If there were some way to get the ammonite repl to use it on all results it would be hugely helpful.

@lihaoyi
Copy link
Member

lihaoyi commented Mar 10, 2015

You provide an implicit PPrinter[T] to tell it how to pprint something, and it should do it (modulo bugs). PPrinter[T] is separate from PPrint[T] to try and make the contravariant typeclasses work (seems to...)

This is describe too-briefly in http://lihaoyi.github.io/Ammonite/#Ammonite-PPrint. Could definitely use more prose.

@paulp
Copy link
Author

paulp commented Mar 10, 2015

I haven't been able to make it work for long enough to try much out.

@ implicit def implicitPPrint[A](implicit z: Show[A]): PPrint[A] = null
Compilation Failed
Main.scala:69: '}' expected but identifier found.
  +:$,
    ^
@

I guess I'm skeptical that, assuming other things worked, that if I had an implicit Show[String] which always prints "bippy" and I gave the literal String "foo" then I would see res0: bippy. But that's what I am after.

@paulp
Copy link
Author

paulp commented Mar 10, 2015

(Make the above PPrinter, obviously not going to save the day.)

@alexarchambault
Copy link
Collaborator

Same error as #36, possibly just fixed by @lihaoyi

@paulp
Copy link
Author

paulp commented Mar 10, 2015

I'm running against 47a361e, so guessing not fixed.

@lihaoyi
Copy link
Member

lihaoyi commented Mar 10, 2015

yeah imports are borked and being tracked here #36.

Hopefully after playing whack-a-mole and adding a few more unit tests for all these failing cases it should be in reasonable shape.

@paulp
Copy link
Author

paulp commented Mar 10, 2015

Oh, when you say just you really mean "just".

@alexarchambault
Copy link
Collaborator

A few minutes ago, yeah :-)

@lihaoyi
Copy link
Member

lihaoyi commented Mar 10, 2015

note that not all tests pass yet, but it should put it in a slightly better state. I'll come back and make it better after I'm done writing puppet scripts for $$$ at work

@alexarchambault
Copy link
Collaborator

Update: it's not fixed :-|

@paulp
Copy link
Author

paulp commented Mar 10, 2015

By the way, if you want to improve on the scala repl this is the hard way to do it. Trying to elicit expected behavior wrt implicits and user-given imports in a repl setting via a rat's nest of generated strings is never going to work right. You have to manage the namespace directly.

@lihaoyi
Copy link
Member

lihaoyi commented Mar 10, 2015

@paulp I don't understand. I am managing the namespace directly, and have a Map[String, ImportData] of where the various things I'm importing come from. Do you think I should dive deeper into the compiler internals and munge the data-structures myself?

@paulp
Copy link
Author

paulp commented Mar 10, 2015

@lihaoyi Yes, that's what I mean. Here are some excerpts from mine.

class Analyzer(val global: outer.type) extends typechecker.Analyzer {
  def filteredRootContext(unit: ScalpelUnit, tree: Tree = EmptyTree): Context = rootContext(unit, tree)
  override def newNamer(context: Context): ScalpelNamer = new ScalpelNamer(context)
  override def newTyper(context: Context): ScalpelTyper = new ScalpelTyper(context)

  class ScalpelTyper(context: Context) extends Typer(context) {
    override def typed1(tree: Tree, mode: Mode, pt: Type): Tree = {
      super.typed1(tree, mode, pt)
    }
    override def typed(tree: Tree, mode: Mode, pt: Type): Tree = {
      strace(s"typed($tree, $mode, $pt)")

      tree match {
        case Ident(name) =>
        strace(s"typed($tree, $mode, $pt)")
        bindings get name.swap foreach { fullType =>
          val ref = atPos(tree.pos)(fullType.fullNameSelection)
          strace(s"Inserting $ref for $name")
          return super.typed(ref, mode, pt)
        }
        case _           =>
      }
      super.typed(tree, mode, pt)
    }
  }
  override def findMacroClassLoader(): ClassLoader = new AbstractFileClassLoader(outdir, super.findMacroClassLoader)
}

trait Bindings[K, V] {
  def prev: Bindings[K, V]       // the bindings which preceded these - collisions are shadowed
  def defined: Set[K]            // keys defined directly in this instance
  def imported: Set[K]           // keys directly imported by this instance
  def apply(key: K): V           // if defined or imported in this instance, the value, otherwise consult prev
  def keys: Set[K]               // directly defined/imported keys plus all keys of prev
  def contains(key: K): Boolean  // if the key is defined/imported here or in prev
}

trait FullTypes {
  val global: Global

  import global._, definitions._
  private[this] var currentBindings: ReBindings = NoBindings
  def isBound(name: nm.Name)                        = bindings contains name
  def lookup(name: nm.Name)                         = bindings(name)
  def bindings                                      = currentBindings
  def applyBindings(f: ReBindings => ReBindings)    = currentBindings = f(currentBindings)
  def retainBindings(p: nm.Name => Boolean): Unit   = currentBindings = currentBindings filterKeys p
  def addBindings(bs: ReMap): Unit                  = currentBindings = currentBindings ++ bs
  def setBindings(bs: ReBindings): Unit             = currentBindings = bs
  def importAllFrom(name: String): Unit             = wrap(importAllFrom(staticTerm(name)))
  def importAllFrom(tpe: FullType): Unit            = addBindings(tpe.membersMap)
  def bindOne(name: nm.Name, value: FullType): Unit = addBindings(Map(name -> value))

  type ReBindings = Bindings[nm.Name, FullType]
  type ReMap      = Map[nm.Name, FullType]
  def NoBindings: ReBindings = Bindings.empty

  ...
}

@paulp
Copy link
Author

paulp commented Mar 10, 2015

What it all leads to is taking the parse tree and attaching symbols to identifiers directly, based on the bindings you are tracking yourself. Then you hand that tree off to typer to let it go the rest of the way.

@lihaoyi
Copy link
Member

lihaoyi commented Mar 11, 2015

@paulp Seems plausible. I don't know how well it'd actually work in practice: it seems pretty complicated, but stitching strings is starting to get complicated too. Likely I will keep this stuff in mind as I keep playing whack-a-mole with the string splicer, and if the string splicing gets too annoying then I'll try your approach

@lihaoyi
Copy link
Member

lihaoyi commented Mar 11, 2015

This works now

@ class C
defined class C
@ implicit def pprint = ammonite.pprint.PPrinter[C]((t, c) => Iterator("INSTANCE OF CLASS C"))
defined function pprint
@ new C
res2: cmd0.C = INSTANCE OF CLASS C

https://github.com/lihaoyi/Ammonite/blob/master/repl/src/test/scala/ammonite/repl/AdvancedTests.scala#L171-L182

@lihaoyi lihaoyi closed this as completed Mar 11, 2015
@paulp
Copy link
Author

paulp commented Mar 11, 2015

Yes, it is pretty complicated, because the compiler is nothing but complicated. You haven't gotten to the real complications in in string land yet. Do you know why the repl creates all those nested objects? Do you intend to duplicate that approach?

@lihaoyi
Copy link
Member

lihaoyi commented Mar 11, 2015

I know why the REPL creates nested objects, and I think I can avoid doing that. The main distinction is that I expand out imports into their individual names and the nsc.interpreter doesn't. Thus I can avoid relying on the depth of an import to impose ordering, and can emulate import-shadowing manually by adding and removing imports at the correct time.

It's not 100% accurate yet (e.g. it doesn't distinguish the type namespace from the term namespace) but it's pretty darn close. If you can elaborate on what test cases w.r.t. implicits would fail, we may be able to fix them, or we may decide that some of the semantics are just broken (e.g. Scala's ambiguous-import errors) and document/wontfix them rather than trying to emulate.

@paulp
Copy link
Author

paulp commented Mar 11, 2015

I guarantee you that you underestimate the complexity of reproducing the compiler's rules for ambiguous imports. You will never make it work well this way. I won't need to elaborate because you will discover it for yourself.

@lihaoyi
Copy link
Member

lihaoyi commented Mar 11, 2015

I await my enlightenment then ^_^

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants