Pregunta ¿Es la biblioteca de colecciones Scala 2.8 un caso de "la nota de suicidio más larga de la historia"? [cerrado]


Acabo de comenzar a mirar el Reinstalación de la biblioteca de colecciones Scala que viene en el inminente 2.8 lanzamiento. Quienes estén familiarizados con la biblioteca de 2.7 notarán que la biblioteca, desde una perspectiva de uso, ha cambiado poco. Por ejemplo...

> List("Paris", "London").map(_.length)
res0: List[Int] List(5, 6)

... funcionaría en cualquiera de las versiones. La biblioteca es eminentemente utilizable: de hecho, es fantástico. Sin embargo, aquellos que anteriormente no estaban familiarizados con Scala y hurgando para tener una idea del lenguaje ahora tiene que dar sentido a las firmas de métodos como:

def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That

Para una funcionalidad tan simple, esta es una firma desalentadora que me cuesta entender. No es que creo que Scala haya sido probablemente el próximo Java (o / C / C ++ / C #) - No creo que sus creadores lo apuntaran a ese mercado, pero creo que es / era ciertamente factible para Scala convertirse en el próximo Ruby o Python (es decir, para obtener un usuario comercial significativo) -base)

  • ¿Esto va a hacer que la gente no vaya a Scala?
  • ¿Esto le dará a Scala un mal nombre en el mundo comercial como juguete académico que solo los estudiantes de doctorado pueden entender? Son CTOs y los jefes de software se van a espantar?
  • ¿El rediseño de la biblioteca fue una idea sensata?
  • Si está usando Scala comercialmente, ¿le preocupa esto? ¿Estás planeando adoptar 2.8 inmediatamente o esperar a ver qué pasa?

Steve Yegge  una vez atacado Scala (equivocadamente en mi opinión) por lo que él vio como su sistema de tipo demasiado complicado. Me preocupa que alguien tenga un día de campo extendiéndose FUD con esta API (de manera similar a como Josh Bloch asustó al JCP de agregar cierres a Java).

Nota - Debo aclarar eso, aunque creo que Joshua Bloch fue influyente en el rechazo de la propuesta de clausuras BGGA, no atribuyo esto a nada más que sus creencias honestas de que la propuesta representaba un error.


A pesar de lo que sea que mi esposa y mis compañeros de trabajo sigan diciéndome, no creo que sea un idiota: tengo un buen título en matemáticas del Universidad de Oxford, y he estado programando comercialmente por casi 12 años y en Scala durante aproximadamente un año (también comercialmente).

Tenga en cuenta que el título del tema inflamatorio es una cita hecha sobre el manifiesto de un partido político del Reino Unido a principios de la década de 1980. Esta pregunta es subjetiva, pero es una pregunta genuina, la hice CW y me gustaría tener algunas opiniones al respecto.


809


origen


Respuestas:


Espero que no sea una "nota de suicidio", pero puedo ver tu punto. Entraste en lo que a la vez es una fortaleza y un problema de Scala: es extensibilidad. Esto nos permite implementar la mayoría de las principales funcionalidades en las bibliotecas. En algunos otros idiomas, secuencias con algo así como mapo collect se integraría, y nadie tiene que ver todos los obstáculos que el compilador tiene que pasar para que funcionen sin problemas. En Scala, todo está en una biblioteca y, por lo tanto, a la vista.

De hecho, la funcionalidad de map eso es compatible con su tipo complicado es bastante avanzado. Considera esto:

scala> import collection.immutable.BitSet
import collection.immutable.BitSet

scala> val bits = BitSet(1, 2, 3)
bits: scala.collection.immutable.BitSet = BitSet(1, 2, 3)

scala> val shifted = bits map { _ + 1 }
shifted: scala.collection.immutable.BitSet = BitSet(2, 3, 4)

scala> val displayed = bits map { _.toString + "!" }
displayed: scala.collection.immutable.Set[java.lang.String] = Set(1!, 2!, 3!)

¿Ves cómo siempre obtienes el mejor tipo posible? Si mapea Ints a Ints obtienes otra vez BitSet, pero si mapea Ints a Strings, obtienes un general Set. Tanto el tipo estático como la representación en tiempo de ejecución del resultado del mapa dependen del tipo de resultado de la función que se le pasa. ¡Y esto funciona incluso si el conjunto está vacío, por lo que la función nunca se aplica! Hasta donde sé, no hay otro marco de recopilación con una funcionalidad equivalente. Sin embargo, desde la perspectiva del usuario, así es como están las cosas supuesto trabajar.

El problema que tenemos es que toda la tecnología inteligente que hace que esto suceda se filtra a las firmas de tipos que se vuelven grandes y atemorizantes. Pero tal vez un usuario no debería mostrarse por defecto la firma de tipo completo de map? ¿Qué tal si miraba hacia arriba? map en BitSet ella tiene:

map(f: Int => Int): BitSet     (click here for more general type)

Los documentos no se encontrarían en ese caso, porque desde la perspectiva del usuario, de hecho, el mapa tiene el tipo (Int => Int) => BitSet. Pero map también tiene un tipo más general que se puede inspeccionar haciendo clic en otro enlace.

Todavía no hemos implementado una funcionalidad como esta en nuestras herramientas. Pero creo que tenemos que hacer esto para evitar asustar a la gente y brindar información más útil. Con herramientas como esa, esperamos que los marcos inteligentes y las bibliotecas no se conviertan en notas suicidas.


828



No tengo un doctorado, ni ningún otro tipo de título ni en CS ni en matemáticas ni en ningún otro campo. No tengo experiencia previa con Scala ni ningún otro lenguaje similar. No tengo experiencia con sistemas de tipo ni remotamente comparables. De hecho, el único idioma que tengo es más que un conocimiento superficial del que incluso tiene un sistema de tipo es Pascal, no exactamente conocido por su sistema de tipo sofisticado. (A pesar de esto hace tienen tipos de rango, que AFAIK prácticamente no tiene otro lenguaje, pero eso no es realmente relevante aquí.) Los otros tres idiomas que conozco son BASIC, Smalltalk y Ruby, ninguno de los cuales tiene un sistema de tipos.

Y, sin embargo, no tengo problemas para entender la firma del map función que publicaste Me parece más o menos la misma firma que map tiene en todos los otros idiomas que he visto. La diferencia es que esta versión es más genérica. Se parece más a una C ++ STL que, por ejemplo, a Haskell. En particular, se abstrae del tipo de colección concreta al solo requerir que el argumento sea IterableLike, y también abstrae del tipo de retorno concreto al solo requerir que exista una función de conversión implícita que pueda construir alguna cosa fuera de esa colección de valores de resultado. Sí, eso es bastante complejo, pero en realidad solo es una expresión del paradigma general de la programación genérica: no asumas nada que no tengas que hacer.

En este caso, map en realidad no necesitar la colección es una lista, o se ordena o se puede ordenar o algo por el estilo. Lo único que map se preocupa porque puede acceder a todos los elementos de la colección, uno después del otro, pero sin ningún orden en particular. Y no necesita saber cuál es la colección resultante, solo necesita saber cómo crearla. Entonces, eso es lo que su tipo de firma requiere.

Entonces, en lugar de

map :: (a → b) → [a] → [b]

que es la firma de tipo tradicional para map, se generaliza para no requerir un concreto List sino más bien solo una IterableLike estructura de datos

map :: (IterableLike i, IterableLike j) ⇒ (a → b) → i → j

que luego se generaliza solo requiriendo que exista una función que pueda convertir el resultado para cualquier estructura de datos que el usuario desee:

map :: IterableLike i ⇒ (a → b) → i → ([b] → c) → c

Admito que la sintaxis es un poco más ruidosa, pero la semántica es la misma. Básicamente, comienza desde

def map[B](f: (A) ⇒ B): List[B]

que es la firma tradicional para map. (Observe cómo debido a la naturaleza orientada a objetos de Scala, el parámetro de lista de entrada desaparece, porque ahora es el parámetro de receptor implícito que tiene cada método en un sistema OO de envío único.) Luego se generalizó a partir de un List a un más general IterableLike

def map[B](f: (A) ⇒ B): IterableLike[B]

Ahora, reemplaza el IterableLike resultado colección con una función que produce, bueno, realmente casi cualquier cosa.

def map[B, That](f: A ⇒ B)(implicit bf: CanBuildFrom[Repr, B, That]): That

Lo que realmente creo que no es ese difícil de entender. En realidad, solo necesita un par de herramientas intelectuales:

  1. Necesitas saber (más o menos) qué map es. Si tu diste solamente la firma tipo sin el nombre del método, lo admito, sería mucho más difícil descubrir qué está pasando. Pero ya que tú ya saber qué map se supone que es así, y usted sabe cuál es el tipo de firma que se supone que es, puede escanear rápidamente la firma y enfocarse en las anomalías, como "por qué esto map tomar dos funciones como argumentos, ¿no una? "
  2. Necesita poder realmente leer la firma de tipo. Pero incluso si nunca antes ha visto a Scala, esto debería ser bastante fácil, ya que en realidad es solo una mezcla de sintaxis de tipos que ya conoce de otros lenguajes: VB.NET usa corchetes para el polimorfismo paramétrico y usa una flecha para indicar el el tipo de devolución y dos puntos para separar el nombre y el tipo, en realidad es la norma.
  3. Necesita saber aproximadamente de qué se trata la programación genérica. (Que no es ese difícil de entender, ya que básicamente está todo escrito en el nombre: es literalmente solo programación en una forma genérica).

Ninguno de estos tres debe dar un dolor de cabeza serio a ningún programador profesional o incluso aficionado. map ha sido una función estándar en casi todos los lenguajes diseñados en los últimos 50 años, el hecho de que diferentes lenguajes tengan una sintaxis diferente debería ser obvio para cualquiera que haya diseñado un sitio web con HTML y CSS, y usted no puede suscribirse a una programación remota lista de correo relacionada sin un molesto fanático de C ++ de la iglesia de San Stepanov explicando las virtudes de la programación genérica.

Sí, Scala es complejo. Sí, Scala tiene uno de los sistemas de tipo más sofisticado conocido por el hombre, rivalizando e incluso superando idiomas como Haskell, Miranda, Clean o Cyclone. Pero si la complejidad fuera un argumento en contra del éxito de un lenguaje de programación, C ++ habría muerto hace mucho tiempo y todos estaríamos escribiendo Scheme. Hay muchas razones por las cuales Scala probablemente no tenga éxito, pero el hecho de que los programadores no se pueden tomar la molestia de encender sus cerebros antes de sentarse frente al teclado probablemente no sea el principal.


206



Lo mismo en C ++:

template <template <class, class> class C,
          class T,
          class A,
          class T_return,
          class T_arg
              >
C<T_return, typename A::rebind<T_return>::other>
map(C<T, A> &c,T_return(*func)(T_arg) )
{
    C<T_return, typename A::rebind<T_return>::other> res;
    for ( C<T,A>::iterator it=c.begin() ; it != c.end(); it++ ){
        res.push_back(func(*it));
    }
    return res;
}

163



Bueno, puedo entender tu dolor, pero, francamente, la gente como tú y yo, o casi cualquier usuario habitual de Stack Overflow, no es la regla.

Lo que quiero decir con eso es que ... a la mayoría de los programadores no les importará ese tipo de firma, porque ellos nunca los verán! No leen la documentación.

Siempre y cuando vean un ejemplo de cómo funciona el código, y el código no les falla al producir el resultado, esperar, nunca mirarán la documentación. Cuando eso falla, mirarán la documentación y esperan ver ejemplos de uso en la cima.

Con estas cosas en mente, creo que:

  1. Cualquiera (como en la mayoría de las personas) que se encuentre con esa firma de tipo se burlará de Scala sin fin si están predispuestos en su contra, y lo considerarán un símbolo del poder de Scala si les gusta Scala.

  2. Si la documentación no se mejora para proporcionar ejemplos de uso y explicar claramente para qué es un método y cómo usarlo, puede restarle un poco la adopción de Scala.

  3. A la larga, no importará. Esa Scala poder Hacer cosas así hará que las bibliotecas escritas para Scala sean mucho más potentes y seguras de usar. Estas bibliotecas y marcos atraerán a programadores atraídos por herramientas poderosas.

  4. Los programadores a los que les gusta la simplicidad y la franqueza seguirán usando PHP o idiomas similares.

Por desgracia, los programadores de Java están muy interesados ​​en las herramientas de poder, así que, al responder eso, acabo de revisar mi expectativa de la adopción de la corriente principal de Scala. No tengo ninguna duda de que Scala se convertirá en un idioma general. No es C-mainstream, sino quizás Perl-mainstream o PHP-mainstream.

Hablando de Java, ¿alguna vez reemplazaste el cargador de clases? ¿Alguna vez has visto lo que eso implica? Java puede ser aterrador, si nos fijamos en los lugares que los escritores de framework hacen. Es solo que la mayoría de las personas no. Lo mismo se aplica a Scala, en mi humilde opinión, pero los primeros usuarios tienden a mirar debajo de cada roca que encuentran, para ver si hay algo escondido allí.


67



¿Esto va a hacer que la gente no vaya a Scala?

Sí, pero también evitará que la gente se desanime. Considero que la falta de colecciones que utilizan tipos de mayor nivel es una gran debilidad desde que Scala obtuvo soporte para tipos de alto nivel. Hace que los documentos API sean más complicados, pero realmente hace que el uso sea más natural.

¿Esto le dará a scala un mal nombre en el mundo comercial como un juguete académico que solo los estudiantes de doctorado pueden entender? ¿Los CTO y los jefes de software se van a espantar?

Algunos probablemente lo harán. No creo que Scala sea accesible para muchos desarrolladores "profesionales", en parte debido a la complejidad de Scala y en parte a la falta de voluntad de muchos desarrolladores para aprender. Los CTO que emplean a tales desarrolladores estarán asustados con razón.

¿El rediseño de la biblioteca fue una idea sensata?

Absolutamente. Hace que las colecciones se ajusten mucho mejor con el resto del lenguaje y el sistema de tipos, incluso si todavía tiene algunos bordes irregulares.

Si está usando Scala comercialmente, ¿le preocupa esto? ¿Estás planeando adoptar 2.8 inmediatamente o esperar a ver qué pasa?

No lo estoy usando comercialmente. Probablemente esperaré hasta al menos un par de revoluciones en la serie 2.8.x incluso antes de tratar de introducirlo para que los errores puedan eliminarse. También esperaré para ver cuánto éxito tiene EPFL en mejorar su desarrollo y procesos de lanzamiento. Lo que veo parece prometedor, pero trabajo para una empresa conservadora.

Uno de los temas más generales de "¿Scala es demasiado complicado para los desarrolladores convencionales?" ...

La mayoría de los desarrolladores, convencionales o de otro tipo, mantienen o amplían los sistemas existentes. Esto significa que la mayor parte de lo que usan viene dictada por decisiones tomadas hace mucho tiempo. Todavía hay mucha gente escribiendo COBOL.

El desarrollador principal de mañana trabajará manteniendo y extendiendo las aplicaciones que se están construyendo hoy. Muchas de estas aplicaciones no están siendo desarrolladas por desarrolladores convencionales. Los principales desarrolladores de mañana utilizarán el lenguaje que utilizan los desarrolladores más exitosos de nuevas aplicaciones.


52



Una forma en que la comunidad de Scala puede ayudar a aliviar el temor de los programadores noveles de Scala es centrarse en la práctica y enseñar con el ejemplo: una gran cantidad de ejemplos que comienzan de a poco y crecen gradualmente. Aquí hay algunos sitios que tienen este enfoque:

Después de pasar un tiempo en estos sitios, uno se da cuenta rápidamente de que Scala y sus bibliotecas, aunque quizás sean difíciles de diseñar e implementar, no son tan difíciles de usar, especialmente en los casos comunes.


46



Tengo una licenciatura de una universidad estadounidense barata de "mercado masivo", así que diría que caigo en el medio de la escala de inteligencia de los usuarios (o al menos educación) :) He estado jugando con Scala solo por unos pocos meses y he trabajado en dos o tres aplicaciones no triviales.

