Supporting import in extension method blocks

Couldn’t we desugar that to:

object O:
  type M
extension (x: A) 
  def f(x: O.M) =
    import O.*
    ...

Or maybe crazier, allow import parameter clauses:

def f(x: A)(import O.*)(x: M)

This could be internal-only of course

Overall, I’m really not a fan of this “It’s not a scope because there’s no colon (:)” argument

To me Scala is the language of “looking right”, things behave as they look, and extensions look like they create a scope, with or without colon, and that’s why many people want to use a colon there !

And we can create scopes without braces or colons:

x match
  case a =>
    import whatever.I.want
    "This is a scope !"

For me colon is just a clunky way of saying “I’m going to start an indented section here”
(And I am fairly confident we could completely do “colon elision”, in a way not so different from how we did semi-colon elision)

3 Likes

Actually given ... with has no colon and provides scope.

1 Like

Lots of keywords create a scope, e.g. then, else, do, yield, the list goes on. (They are listed in the spec).

I think this convenience is what creates an illusion of a new scope being opened since the extension parameters are then visible everywhere below.

Could there be another version of scope we could use here without any ‘state’ based entries (val / var)? It does seem natural syntactically to allow imports / exports / defs / type aliases here in this stateless scope, even though I get that its very far from whats happening under the hood.

1 Like

My main use case for extensions is to add methods to collection wrapper classes such as

case class Tracks(tracks: Vector[Track]):
...

extension(tracks: Vector[Track])
...

Unless there is some technical reason not to do so, I say just make the import automatic (as in Kotlin?) and be done with it. That would be consistent with adding a method within the class constructor itself if you had access to the source code, so I don’t see how that could be a problem.

Why should I have to write

def timeRange = ScalarRange(tracks.head.time, tracks.last.time)

when I can just write

def timeRange = ScalarRange(head.time, last.time)

An explicit import declaration as others have suggested would be the next best thing, but I don’t see why it should even be needed since it is not needed in the class constructor itself.

After thinking about it a bit more, it occurred to me that we could take it a step further and not even require a name for the extension, as in

extension(Vector[Track])

as opposed to

extension(tracks: Vector[Track])

The name could even be optional, depending on user preference. If no name is given, then the current instance would be referred to as this, and the method would be identical to what it would be if it were inside the class constructor. That would mean that methods could be moved from the class constructor to an extension and back with no changes whatsoever. I like that idea. (I hope I am not missing something obvious here.)

+1 For keeping the current syntax (and thus not forcing each extension method to be broken out individually).

Here’s an example of why I’m keen on preserving the status quo:

This extension construct defines two related helpers - just as in a trait / class / case class, any methods defined there are logically grouped together, so they are in the extension.

I’d like to keep that ability to group things together for the sake of comprehension, even though I know that all extensions are add-ons to a core API / interface / however you think of it.

Also note that both helpers delegate to a private implementation-sharing method defined in the extension - at least I think that private method belongs to the extension. If I have to break out the two helpers into separate extensions, then I either have to inline said method or move it up to the enclosing scope and thus leak it.

Stepping back, how much pain is there in just typing out <self>.<whatever> as opposed to <whatever>? In the example linked to, nearly all of the references are to sequence, there is just one to sequence.empty.

Just how large are these extension methods in reality?

If this is a problem, is it really so hard just to write import <self>.* at the start of each extension method?

If folks are happy to write import <self>.* at the start of an extension, can they not just write it for each extension method in the construct instead?

If there is however a way of allowing a pseudo-extension scope import, then great.

1 Like

I’ve edited my earlier response because I’m conflating two separate things - my reluctance to have the syntax go backwards and the original point of this discussion.

It’s not fair of me to say that others shouldn’t want something more when I’m being vociferous about keeping something already there, sorry.

Yep current syntax is excellent!

Exactly! There is such a thing as too much sugar, it’s bad for your health :smiley: Moreover, self or this etc. make things much clearer to the reader.

Exactly. That’s what I do.

Exactly. Extensions are meant to be one-off or two-off, small things here and there. Especially when you are using a library you cannot change. This is what made it click for me. At first I thought “what’s the point of this?” and then I ran into the exact situation it’s designed for, in the wild.

I think massive extension blocks are a sign of taking the “avoiding the wrapper” obsession way too far. It’s like “I’m gonna bend over backwards just to avoid writing a normal class or case class.” This is drifting back towards the abused implicit class stuff. There is a point at which you have to stop and say “let’s just design a normal class.”

@spamegg1 I agree completely; I guess you responded to my earlier version of my reply. However, I’m concerned just about going backwards to a more cumbersome syntax.

While the principal theme of this discussion doesn’t count as a pressing need for me, that’s no reason why it shouldn’t be proposed.

Sorry again, for the confusion.

1 Like

At that point, extension becomes almost the same as class or case class, gaining most of its functionality… the difference becomes mostly cosmetic.

There is good reason to keep certain designs simple and limited on purpose. Otherwise we’re duplicating the same functionality.

Too much sugar will rot your teeth! :stuck_out_tongue_winking_eye:

Let me test my understanding of why this is tricky…

There is no scope on an extension because someone (like me) will write this:

// Hypothetical syntax that doesn't compile...

