Статьи

Вопросы студентов о Scala, часть 2

Предисловие

Это второй пост, отвечающий на вопросы студентов из моего курса по прикладному анализу текста. Вы можете увидеть первый здесь . Этот пост обычно охватывает вопросы более высокого уровня, начиная с одного основного вопроса, который не попал в первый пост.

Основной вопрос

В. Когда я работал с Картами над домашней работой и пытался превратить List [List [Int]] в карту, я часто получал сообщение об ошибке, что Scala «не может доказать, что Int <: <(T, U)». Что это значит?

О. Итак, вы пытались сделать следующее.

1
2
3
4
5
6
7
scala> val foo = List(List(1,2),List(3,4))
foo: List[List[Int]] = List(List(1, 2), List(3, 4))
 
scala> foo.toMap
<console>:9: error: Cannot prove that List[Int] <:< (T, U).
foo.toMap
^

Это происходит потому, что вы пытаетесь сделать следующее на уровне одного двухэлементного списка, что легче увидеть в следующем.

1
2
3
4
scala> List(1,2).toMap
<console>:8: error: Cannot prove that Int <:< (T, U).
List(1,2).toMap
^

Итак, вам нужно преобразовать каждый двухэлементный список в кортеж, а затем вы можете вызвать toMap в списке кортежей.

1
2
3
4
5
6
7
scala> foo.map{case List(a,b)=>(a,b)}.toMap
<console>:9: warning: match is not exhaustive!
missing combination            Nil
 
foo.map{case List(a,b)=>(a,b)}.toMap
^
res3: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2, 3 -> 4)

Вы можете избежать предупреждений с помощью flatMapping (который в любом случае безопаснее).

1
2
scala> foo.flatMap{case List(a,b)=>Some(a,b); case _ => None}.toMap
res4: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2, 3 -> 4)

Если вам нужно много делать такого рода вещи, вы можете использовать импликации для преобразования двухэлементных списков в кортежи, как обсуждалось в предыдущем посте о студенческих вопросах .

Доступ к файловой системе

В. Как сделать так, чтобы скрипт или программа извлекали каждый файл (или каждый файл определенного формата) из каталога, заданного в качестве аргумента командной строки, и выполняли над ним операции?
А. Легко. Допустим, у вас есть каталог example_dir со следующими файлами.

1
2
$ ls example_dir/
file1.txt      file2.txt      file3.txt      program1.scala program2.scala program3.py    program4.py

Я создал их с некоторым простым содержанием. Вот команда bash, которая распечатает каждый файл и его содержимое, чтобы вы могли воссоздать их (а также увидеть удобную командную строку для цикла ).

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
$ for i in `ls example_dir`; do echo "File: $i"; cat example_dir/$i; echo; done
File: file1.txt
Hello.
 
File: file2.txt
Nice to meet you.
 
File: file3.txt
Goodbye.
 
File: program1.scala
println("Hello.")
 
File: program2.scala
println("Goodbye.")
 
File: program3.py
print("Hello.")
 
File: program4.py
print("Goodbye.")

Итак, вот как мы можем сделать то же самое, используя Scala. В том же каталоге, который содержит example_dir , сохраните следующее как ListDir.scala .

1
2
3
4
5
6
7
8
val mydir = new java.io.File(args(0))
val allfiles = mydir.listFiles
val contents = allfiles.map { file => io.Source.fromFile(file).mkString }
 
allfiles.zip(contents).foreach { case(file,content) =>
  println("File: " + file.getName)
  println(content)
}

Теперь вы можете запустить его как scala ListDir.scala example_dir .
Если вы хотите просматривать только файлы определенного типа, используйте фильтр в списке файлов, возвращаемых mydir.listfiles . Например, следующее получает файлы Scala и печатает их имена.

1
2
val scalaFiles = mydir.listFiles.filter(_.getName.endsWith(".scala"))
println(scalaFiles.mkString("\n"))