Especialmente ahora que IntelliJ ha lanzado su IDE fino con lo que en mi humilde opinión es actualmente el mejor plugin de Scala, el desarrollo de Scala es relativamente sencillo:

  • Encuentro que puedo usar Scala como "Java sin punto y coma", es decir, escribo un código de aspecto similar a lo que haría en Java, y me beneficio un poco de la brevedad sintáctica, como la que se obtiene mediante la inferencia de tipo. El manejo de excepciones, cuando lo hago, es más conveniente. La definición de la clase es mucho menos detallada sin la repetición getter / setter.

  • De vez en cuando logro escribir una sola línea para lograr el equivalente de varias líneas de Java. Donde corresponda, las cadenas de métodos funcionales como mapear, doblar, recolectar, filtrar, etc. son divertidas de componer y elegantes de contemplar.

  • Solo en raras ocasiones me encuentro beneficiándome de las funciones de mayor potencia de Scala: cierres y funciones parciales (o currículum), coincidencia de patrones ... algo así.

Como novato, sigo luchando con la sintaxis escueta e idiomática. Las llamadas a métodos sin parámetros no necesitan paréntesis, excepto donde sí lo hacen; los casos en la declaración del partido necesitan una flecha adiposa ( => ), pero también hay lugares donde se necesita una flecha delgada ( -> ) Muchos métodos tienen nombres cortos pero bastante crípticos, como /:o \: - Puedo hacer mis cosas si cambio suficientes páginas de manual, pero parte de mi código termina pareciéndose a Perl o ruido de línea. Irónicamente, uno de los trozos más populares de taquigrafía sintáctica falta en acción: me sigue mordiendo el hecho de que Int no define un ++ método.

