Vraag Is er een manier om een ​​Scala-klas te 'verrijken' zonder de code in een ander voorwerp te verpakken?


In Scala 2.9 om aangepaste methoden aan een bibliotheekklasse toe te voegen (verrijken of "pimpen") moest ik zoiets als dit schrijven:

object StringPimper {
  implicit def pimpString(s: String) = new {
    def greet: String = "Hello " + s
  }
}

Toen Scala 2.10 werd uitgebracht, heb ik gelezen dat het een impliciete klassendefinitie introduceert die, theoretisch, bedoeld was om de bovenstaande taak te vereenvoudigen door de noodzaak te verwijderen van een impliciete methode die een object van een anonieme klasse retourneert. Ik dacht dat het me in staat zou stellen om gewoon te schrijven

implicit class PimpedString(s: String) {
  def greet: String = "Hello " + s
}

dat ziet er veel mooier uit voor mij. Maar zo'n definitie veroorzaakt een compilatiefout:

`implicit' modifier cannot be used for top-level objects

wat wordt opgelost door de code opnieuw in een object te verpakken:

object StringPimper {
  implicit class PimpedString(s: String) {
    def greet: String = "Hello " + s
  }
}

overbodig om te zeggen dat dit bijna het gevoel van de verbetering neutraliseert.

Dus, is er een manier om het korter te schrijven? Om het wrapper-object kwijt te raken?

Ik heb eigenlijk een MyApp.pimps pakket waar alle pooiers naartoe gaan (ik heb er niet te veel, ik zou wat losse pakketten gebruiken als ik dat had) en ik ben het beu om te importeren MyApp.pimps.StringPimper._ in plaats van MyApp.pimps.PimpedString of MyApp.pimps._. Natuurlijk zou ik alle impliciete klassen in een enkel wrapper-object kunnen plaatsen, maar dit zou betekenen dat ze allemaal in één bestand zouden moeten worden geplaatst, wat vrij lang zou zijn - een nogal lelijke oplossing.


15
2018-02-10 20:58


oorsprong


antwoorden:


De standaardterm is dat nu verrijken. Ik weet niet zeker waarom dit niet eerder was, gezien het feit dat de bibliotheekmethoden die het gebruikten dingen waren zoals RichString niet PimpedString.

Hoe dan ook, je kunt impliciete klassen niet in niets plaatsen, maar om implicaties te gebruiken, heb je methodes nodig en kun je methoden niet in het niets plaatsen. Maar je kunt vals spelen om alles in te krijgen pimps.

// This can go in one file
trait ImplicitsStartingWithB {
  implicit class MyBoolean(val bool: Boolean) { def foo = !bool }
  implicit class MyByte(val byte: Byte) { def bar = byte*2 }
}

// This can go in another file
trait ImplicitsStartingWithS {
  implicit class MyShort(val short: Short) { def baz = -short }
  implicit class MyString(val st: String) { def bippy = st.reverse }
}

// This is a third file, if you want!
object AllImplicits extends ImplicitsStartingWithB with ImplicitsStartingWithS {}

scala> import AllImplicits._
import AllImplicits._

scala> true.foo
res0: Boolean = false

Je kunt dit ook doen met de pimps package-object - meestal plaats je een bestand met de naam package.scala in een map genaamd pimps, en dan

package object pimps extends /* blah blah ... */ {
  /* more stuff here, if you need it */
}

Het enige nadeel is dat waardeklassen, als nieuwe functie, een behoorlijk aantal beperkingen hebben. Sommige zijn niet nodig, maar als u wilt voorkomen dat u een nieuwe toewijst MyBoolean class (die de JVM vaak enorm kan optimaliseren, maar meestal niet in de mate van een kale methode) moet je de beperkingen omzeilen. In dat geval,

// In some file, doesn't really matter where
class MyBoolean(val bool: Boolean) extends AnyVal { def foo = !bool }

package object pimps {
  def implicit EnrichWithMyBoolean(b: Boolean) = new MyBoolean(b)
}

wat je niet helpt te werken (maar wel sneller werkt).


10
2018-02-10 21:27



Het werk rond voor het toestaan ​​van de verrijking klasse om te verblijven in een ander bestand is om het te wikkelen in een eigenschap in plaats van een object:

// file: package.scala
package object mypackage extends StringImplicits {
  def test { println("Scala".greet) }
}

// file: StringImplicits.scala
package mypackage

trait StringImplicits {
  implicit class RichString(s: String) {
    def greet: String = "Hello " + s
  }
}

Zoals Rex aangeeft, kun je geen waardeklassen gebruiken in andere klassen. Dus als u wilt profiteren van de waarderingscategorieën van Scala 2.10, kunt u geneste impliciete klassen niet gebruiken. D'oh!


4
2018-02-10 21:30



Nou, de compileerfout geeft het allemaal weer. Je kunt gewoon niet een impliciete klasse op topniveau definiëren. Dat betekent dat u ofwel een wrapper-object gebruikt (dat ook een pakketobject kan zijn), of de klasse rechtstreeks definieert in de scope waarvoor het zou moeten worden gebruikt (in het uitzonderlijke geval dat het niet opnieuw bruikbaar hoeft te zijn).

De reden hiervoor is dat je de impliciete conversie niet zonder de wrapper zou kunnen importeren. U kunt de impliciete klasse (precies: de conversie) niet op eigen naam importeren.

Daarnaast zou ik willen voorstellen om een ​​iets andere signatuur te gebruiken (zie Waardeklassen):

implicit class PimpedString(val self: String) extends AnyVal

wat tot gevolg heeft dat de oproepen naar uw extensiemethoden kunnen worden (en zullen) worden gestreept.


3
2018-02-10 21:04



Een andere zeer goede plaats om een ​​impliciete melding te doen is een begeleidend object.

Voor een impliciete conversie van Type1 naar Type2 Scala kijkt in metgezelobjecten van alle voorouders van beide typen. Het is dus vaak gemakkelijk om ergens een impliciete conversie te plaatsen, zodat deze niet expliciet hoeft te worden geïmporteerd.

case class Curie(curie:String)

object Curie {
  implicit def toCurie(s:String) = Curie(curie)
}

Waar je ook een methode noemt die vereist Curie met een String de conversiemethode wordt aangeroepen zonder extra import.


2
2017-09-07 17:51