В качестве упражнения теперь рассмотрим, что вам нужно сделать, чтобы рекурсивно исследовать каталог, в котором есть каталоги, и перечислите содержимое всех файлов, которые в нем находятся. Совет : вам нужно использовать метод isDirectory () файла java.io.File .

В. Возможно ли запустить программу R внутри программы Scala? Как написать Scala-программу, которая выполняет R операций с использованием R. Если да, то как? Существуют ли какие-либо требования к каталогам?
О. Хотя я не использовал их, вы можете посмотреть на JRI (Java-R Interface) или RCaller .
Для некоторых простых вещей вы всегда можете использовать стратегию сохранения некоторых данных в файл, вызывая R-программу, которая обрабатывает этот файл и производит вывод в одном или нескольких файлах, которые вы затем читаете обратно в Scala. Это полезно для других вещей, которые вы можете захотеть сделать, включая вызов произвольных приложений для вычисления и вывода некоторых значений на основе данных, созданных вашей программой.
Вот пример того, как сделать это. Сохраните следующее как что-то вроде CallR.scala , а затем запустите scala CallR.scala . Предполагается, что у вас установлен R

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.io._
 
val data = List((4,1000), (3,1500), (2,1500), (2,6000), (1,14000), (0,18000))
 
val outputFilename = "vague.dat"
val bwriter = new BufferedWriter(new FileWriter(outputFilename))
 
val dataLine = data.map {
  case(numAdjectives, price) => "c("+numAdjectives+","+price+")"
}.mkString(",")
 
bwriter.write(
  """data = rbind(""" + dataLine + ")" + "\n" +
  """pdf("out.pdf")""" + "\n" +
  """plot(data)"""+ "\n" +
  """data.lm = lm(data[,2] ~ data[,1])""" "\n" +
  """abline(data.lm)""" "\n" +
  """dev.off()""" + "\n")
bwriter.flush
bwriter.close
 
val command = List("R", "-f", outputFilename)
scala.sys.process.stringSeqToProcess(command).lines.foreach(println)

Он принимает набор точек в виде списка Scala [(Int, Int)] и создает набор команд R для построения точек, подгонки модели линейной регрессии к точкам, построения линии регрессии и вывода PDF. Я взял конкретный набор пунктов, использованных здесь, из примера в Jurafsky и Martin в главе о максимальной энтропии (полиномиальная логистическая регрессия), которая основана на исследовании того, как неопределенные прилагательные в списке домов влияют на его покупную цену. Например, дома с четырьмя расплывчатыми прилагательными в их списке были проданы за 1000 долларов по прейскуранту, в то время как дома с одним расплывчатым прилагательным проданы за 14000 долларов по прейскуранту (читайте книгу Freakonomics, чтобы узнать об этом захватывающем обсуждении).
Вот код R, который производится.

1
2
3
4
5
6
data = rbind(c(4,1000),c(3,1500),c(2,1500),c(2,6000),c(1,14000),c(0,18000))
pdf("vague_lm.pdf")
plot(data)
data.lm = lm(data[,2] ~ data[,1])
abline(data.lm)
dev.off()

Вот изображение, созданное в vague_lm.pdf .

Напомним, что основная логика этого процесса заключается в следующем.

  1. Имейте или создайте некоторый набор точек в Scala (который, чтобы быть полезным, основывался бы на некоторых вычислениях, которые вы выполнили, и теперь для завершения нужно перейти к R).
  2. Используйте эти данные для программного создания сценария R с использованием кода Scala.
  3. Запустите скрипт R, используя scala.sys.process .

Вы также можете получить текстовую информацию из скрипта R в файл, который затем сможете прочитать обратно в Scala и проанализировать, чтобы получить результаты.
Обратите внимание, что это не обязательно самый надежный способ сделать это в целом, но он демонстрирует способ выполнения таких действий, как вызов системных команд из программы Scala.
Еще одна альтернатива — взглянуть на такие фреймворки, как ScalaLab , целью которых является поддержка среды, подобной Matlab, для Scala. На мой взгляд, это позволяет мне использовать Scala для непосредственного выполнения того, что вы хотели бы вызвать в R и других подобных языках.