Esta es solo mi opinión: siento que Scala tiene el poder de C ++ combinado con la complejidad y legibilidad de C ++. La complejidad sintáctica del lenguaje también dificulta la lectura de la documentación de la API.

Scala es muy bien pensado y brillante en muchos aspectos. Sospecho que a muchos académicos les encantaría programar en él. Sin embargo, también está lleno de astucia e inteligencia, tiene una curva de aprendizaje mucho más alta que Java y es más difícil de leer. Si exploro los foros y veo cuántos desarrolladores todavía están luchando con los puntos más finos de Java, No puedo concebir que Scala se haya convertido alguna vez en un lenguaje convencional. Ninguna empresa podrá justificar el envío de sus desarrolladores a un curso de Scala de 3 semanas cuando anteriormente solo necesitaban un curso de Java de 1 semana.


40



Creo que el principal problema con ese método es que el (implicit bf : CanBuildFrom[Repr, B, That]) va sin ninguna explicación. Aunque sé cuáles son los argumentos implícitos, no hay nada que indique cómo esto afecta la llamada. Perseguir el scaladoc solo me deja más confundido (pocas de las clases relacionadas con CanBuildFrom incluso tener documentación).

Creo que un simple "debe haber un objeto implícito en el alcance de bf que proporciona un constructor para objetos de tipo B en el tipo de retorno That"ayudaría un poco, pero es un concepto embriagador cuando todo lo que realmente quieres hacer es mapear Aes a Bes De hecho, no estoy seguro de que sea correcto, porque no sé de qué tipo Repr significa, y la documentación para Traversable ciertamente no da ninguna pista en absoluto.