extension (self: Throwaway):
  // Where do I live in memory - in a COM-style tearoff?
  var sponsor: String = ""
  
  // When do I get initialised - at construction, or when the
  // class loader pulls in the  extension, or lazily on demand,
  // or each time a method is called?
  val thing: Int = bigLongComputation() 
  
  def sponsor(newSponsor: String): Unit =
    sponsor = newSponsor
    
  def get(): String = s"$thing, proudly sponsored by $sponsor"  
end extension

BTW, import-only has surprises anyway, even in the language as it stands:

object Throwaway:
  private var secret: Int = 1
end Throwaway

class Throwaway:
  def hitIt(): Unit =
    import Throwaway.* // Everything is imported - private parts too.
    secret += 1
end Throwaway

// OK, so let's try that in an extension, that's part of the class, isn't it?

class Throwaway2:
  private var secret: Int = 1
  var open: Int = 1

  def hitIt(): Unit =
    secret += 1
end Throwaway2


extension (self: Throwaway2)  
  def giveMeAccessToThatThirdPartyImplementation: Int = 
    import self.*
    
    // Nope!
    open + secret
end extension

That doesn’t work because extension methods have to respect the encapsulation boundary, just as they do in C# and if I recall, in F# too.

I’ve got no problem with the current state of affairs, and if it could be implemented, I’d have no problem with the original proposal, but extensions are fundamentally a bit different, and that should be expected. They work well, though.

1 Like

I don’t think you understand my application and the issues that I am dealing with. You seem to think that the only valid use case for extensions is the occasional library class that you need to add a function to but don’t have access to the source code. That is not true.

As I mentioned earlier, one of the things my code does is to construct flyable aircraft trajectories. It does so by constructing trajectory segments such as climb, cruise, descent, and various maneuvers, then it splices them together into a complete trajectory. One of the collection wrapper classes that I use for this process looks something like this:

case class Tracks(tracks: Vector[Track]):
...

For most of this work, I only need access to the Vector[Track] part of it, not the wrapper class. I have dozens of functions that work on Vector[Track]. I was using implicit classes, but then I had a nasty bug due to a cyclic reference of implicits, and I was informed on this forum that extensions are a better choice. So now I am trying to use extensions, and now you are telling me that they are only intended for very limited uses.

I think extensions should be very useful for my application, in which I have many functions (“methods”) that apply to the underlying Vector[Track] class but can also be used in the wrapper class. If I just put all those functions in the wrapper class without using extensions, I end up having to constantly unwrap and wrap instances of Vector[Track] to use those functions. Why should I have to do that? It just clutters the code for no good reason.

Also, as I said before, I don’t understand why the semantics of an extension can’t be the same as for the class constructor itself, in which other class methods are automatically in scope. For Vector, that includes basic functions such as head, last, length, isEmpty, etc. I think it is more elegant to have the extension semantics indentical to the semantics of the class constructor. If you are trying to add a function to a class, why should it look different than if you had access to the source code and put it in the constructor?

Indeed. Consistency - this is the key argument.

In a class’s methods, access via the this reference is so common that it is made implicit, by a longstanding convention is the Scala/C#/Java language lineage.

The purpose of extension methods is to permit retroactive extension of a class defined elsewhere. For many reasons, this is a common problem programmers face; has been for decades, and it is not going away.

There may be few such retroactive extensions, or there may be many. If Scala’s BigDecimal were implemented via extension methods instead of wrappers, as Martin has stated would have been the preferable approach if the technology were available back then, extensions would number about 100.

IMO there’s no particularly convincing reason why referring to this in a retroactive method should be explicit, but in an instance method, it is implicit. The language is more regular and consistent if the two cases work the same, and thus retroactive extension isn’t (yet again) a second-class citizen.

That said, we could not simply change the language semantics in this regard, as it would change the meaning of existing code. That’s why allowing an opt-in via import is a viable path forward.

If import at the extension-block is not possible, then my alternative syntax proposal deserves more serious consideration.

1 Like

My earlier proposal would not change the meaning of existing code either. Keep the existing syntax for extensions, but also allow an option to omit the arbitrary name in the extension declaration, as in

case class Circle(x: Double, y: Double, radius: Double)

extension (Circle)
  def circumference: Double = radius * math.Pi * 2
1 Like

My earlier proposal would not change the meaning of existing code either. Keep the existing syntax for extensions, but also allow an option to omit the arbitrary name in the extension declaration

Right, yes I think that will work without breaking existing semantics. I’d missed that detail earlier. And there is a precedent for writing types without term names, given that its permitted for given declarations.

On consideration, I prefer your type-without-named-term syntax proposal.

1 Like

I am having second thoughts about collection wrapper classes – and realizing that maybe some of my earlier comments were wrong. (This is separate from my suggestion for the extension declaration syntax with no instance name.)

Going back to my basic example:

case class Tracks(tracks: Vector[Track]):

I was thinking that I needed extension functions on Vector[Track] to avoid constant wrapping and unwrapping of the Vector[Track] class to get things done. However, I am now realizing that just adding

  import tracks.*
  export tracks.*

at the top of the Tracks class should give me all the Vector functions I need, including head, last, length, take, ++, :+, etc. That should allow me to essentially “work through the wrapper” and avoid explicit wrapping and unwrapping (without using any extensions). Am I on the right track here, or am I missing something?

As I understand it, the import tracks.* will let you call methods on tracks without dealing with the explicit reference inside the class. export tracks.* will not re-wrap, so you’ll still need to override any methods you want to return a Tracks rather than a Vector[Track].

1 Like