Capítulo 1. Tareas desde la línea de comandos

Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com

Lo más probable es que uno de los primeros pasos en tu viaje con Scala 3 implique trabajar en la línea de comandos. Por ejemplo, después de instalar Scala como se muestra en "Instalación de Scala", es posible que quieras iniciar el REPL (Read/Eval/Print/Loop de Scala) escribiendo scala en la línea de comandos de tu sistema operativo. O puede que quieras crear un pequeño proyecto "Hola, mundo" de un solo archivo y luego compilarlo y ejecutarlo. Dado que muchas personas empezarán a trabajar con Scala en estas tareas de la línea de comandos, aquí se tratan en primer lugar.

El REPL es un intérprete de comandos. Es una zona de juegos donde puedes hacer pequeñas pruebas para ver cómo funcionan Scala y sus bibliotecas de terceros. Si estás familiarizado con el JShell de Java, el irb de Ruby, el shell de Python o IPython, o el ghci de Haskell, el REPL de Scala es similar a todos ellos. Como se muestra en la Figura 1-1, sólo tienes que iniciar el REPL escribiendo scala en la línea de comandos de tu sistema operativo, después escribe tus expresiones Scala y se evaluarán en el intérprete de comandos.

Siempre que quieras probar algún código Scala, la REPL es un entorno de juego estupendo. No es necesario crear un proyecto completo: basta con que pongas tu código de prueba en la REPL y experimentes con él hasta que sepas que funciona. Dado que la REPL es una herramienta tan importante, sus funciones más importantes se muestran en las dos primeras recetas de este capítulo.

Figura 1-1. El REPL de Scala 3 ejecutándose en una ventana de Terminal de macOS

Aunque el REPL es estupendo, no es el único juego de la ciudad. El REPL de Ammonite se creó originalmente para Scala 2, y tenía muchas más funciones que el REPL de Scala 2, entre ellas:

  • La posibilidad de importar código desde repositorios GitHub y Maven

  • La posibilidad de guardar y restaurar sesiones

  • Impresión bonita

  • Edición multilínea

En el momento de escribir esto, Ammonite todavía se está portando a Scala 3, pero muchas funciones importantes ya funcionan. Consulta la Receta 1.3 para ver ejemplos de cómo utilizar esas funciones.

Por último, cuando necesites compilar proyectos Scala, normalmente utilizarás una herramienta de compilación como sbt, que se muestra en el capítulo 17. Pero si alguna vez quieres compilar y ejecutar una pequeña aplicación Scala, como una que sólo tenga uno o dos archivos, puedes compilar tu código con el comando scalac y ejecutarlo con scala, igual que haces en Java con los comandos javac y java. Este proceso se demuestra en la Receta 1.4, y después, la Receta 1.6 muestra cómo puedes ejecutar aplicaciones que empaquetas como un archivo JAR con los comandos java o scala.

1.1 Primeros pasos con el REPL de Scala

Problema

Quieres empezar a utilizar el REPL de Scala y empezar a aprovechar algunas de sus funciones básicas.

Solución

Si has utilizado entornos REPL en lenguajes como Java, Python, Ruby y Haskell, el REPL de Scala te resultará muy familiar. Para iniciar la REPL, escribe scala en la línea de comandos de tu sistema operativo. Cuando se inicie el REPL, es posible que veas un mensaje inicial, seguido de la indicación scala>:

$ scala
Welcome to Scala 3.0
Type in expressions for evaluation. Or try :help.

scala> _

El prompt indica que ahora estás utilizando el REPL de Scala. Dentro del entorno REPL puedes probar todo tipo de experimentos y expresiones diferentes:

scala> val x = 1
x: Int = 1

scala> val y = 2
y: Int = 2

scala> x + y
res0: Int = 3

scala> val x = List(1, 2, 3)
x: List[Int] = List(1, 2, 3)

scala> x.sum
res1: Int = 6