Вопросы высокого уровня

В. Поскольку Scala работает на JVM, можем ли мы заключить, что все, что было написано на Scala, может быть написано на Java? (с потерей производительности и может быть с длинным кодом).
О. Для любых двух достаточно выразительных языков X и Y можно написать что-нибудь на X, используя Y и наоборот. Так да. Однако, с точки зрения простоты выполнения, очень легко перевести код Java в Scala, поскольку последний поддерживает изменяемое императивное программирование, которое обычно выполняется в Java. Если у вас есть Scala-код, который является функциональным по своей природе, его будет гораздо сложнее перевести на Java (хотя, конечно, это можно сделать).
Эффективность — это другой вопрос. Иногда функциональный стиль может быть менее эффективным (особенно если вы ограничиваете себя одной машиной), поэтому иногда полезно использовать циклы while и тому подобное. Однако в большинстве случаев эффективность времени программиста важнее, чем эффективность времени выполнения, поэтому, на мой взгляд, быстрое создание решения с использованием функциональных средств и последующая его оптимизация позднее — даже за счет «стоимости» менее функциональных. правильный путь. У Джоша Суерета есть хорошая запись в блоге об этом, Макро против Микро Оптимизации , подчеркивая его опыт в Google.
По сравнению со Scala объем написанного кода в Java почти всегда будет больше из-за большого объема стандартного кода и более высокого уровня функционального программирования. Я считаю, что программы Scala (написанные в идиоматическом, функциональном стиле), преобразованные из Java, как правило, составляют от 1/4 до 1/3 числа символов своих аналогов в Java. Переход с Python на Scala также приводит к получению менее длинного кода, возможно от 3/4-х до 5/6-х или около того, по моему опыту. (Хотя это во многом зависит от того, какой стиль Scala вы используете, функциональный или императивный или сочетание).

Вопрос. Кажется, что Scala относительно новый — есть ли у него вспомогательные библиотеки для общих задач в NLP, например, хорошие парсеры JSON / XML, о которых вы знаете?
А. Конечно. По сути, все, что было написано для JVM, довольно просто для использования с Scala. Для обработки естественного языка мы будем использовать библиотеку Apache OpenNLP (которую я и Ганн Бирнер основали в 1999 году в Университете Эдинбурга), но вы также можете использовать другие наборы инструментов, такие как программное обеспечение Stanford NLP , Mallet , Weka и другие. , На самом деле, использование Scala часто упрощает использование этих инструментов. Также появляются специфические наборы инструментов для Scala, в том числе Factorie , ScalaNLP и Scalabha (которые мы используем в классе).
Scala имеет встроенную поддержку XML, которая мне кажется довольно удобной, хотя другие хотели бы, чтобы ее не было в языке. Он описан в большинстве книг о Scala, и у Дана Спивака есть хорошая запись в блоге: Работа с поддержкой XML в Scala .
Нативная поддержка JSON невелика, но библиотеки Java для JSON работают просто отлично.

Q. Общий вопрос / комментарий: Scala лежит в области между объектно-ориентированным и функциональным языком программирования. Мой вопрос — почему? Это потому, что это делает кодирование намного проще и уменьшает количество строк? В этом случае, я полагаю, python достигает этой цели достаточно хорошо, и у него есть богатая библиотека для обработки строк. Я могу оценить некоторые вещи и простоту выполнения задач в Scala, но я не совсем уверен, почему это было даже введено, что тоже несколько нестандартно (такое сочетание ООП и парадигмы функционального программирования является первое что я слышал).
Я откажусь от Одерского, создателя Scala. Это из его поста « Почему Scala? «:

Scala рискнула тем, что до выхода объектно-ориентированные и функциональные подходы к программированию были в значительной степени непересекающимися; даже сегодня эти два сообщества все еще иногда противоречат друг другу. Но то, что команда и я узнали в нашей ежедневной практике программирования с тех пор, полностью подтвердило наши первоначальные надежды. Объекты и функции играют очень хорошо вместе; они обеспечивают новые, выразительные стили программирования, которые поддаются высокоуровневому моделированию предметной области и встроенным предметно-ориентированным языкам. Будь то лог-анализ в НАСА, моделирование контрактов в EDF или анализ рисков во многих крупнейших финансовых учреждениях, DSL на базе Scala, похоже, появляются повсюду в наши дни.

Вот некоторые другие интересные чтения, которые касаются этих вопросов:

В. Видите ли вы явное преимущество использования Scala для вещей, связанных с НЛП? Я знаю, что это не очень конкретный вопрос, но было бы здорово, если бы вы продолжили выделять разницу между scala и другими языками (такими как Java, Python), чтобы наше понимание становилось все яснее и яснее с помощью большего количества примеров.
О. Во многих отношениях такие вопросы являются вопросом личного вкуса. Я использовал Python и Java до того, как переключился на Scala. Мне понравился Python для быстрого создания прототипов и Java для разработки систем с большими масштабами. Я считаю, что Scala лучше или лучше для создания прототипов, чем Python, и он столь же хорош или лучше, чем Java для крупномасштабной разработки. Теперь я могу использовать один язык — Scala — для большинства разработок. Исключением является то, что я все еще использую R для построения наборов данных, а также для выполнения определенных статистических анализов. Переход от Java к Scala был простым, и я перешел от написания Java-as-Scala к все более и более функциональному стилю, по мере того, как мне стало легче с языком. Полученный код гораздо лучше разработан, что делает его более надежным, расширяемым и более увлекательным.

В частности, в отношении NLP, определенным преимуществом Scala является то, что, как упоминалось ранее, действительно легко использовать существующие библиотеки Java (или любую библиотеку JVM, в этом отношении). Другой заключается в том, что, поскольку используется более функциональный стиль, это облегчает переход (с точки зрения как мышления, так и фактического кодирования) к определенным типам распределенных вычислительных архитектур, таким как MapReduce. В качестве действительно интересного примера Scala и распределенных вычислений, посмотрите Spark . С таким большим количеством текстовой аналитики, выполняемой на массивных наборах данных, эта возможность становится все более важной. Другое дело, что модель вычислений на основе акторов, поддерживаемая библиотекой Akka (которая тесно связана с основными библиотеками Scala), также имеет много преимуществ для построения систем языковой обработки, которые должны работать с асинхронными потоками информации и обработкой данных (FWIW, Akka). может быть использован из Java, хотя гораздо менее приятным, чем из Scala). Он также весьма удобен для создания распределенных версий многих классов алгоритмов машинного обучения, которые могут использовать преимущества структуры решения лучше, чем стратегия MapReduce «один размер подходит всем». Например, вы можете посмотреть версию модифицированной адсорбции Akka и версию Hadoop того же алгоритма в наборе инструментов Junto.

В конце концов, однако, будет ли один язык «лучше», чем другой, будет зависеть от предпочтений и способностей данного программиста. Например, отличной альтернативой Scala является Clojure , который динамически типизирован, также основан на JVM, а также функционален — даже в большей степени, чем Scala. Поэтому, оценивая тот или иной язык, спросите, можете ли вы сделать больше быстрее и с большей наглядностью. Результат будет зависеть от возможностей языка и ваших навыков программиста.

В. В C ++ класс — это всего лишь план объекта, который имеет размер 1 независимо от того, сколько у него членов. Размер класса Scala зависит от его членов? Кроме того, есть ли что-нибудь, соответствующее оператору «sizeof» в Scala?
О. Я не знаю ответа на это. Любые полезные ответы от читателей будут приветствоваться, и я добавлю их к этому ответу, если и когда они придут.

Справка: вопросы студентов о Scala, часть 2 от нашего партнера JCG