Entonces, me quedan dos opciones, ninguna de ellas agradable:

  • Asuma que solo funcionará cómo funciona el mapa antiguo y cómo funciona el mapa en la mayoría de los otros idiomas
  • Adéntrate en el código fuente un poco más

Entiendo que Scala esencialmente está exponiendo las agallas de cómo funcionan estas cosas y que, en última instancia, esto es una manera de hacer lo que oxbow_lakes está describiendo. Pero es una distracción en la firma.


32



Soy un principiante de Scala y sinceramente no veo ningún problema con esa firma de tipo. El parámetro es la función para mapear y el parámetro implícito del constructor para devolver la colección correcta. Claro y legible

Todo es bastante elegante, en realidad. Los parámetros de tipo de constructor permiten que el compilador elija el tipo de retorno correcto mientras que el mecanismo de parámetro implícito oculta este parámetro adicional del usuario de la clase. Intenté esto:

Map(1 -> "a", 2 -> "b").map((t) => (t._2) -> (t._1)) // returns Map("a" -> 1, "b" -> 2)
Map(1 -> "a", 2 -> "b").map((t) =>  t._2)            // returns List("a", "b")

Eso es polimorfismo hecho bien.

Ahora, concedido, no es un paradigma convencional y ahuyentará a muchos. Pero, también atraerá a muchos que valoran su expresividad y elegancia.


22