Como se muestra en estos ejemplos:

  • Después de introducir tu comando, la salida de REPL muestra el resultado de tu expresión, incluida la información sobre el tipo de datos.

  • Si no asignas un nombre de variable, como en el tercer ejemplo, el REPL crea su propia variable, empezando por res0, luego res1, etc. Puedes utilizar estos nombres de variable como si los hubieras creado tú mismo:

scala> res1.getClass
res2: Class[Int] = int

scala> res1 + 2
res3: Int = 8

Tanto los desarrolladores principiantes como los experimentados escriben código en el REPL todos los días para ver rápidamente cómo funcionan las funciones de Scala y sus propios algoritmos.

Completar pestaña

Hay algunos trucos sencillos que pueden hacer más eficaz el uso del REPL. Uno de ellos es utilizar el tabulador para ver los métodos disponibles en un objeto. Para ver cómo funciona el completado con tabulador, escribe el número 1, luego un decimal y, a continuación, pulsa la tecla Tabulador. El REPL responde mostrando las decenas de métodos disponibles en una instancia de Int:

scala> 1.
!=                         finalize                   round
##                         floatValue                 self
%                          floor                      shortValue
&                          formatted                  sign
*                          getClass                   signum
many more here ...

También puedes limitar la lista de métodos que se muestran escribiendo la primera parte del nombre de un método y pulsando después la tecla Tabulador. Por ejemplo, si quieres ver todos los métodos disponibles en un List, escribe List(1). seguido de la tecla Tabulador, y verás más de doscientos métodos. Pero si sólo te interesan los métodos de un List que empiecen por los caracteres to, escribe List(1).to y luego pulsa Tabulador, y la salida se reducirá a estos métodos:

scala> List(1).to
to              toIndexedSeq    toList          toSet           toTraversable
toArray         toIterable      toMap           toStream        toVector
toBuffer        toIterator      toSeq           toString

Debate

Utilizo la REPL para crear muchos pequeños experimentos, y también me ayuda a entender algunas conversiones de tipo que Scala realiza automáticamente. Por ejemplo, cuando empecé a trabajar con Scala y escribí el siguiente código en la REPL, no sabía de qué tipo era la variable x:

scala> val x = (3, "Three", 3.0)
val x: (Int, String, Double) = (3,Three,3.0)

Con el REPL, es fácil ejecutar pruebas como ésta y luego llamar a getClass sobre una variable para ver su tipo:

scala> x.getClass
val res0: Class[? <: (Int, String, Double)] = class scala.Tuple3

Aunque parte de esa línea de resultados es difícil de leer cuando empiezas a trabajar con Scala, el texto de la parte derecha de = te permite saber que el tipo es una clase Tuple3.

También puedes utilizar el comando :type del REPL para ver información similar, aunque actualmente no muestra el nombre Tuple3:

scala> :type x
(Int, String, Double)

Sin embargo, suele ser útil en muchos otros casos:

scala> :type 1 + 1.1
Double

scala> :type List(1,2,3).map(_ * 2.5)
List[Double]

Aunque se trata de ejemplos sencillos, verás que el REPL es extremadamente útil cuando trabajes con código más complicado y bibliotecas con las que no estés familiarizado.

Iniciar la REPL dentro de sbt

También puedes iniciar una sesión REPL de Scala desde dentro del intérprete de comandos sbt. Como se muestra en la Receta 17.5, "Entender otros comandos de sbt", sólo tienes que iniciar el intérprete de comandos sbt dentro de un proyecto sbt:

$ sbt
MyProject> _

A partir de ahí, utiliza el comando console o consoleQuick:

MyProject> console
scala> _

El comando console compila los archivos de código fuente del proyecto, los coloca en el classpath e inicia la REPL. El comando consoleQuick inicia la REPL con las dependencias del proyecto en el classpath, pero sin compilar los archivos de código fuente del proyecto. Esta segunda opción es útil para cuando tu código no se compila, o cuando quieres probar algún código de prueba con tus dependencias (bibliotecas).

