question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

In Scala.js 1.0, js.eval returns scala.Any instead of js.Any

See original GitHub issue

In the 0.6.x branch it returns js.Any.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:8 (8 by maintainers)

github_iconTop GitHub Comments

1reaction
sjrdcommented, May 2, 2020

Ah, I believe you’ve misunderstood the nature of js.Any, which is quite understandable since I had myself misunderstood it for years. Let me try to explain from first principles.

In Scala/JVM

First, let’s look at the top of the Scala hierarchy:

          +-----------+
          |    Any    |
          +-+-------+-+
            ^       ^
            |       |
        +---+       +----+
        |                |
+-------+----+     +-----+------+
|   AnyVal   |     |   AnyRef   |
+------------+     +------------+

Any (if I write Any, it always means scala.Any; I write js.Any explicitly) is the top type of the Scala type system. No class can directly extend it but AnyVal and AnyRef.

AnyVal and AnyRef form a partition of all the classes in the Scala type system. Depending on which one any given class C inherits from, C features different fundamental semantics:

  • AnyVals are value classes; they do not have an identity, cannot be compared by reference
  • AnyRefs are reference classes; they have an identity and can be compared by reference

If you look at how one uses AnyVal and AnyRef in Scala, it is almost exclusively in extends clauses. Their use in an extends clause like

class C extends AnyVal

is to give C the specific semantics of AnyVal. AnyRef appears less often in typical code because it is the default, but if weren’t, it would be used in the same way.

You’re not primarily extending AnyVal or AnyRef because you want to be a subtype of those almost-top types. You’re doing it because you want the semantics that are associated with their respective hierarchies.

More glaring: you never use AnyVal and AnyRef as the type of a variable or a parameter. At best, you use a type parameter T <: AnyRef if you want the capability to compare Ts by reference.

Now comes js.Any

In Scala.js, we have a third almost-top type beside AnyVal and AnyRef: js.Any:

                   +-----------+
                   |    Any    |
                   +-+---+---+-+
                     ^   ^   ^
                     |   |   |
        +------------+   |   +------------+
        |                |                |
+-------+----+     +-----+------+     +---+--------+
|   AnyVal   |     |   AnyRef   |     |   js.Any   |
+------------+     +------------+     +------------+

Although js.Any is declared as a trait for technical reasons, there are compile-time checks that make sure that there cannot be a class C that inherits both from js.Any and AnyVal or AnyRef (with the exception of directly extending AnyRef, again for technical reasons related to how much we can convince the compiler to do what we want, and how much it constrains us).

So now we have 3 almost-top classes AnyVal, AnyRef and js.Any, forming a partition of all the classes in the Scala.js type system.

Just like AnyVal and AnyRef are providing specific semantics to their subtrees, so does js.Any: classes inheriting from js.Any have JavaScript semantics. They resolve overloads at run-time rather than compile-time, their non-private members are visible from JavaScript, they have a first-class class value (js.constructorOf[C]), etc.

Just like AnyVal and AnyRef, the main use js.Any in code is in C extends js.Any clauses, to associate C with JavaScript semantics. We don’t primarily use js.Any for the subtyping relationship, and we almost never give the exact type js.Any to variables, parameters or result types, because there’s nothing you can do with a js.Any.

An exception, like T <: AnyRef, is to use T <: js.Any, when for some reason we need T to have JavaScript semantics (e.g., if we need a js.ConstructorTag[C], which only makes sense if C has JS semantics).

What I still consider confusing

There’s one thing in the core library that contradicts the principles laid above: js.Dynamic. The API of js.Dynamic takes js.Anys as inputs for all the parameters, even though I said above that we should basically never use js.Any as the type of a parameter.

TBH, it’s a hack. We could use scala.Anys there, and in a sense we should, but we’re using js.Any to force implicit conversions to kick in, so that simple-looking code works (usually). For example, you can give a lambda to a js.Dynamic method, and it will be implicitly converted to the appropriate js.FunctionN. That wouldn’t happen if it took a scala.Any.

Essentially, the use of js.Any in js.Dynamic is a DSL-like hack, which we need because we don’t have better types. You shouldn’t copy that idiom in statically typed APIs.

Addressing some of your direct comments/suggestions

Now that you hopefully have a clearer picture of the design as I see it, here are some answers to your comments and suggestions.

js.eval is executing some code and getting a value back from JS land and you’re saying that it could be executing so SJS which could result in a Scala value. I agree! That property isn’t unique to js.eval; it’s a property of the entire environment when Scala.JS is running in JS.

Absolutely. And that is why one should virtually never use js.Any as a type for parameters and result types. We should always use scala.Any instead, which is the true top type of the Scala.js type system.

I don’t see a way of thinking that could allow one to say JS types don’t cover Scala types in JS

The way of thinking is about the semantics: JS types have JS semantics (run-time overloading, etc.) which definitely does not cover Scala types, which have Scala semantics (compile-time overloading, etc.).

Btw to concisely clarify here: I believe js.Any should be removed in favour of scala.Any. I believe there shouldn’t be a distinction. All js.Any values are scala.Any values (via subtyping); and all scala.Any values are js.Any values because js.Any is the Top type in JS, and all SJS values exist in JS.

I believe the explanation on semantics covers why that would not be appropriate.

I imagine the only problem with removing js.Any would be extends js.Any but you could use any old marker trait for that purpose. js.Native would be clearer.

js.Native would clearly be wrong, since there are non-native classes that extend js.Any: anything that’s not annotated with @js.native is non-native. Being native or not is not an inherited property (otherwise we could never extend a native class from a non-native one).

0reactions
sjrdcommented, May 4, 2020

True 😅

Read more comments on GitHub >

github_iconTop Results From Across the Web

Scala.js 1.0.0-M6 - scala.scalajs.js
This package is only relevant to the Scala.js compiler, and should not be referenced by any project compiled to the JVM. Guide. General...
Read more >
Scala.js 1.0.0 - scala.scalajs.js.Function
The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence...
Read more >
Announcing Scala.js 1.0.0-RC1
We are thrilled to announce the release of Scala.js 1.0.0-RC1! ... If no critical issue is found until the end of January 2020, ......
Read more >
Announcing Scala.js 1.0.0
We are thrilled to announce the General Availability release of Scala.js 1.0.0! Scala.js is a close dialect of Scala compiling to JavaScript, ...
Read more >
Any - extends AnyRef - Scala.js
A JavaScript type that is annotated with @js.native is a facade type to APIs implemented in JavaScript code. Its implementation is irrelevant and...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found