Ver también

Si te gusta la idea de un entorno REPL pero quieres probar alternativas al REPL por defecto, hay varias alternativas gratuitas estupendas:

  • El REPL Ammonite tiene más funciones que el REPL, y se demuestra en la Receta 1.3.

  • Scastie es una alternativa web al REPL que admite opciones sbt y te permite añadir bibliotecas externas a tu entorno.

  • ScalaFiddle también es una alternativa basada en la web.

  • Tanto el IDE IntelliJ IDEA como el IDE Visual Studio Code (VS Code) tienen hojas de trabajo, que son similares a la REPL.

1.2 Cargar código fuente y archivos JAR en el REPL

Problema

Tienes código Scala en un archivo de código fuente y quieres utilizar ese código en la REPL.

Solución

Utiliza el comando :load para leer archivos de código fuente en el entorno REPL. Por ejemplo, dado este código en un archivo llamado Person.scala, en un subdirectorio llamadomodelos:

class Person(val name: String):
    override def toString = name

puedes cargar ese código fuente en el entorno REPL en ejecución de la siguiente manera:

scala> :load models/Person.scala
// defined class Person

Una vez cargado el código en el REPL, puedes crear una nueva instancia de Person:

scala> val p = Person("Kenny")
val p: Person = Kenny

Ten en cuenta, sin embargo, que si tu código fuente tiene una declaración package:

// Dog.scala file
package animals
class Dog(val name: String)

el comando :load fallará:

scala> :load Dog.scala
1 |package foo
  |^^^
  |Illegal start of statement

Los archivos de código fuente no pueden utilizar paquetes en la REPL, por lo que en situaciones como ésta tendrás que compilarlos en un archivo JAR y luego incluirlos en el classpath cuando inicies la REPL. Por ejemplo, así es como utilizo la versión 0.2.0 de mi biblioteca Simple Test con la REPL de Scala 3:

// start the repl like this
$ scala -cp simpletest_3.0.0-0.2.0.jar

scala> import com.alvinalexander.simpletest.SimpleTest.* 

scala> isTrue(1 == 1)
true

En el momento de escribir esto, no puedes añadir un JAR a una sesión REPL que ya se esté ejecutando, pero es posible que esa función se añada en el futuro.

Debate

Otra cosa que conviene saber es que los archivos de clase compilados en el directorio actual se cargan automáticamente en el REPL. Por ejemplo, si pones este código en un archivo llamado Cat.scala y luego lo compilas con scalac, se creará un archivo Cat.class:

case class Cat(name: String)

Si inicias el REPL en el mismo directorio que ese archivo de clase, puedes crear un nuevo Cat:

scala> Cat("Morris")
val res0: Cat = Cat(Morris)

En los sistemas Unix puedes utilizar esta técnica para personalizar tu entorno REPL. Para ello, sigue estos pasos:

  1. Crea un subdirectorio en tu directorio personal llamado repl. En mi caso, creo este directorio como /Usuarios/al/repl. (Utiliza el nombre que prefieras para este directorio).

  2. Pon los archivos *.class que quieras en ese directorio.

  3. Crea un alias o un script de shell que puedas utilizar para iniciar el REPL en ese directorio.

En mi sistema puse un archivo llamado Repl.scala en mi directorio ~/repl, con estoscontenidos:

import sys.process.*

def clear = "clear".!
def cmd(cmd: String) = cmd.!!
def ls(dir: String) = println(cmd(s"ls -al $dir"))
def help =
    println("\n=== MY CONFIG ===")
    "cat /Users/Al/repl/Repl.scala".!

case class Person(name: String)
val nums = List(1, 2, 3)

Luego compilo ese código con scalac para crear sus archivos de clase en ese directorio. Luego creo y utilizo este alias para iniciar el REPL:

alias repl="cd ~/repl; scala; cd -"

Ese alias me mueve al directorio ~/repl, inicia la REPL y me devuelve a mi directorio actual cuando salgo de la REPL.

Como otro enfoque, puedes crear un script de shell llamado repl, hacerlo ejecutable y colocarlo en tu directorio ~/bin (o en cualquier otro lugar de tu PATH):

#!/bin/sh

cd ~/repl
scala

Como un script de shell se ejecuta en un subproceso, volverás a tu directorio original cuando salgas del REPL.

Utilizando este enfoque, tus métodos personalizados se cargarán en el REPL cuando se inicie, de modo que puedas utilizarlos dentro del intérprete de comandos scala:

clear       // clear the screen
cmd("ps")   // run the 'ps' command
ls(".")     // run 'ls' in the current directory
help        // displays my Repl.scala file as a form of help

Utiliza esta técnica para precargar cualquier otra definición personalizada que quieras utilizar en el REPL.

1.3 Primeros pasos con el REPL de Ammonite

Problema

Quieres empezar a utilizar el REPL de Ammonite, incluida la comprensión de algunas de sus funciones básicas.

Solución

El REPL de Ammonite funciona igual que el REPL de Scala: sólo tienes que descargarlo e instalarlo, y luego iniciarlo con su comando amm. Al igual que el REPL de Scala por defecto, evalúa expresiones Scala y asigna un nombre de variable si no le proporcionas uno:

@ val x = 1 + 1
x: Int = 2

@ 2 + 2
res0: Int = 4

Pero Ammonite tiene muchas funciones adicionales. Puedes cambiar el símbolo del sistema con este comando:

@ repl.prompt() = "yo: "

yo: _

A continuación, si tienes estas expresiones Scala en un archivo llamado Repl.scala, en un subdirectorio llamado foo:

import sys.process.*

def clear = "clear".!
def cmd(cmd: String) = cmd.!!
def ls(dir: String) = println(cmd(s"ls -al $dir"))

puedes importarlos a tu Ammonite REPL con este comando:

@ import $file.foo.Repl, Repl.* 

Entonces podrás utilizar esos métodos dentro de Ammonite:

clear        // clear the screen
cmd("ps")    // run the 'ps' command
ls("/tmp")   // use 'ls' to list files in /tmp

Del mismo modo, puedes importar un archivo JAR llamado simpletest_3.0.0-0.2.0.jar en un subdirectorio llamado foo a tu sesión REPL de amm utilizando la variable $cp de Ammonite:

// import the jar file
import $cp.foo.`simpletest_3.0.0-0.2.0.jar`

// use the library you imported
import com.alvinalexander.simpletest.SimpleTest.*
isTrue(1 == 1)

El comando import ivy te permite importar dependencias de Maven Central (y otros repositorios) y utilizarlas en tu shell actual:

yo: import $ivy.`org.jsoup:jsoup:1.13.1`
import $ivy.$

yo: import org.jsoup.Jsoup, org.jsoup.nodes.{Document, Element}
import org.jsoup.Jsoup

yo: val html = "<p>Hi!</p>"
html: String = "<p>Hi!</p>"

yo: val doc: Document = Jsoup.parse(html)
doc: Document = <html> ...

yo: doc.body.text
res2: String = "Hi!"

El comando time integrado en Ammonite te permite cronometrar el tiempo que tarda en ejecutarse tu código:

@ time(Thread.sleep(1_000))
res2: (Unit, FiniteDuration) = ((), 1003788992 nanoseconds)

La capacidad de autocompletar de Ammonite es impresionante. Sólo tienes que escribir una expresión como ésta y pulsar Tab después del decimal:

@ Seq("a").map(x => x.

Al hacerlo, Ammonite muestra una larga lista de métodos disponibles en x-que es un String-empezando por estos métodos:

def intern(): String
def charAt(x$0: Int): Char
def concat(x$0: String): String
much more output here ...

Esto está bien porque te muestra no sólo los nombres de los métodos, sino también sus parámetros de entrada y su tipo de retorno.

Debate

La lista de funciones de Ammonite es larga. Otra muy buena es que puedes utilizar un archivo de configuración de inicio, igual que utilizas un archivo de inicio .bashrc o .bash_profile de Unix. Sólo tienes que poner algunas expresiones en un archivo ~/.ammonite/predef.sc:

import sys.process.*

repl.prompt() = "yo: "
def clear = "clear".!
def cmd(cmd: String) = cmd.!!
def ls(dir: String) = println(cmd(s"ls -al $dir"))
def reset = repl.sess.load()  // similar to the scala repl ':reset' command

Entonces, cuando inicies el REPL de Ammonite, tu prompt cambiará a yo:, y esos otros métodos estarán disponibles para ti.

Otra gran característica es que puedes guardar una sesión REPL, y guardará todo lo que hayas hecho hasta ese momento. Para probarlo, crea una variable en el REPL y luego guarda la sesión:

val remember = 42
repl.sess.save()

A continuación, crea otra variable:

val forget = 0

Ahora vuelve a cargar la sesión, y verás que la variable remember sigue disponible, pero la variable forget se ha olvidado, como deseabas:

@ repl.sess.load()
res3: SessionChanged = SessionChanged(removedImports = Set('forget),
addedImports = Set(), removedJars = Set(), addedJars = Set())

@ remember
res4: Int = 42

@ forget
   |val res5 = forget
   |           ^^
   |           Not found: forget

También puedes guardar y cargar varias sesiones dándoles nombres diferentes, así:

// do some work
val x = 1
repl.sess.save("step 1")

// do some more work
val y = 2
repl.sess.save("step 2")

// reload the first session
repl.sess.load("step 1")

x   // this will be found
y   // this will not be found

Consulta la documentación de Ammonite para conocer másfunciones.

1.4 Compilar con scalac y ejecutar con scala

Problema

Aunque normalmente utilizarás una herramienta de compilación como sbt o Mill para compilar aplicaciones Scala, en ocasiones puede que quieras utilizar herramientas más básicas para compilar y ejecutar pequeños programas de prueba, del mismo modo que podrías utilizar javac y java con pequeñas aplicaciones Java.

Solución

Compila pequeños programas con scalac, y ejecútalos con scala. Por ejemplo, dado este archivo de código fuente Scala llamado Hello.scala:

@main def hello = println("Hello, world")

compílalo en la línea de comandos con scalac:

$ scalac Hello.scala

A continuación, ejecútalo con scala, dando al comando scala el nombre del método @main que has creado:

$ scala hello
Hello, world

Debate

Compilar y ejecutar clases es igual que en Java, incluidos conceptos como el classpath. Por ejemplo, imagina que tienes una clase llamada Pizza en un archivo llamado Pizza.scala, y que depende de un tipo Topping:

class Pizza(val toppings: Topping*):
    override def toString = toppings.toString

Suponiendo que Topping se defina así:

enum Topping:
    case Cheese, Mushrooms

y que está en un archivo llamado Topping .scala, y que se ha compilado en Topping.class en un subdirectorio llamado clases, compila Pizza.scala de la siguiente manera:

$ scalac -classpath classes Pizza.scala

Ten en cuenta que el comando scalac tiene muchas opciones adicionales que puedes utilizar. Por ejemplo, si añades la opción -verbose al comando anterior, verás cientos de líneas de salida adicionales que muestran cómo está funcionando scalac. Estas opciones pueden cambiar con el tiempo, así que utiliza la opción -help para ver información adicional:

$ scalac -help

Usage: scalac <options> <source files>
where possible standard options include:
-P               Pass an option to a plugin, e.g. -P:<plugin>:<opt>
-X               Print a synopsis of advanced options.
-Y               Print a synopsis of private options.
-bootclasspath   Override location of bootstrap class files.
-classpath       Specify where to find user class files.

much more output here ...

Métodos principales

Mientras hablamos de compilar métodos main, es útil saber que se pueden declarar de dos formas con Scala 3:

  • Utilizar la anotación @main en un método

  • Declarar un método main con la firma adecuada en un método object

Como se muestra en la Solución, un simple método @main que no toma parámetros de entrada puede declararse así:

@main def hello = println("Hello, world")

También puedes declarar un método @main para que tome los parámetros que quieras en la línea de comandos, como tomar un String y un Int en este ejemplo:

@main def hello(name: String, age: Int): Unit =
    println(s"Hello, $name, I think you are $age years old.")

Después de compilar ese código con scalac, se puede ejecutar así:

$ scala hello "Lori" 44
Hello, Lori, I think you are 44 years old.

Para el segundo enfoque, declarar un método main dentro de un object es igual que declarar un método main en Java, y la firma del método main de Scala debe tener este aspecto:

object YourObjectName:
    // the method must take `Array[String]` and return `Unit`
    def main(args: Array[String]): Unit =
        // your code here

Si estás familiarizado con Java, ese código Scala es análogo a este código Java:

public class YourObjectName {
    public static void main(String[] args) {
        // your code here
    }
}

1.5 Desensamblar y descompilar código Scala

Problema

En el proceso de aprender cómo se compila el código Scala en archivos de clase, o intentando comprender un problema concreto, querrás examinar el bytecode que genera el compilador Scala a partir de tu código fuente.

Solución

La principal forma de desensamblar código Scala es con el comando javap. También puedes utilizar un descompilador para convertir tus archivos de clase de nuevo en código fuente Java, y esta opción se muestra en la Discusión.

Utilizar javap

Como tus archivos de código fuente Scala se compilan en archivos de clase JVM normales, puedes utilizar el comando javap para desensamblarlos. Por ejemplo, supongamos que has creado un archivo llamado Person.scala que contiene este código fuente:

class Person(var name: String, var age: Int)

A continuación, compila ese archivo con scalac:

$ scalac Person.scala

Ahora puedes desensamblar el archivo Person.class resultante en su firma utilizando javap, así:

$ javap -public Person
Compiled from "Person.scala"
public class Person {
  public Person(java.lang.String, int);
  public java.lang.String name();
  public void name_$eq(java.lang.String);
  public int age();
  public void age_$eq(int);
}

Esto muestra la firma pública de la clase Person, que es su API pública, o interfaz. Incluso en un ejemplo sencillo como éste puedes ver cómo el compilador de Scala hace su trabajo por ti, creando métodos como name(), name_$eq, age(), y age_$eq. La Discusión muestra ejemplos más detallados.

Si quieres, puedes ver información adicional con la opción javap -private:

$ javap -private Person
Compiled from "Person.scala"
public class Person {
  private java.lang.String name;   // new
  private int age;                 // new
  public Person(java.lang.String, int);
  public java.lang.String name();
  public void name_$eq(java.lang.String);
  public int age();
  public void age_$eq(int);
}

El javap tiene varias opciones más que son útiles. Utiliza la opción -c para ver los comandos reales que componen el código de bytes de Java, y añádele la opción -verbose para ver muchos más detalles. Ejecuta javap -help para ver los detalles de todas las opciones.

Debate

Desensamblar archivos de clase con javap puede ser una forma útil de entender cómo funciona Scala. Como has visto en el primer ejemplo con la clase Person, definir los parámetros del constructor name y age como campos var te genera bastantes métodos.

Como segundo ejemplo, quita el atributo var a esos dos campos, de modo que tengas esta definición de clase:

class Person(name: String, age: Int)

Compila esta clase con scalac, y luego ejecuta javap en el archivo de clase resultante. Verás que el resultado es una firma de clase mucho más corta:

$ javap -public Person
Compiled from "Person.scala"
public class Person {
  public Person(java.lang.String, int);
}

Por el contrario, dejar var en ambos campos y convertir la clase en una clase case amplía significativamente la cantidad de código que Scala genera en tu nombre. Para ver esto, cambia el código en Person.scala para que tengas esta clase case:

case class Person(var name: String, var age: Int)

Cuando compilas este código, crea dos archivos de salida, Persona.class y Person$.class. Desensambla esos dos archivos utilizando javap:

$ javap -public Person
Compiled from "Person.scala"
public class Person implements scala.Product,java.io.Serializable {
  public static Person apply(java.lang.String, int);
  public static Person fromProduct(scala.Product);
  public static Person unapply(Person);
  public Person(java.lang.String, int);
  public scala.collection.Iterator productIterator();
  public scala.collection.Iterator productElementNames();
  public int hashCode();
  public boolean equals(java.lang.Object);
  public java.lang.String toString();
  public boolean canEqual(java.lang.Object);
  public int productArity();
  public java.lang.String productPrefix();
  public java.lang.Object productElement(int);
  public java.lang.String productElementName(int);
  public java.lang.String name();
  public void name_$eq(java.lang.String);
  public int age();
  public void age_$eq(int);
  public Person copy(java.lang.String, int);
  public java.lang.String copy$default$1();
  public int copy$default$2();
  public java.lang.String _1();
  public int _2();
}

$ javap -public Person$
Compiled from "Person.scala"
public final class Person$ implements scala.deriving.Mirror$Product,
java.io.Serializable {
  public static final Person$ MODULE$;
  public static {};
  public Person apply(java.lang.String, int);
  public Person unapply(Person);
  public java.lang.String toString();
  public Person fromProduct(scala.Product);
  public java.lang.Object fromProduct(scala.Product);
}

Como se muestra, cuando defines una clase como clase case, Scala genera mucho código para ti, y esta salida muestra la firma pública de ese código. Consulta la Receta 5.14, "Generar código boilerplate con clases case", para una discusión detallada de este código.

Acerca de esos archivos .tasty

Te habrás dado cuenta de que, además de archivos .class, Scala 3 también genera archivos .tasty durante el proceso de compilación. Estos archivos se generan en lo que se conoce como formato TASTy, donde las siglas TASTy provienen del término árboles sintácticos abstractos tipados.

En cuanto a lo que son estos archivos, la documentación de la Inspección TASTy afirma: "Los archivos TASTy contienen el árbol tipado completo de una clase, incluidas las posiciones fuente y la documentación. Esto es ideal para herramientas que analizan o extraen información semántica del código".

Uno de sus usos es para la integración entre Scala 3 y Scala 2.13+. Como se indica en esta página de compatibilidad futura de Scala, "Scala 2.13 puede leer estos archivos (TASTy) para saber, por ejemplo, qué términos, tipos e implícitos están definidos en una determinada dependencia, y qué código hay que generar para utilizarlos correctamente. La parte del compilador que gestiona esto se conoce como Tasty Reader".

Ver también

  • En mi entrada del blog "Cómo crear métodos en línea en Scala 3", muestro cómo utilizar esta técnica para entender los métodos de inline.

  • También puedes utilizar descompiladores para convertir archivos .class en código Java. Yo utilizo de vez en cuando una herramienta llamada JAD, que dejó de fabricarse en 2001, pero que sorprendentemente sigue siendo capaz de descompilar al menos parcialmente archivos de clase veinte años después. También se mencionó un descompilador mucho más moderno llamado CFR en el canal Gitter de Scala.

Para más información sobre TASTy y los archivos .tasty, consulta estos recursos:

1.6 Ejecutar archivos JAR con Scala y Java

Problema

Has creado un archivo JAR a partir de una aplicación Scala y quieres ejecutarlo utilizando los comandos scala o java.

Solución

Primero, crea un proyecto sbt básico, como se muestra en la Receta 17.1, "Crear una estructura de directorios de proyecto para sbt". A continuación, añade sbt-assembly a la configuración del proyecto añadiendo esta línea al archivo project/plugins.sbt:

// note: this version number changes several times a year
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0")

A continuación, coloca este archivo de código fuente Hello.scala en el directorio raíz de ese proyecto:

@main def hello = println("Hello, world")

A continuación, crea un archivo JAR con el comando assembly o show assembly en el intérprete de comandos sbt:

// option 1
sbt:RunJarFile> assembly

// option 2: shows the output file location
sbt:RunJarFile> show assembly
[info] target/scala-3.0.0/RunJarFile-assembly-0.1.0.jar

Como se muestra, la salida del comando show assembly imprime la ubicación donde se escribe el archivo JAR de salida. Este archivo comienza con el nombre RunJarFile porque ése es el valor del campo name de mi archivo build.sbt. Del mismo modo, la parte 0.1.0 del nombre del archivo procede del campo version de ese archivo:

lazy val root = project
   .in(file("."))
   .settings(
      name := "RunJarFile",
      version := "0.1.0",
      scalaVersion := "3.0.0"
  )

A continuación, crea un subdirectorio Ejemplo, desplázate a ese directorio y copia el archivo JAR en ese directorio:

$ mkdir Example
$ cd Example
$ cp ../target/scala-3.0.0/RunJarFile-assembly-0.1.0.jar .

Como el complemento sbt-assembly empaqueta todo lo que necesitas en el archivo JAR, puedes ejecutar el método main de hello con este comando scala:

$ scala -cp "RunJarFile-assembly-0.1.0.jar" hello
Hello, world

Ten en cuenta que si tu archivo JAR contiene varios métodos @main en paquetes, puedes ejecutarlos con comandos similares, especificando la ruta completa a los métodos al final del comando:

scala -cp "RunJarFile-assembly-0.1.0.jar" com.alvinalexander.foo.mainMethod1
scala -cp "RunJarFile-assembly-0.1.0.jar" com.alvinalexander.bar.mainMethod2

Debate

Si (a) intentas ejecutar tu archivo JAR con el comando java, o (b) creas el archivo JAR con sbt package en lugar de sbt assembly, tendrás que añadir manualmente las dependencias de tu archivo JAR a tu classpath. Por ejemplo, cuando ejecutes un archivo JAR con el comando java, tendrás que utilizar un comando como este

$ java -cp "~/bin/scala3/lib/scala-library.jar:my-packaged-jar-file.jar"foo.bar.Hello
Hello, world

Ten en cuenta que todo el comando java debe estar en una línea, incluida la parte foo.bar.Hello al final de la línea.

Para este enfoque necesitas encontrar el archivo scala-library.jar. En mi caso, como administro la distribución de Scala 3 manualmente, lo encontré en el directorio que se muestra. Si utilizas una herramienta como Coursier para gestionar tu instalación de Scala, los archivos que descarga se encuentran en estos directorios:

  • En macOS: ~/Library/Caches/Coursier/v1

  • En Linux: ~/.cache/coursier/v1

  • En Windows: %LOCALAPPDATA%\Coursier\Cache\v1, que, para un usuario llamado Alvin, suele corresponder a C:\Usuarios\Alvin\AppData\Local\Coursier\Cache\v1

Consulta la página Coursier Cache para obtener información actualizada sobre la ubicación de estos directorios.

¿Por qué utilizar sbt-assembly?

Ten en cuenta que si tu aplicación utiliza dependencias gestionadas o no gestionadas y utilizas sbt package en lugar de sbt assembly, tendrás que entender todas esas dependencias y sus dependencias transitivas, encontrar esos archivos JAR y luego incluirlos en la configuración del classpath. Por ello, se recomienda encarecidamente el uso de sbt assembly o de una herramienta similar.

Ver también

Get Scala Cookbook, 2ª Edición now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.