Статьи

Первые шаги в Scala, попрощайтесь с bash-скриптами…

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

Playframework 2.0 уже не за горами, а его ядро ​​запрограммировано в Scala , так что это прекрасная возможность попробовать этот объектно-функциональный гибридный зверь…
Как и многие другие, я выберу очень простой сценарий, чтобы дать свои первые шаги …

Найти повод, чтобы попробовать Scala

Вместе с парой друзей мы находимся на пути перевода документации по игровым фреймворкам на испанский язык (посмотрите на http://playdoces.appspot.com/ , кстати, вы можете сотрудничать с нами )
Документация состоит из нескольких файлов .textile, и у меня был очень простой и глупый bash-скрипт для отслеживания нашего прогресса. Каждый файл, который еще не был переведен, имеет в первой строке фразу «todavía no ha sido traducida».

1
echo pending: `grep "todavía no ha sido traducida" * | wc -l` / `ls | wc -l`

Который произвел что-то вроде

1
pending: 40 / 63

Довольно просто, правда?

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

Scala как язык сценариев

Использовать scala в качестве языка сценариев довольно просто. Просто введите некоторый код scala в текстовый файл и выполните его с помощью « scala file.scala «. Вы также можете попробовать его с помощью интерактивного интерпретатора, лучше известного как REPL (ну, на самом деле это не интерпретатор, а цикл чтения-оценки-печати, отсюда и название REPL).

В linux вы также можете извинить их непосредственно из оболочки, отметив файл scala как исполняемый и добавив эти строки в начало файла.

1
2
3
#!/bin/sh
exec scala "$0" "$@"
!#

Совет: вы можете ускорить выполнение LOT- скрипта, добавив — savecompiled, как сказано на странице руководства команды scala , например:

1
2
3
4
5
#!/bin/sh
2
exec scala -savecompiled "$0" "$@"
3
!#

Классы и вывод типов в scala

Поэтому я создал DocumentationFile с именем, длиной и свойством isTranslated.

1
2
3
4
5
6
7
8
9
class DocumentationFile(val file: File) {
 
  val name = file.getName
  val length = file.length
  val isTranslated = (firstLine.indexOf("Esta página todavía no ha sido traducida al castellano") == -1)
 
  def firstLine = new BufferedReader(new FileReader(file)).readLine
 
}

Scala забирает много стандартного кода. Конструктор прямо здесь, вместе с объявлением класса. В нашем случае конструктор DocumentationFile принимает в качестве аргумента файл java.io.File.

Scala также активно использует вывод типов, чтобы избавить нас от необходимости объявлять тип каждой переменной. Вот почему вам не нужно указывать, что имя — это String, длина Long и isTranslated — логическое значение. Вы все еще должны объявлять типы в аргументах метода, но обычно вы можете опустить их везде.

Работа с коллекциями

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

1
2
3
4
5
import java.io._
 
val docs = new File(".").listFiles
  .filter(_.getName.endsWith(".textile"))   // process only textile files
  .map(new DocumentationFile(_))

Технически говоря, это всего лишь одна строка кода. «_» — это просто синтаксический сахар, мы могли бы написать его более многословно:

1
2
3
val docs = new File(".").listFiles
  .filter( file => file.getName.endsWith(".textile") )   // process only textile files
  .map( file => new DocumentationFile(file) )

Или если вы в фигурных скобках весело

1
2
3
4
5
6
7
val docs = new File(".").listFiles
  .filter { file =>
    file.getName.endsWith(".textile")         // process only textile files
  }  
  .map { file =>
    new DocumentationFile(file)
  }

Функции высшего порядка

Как только у нас будут все текстильные файлы, нам понадобятся переведенные.

1
val translated = docs.filter(_.isTranslated)

Здесь мы передаем методу фильтра функцию в качестве параметра (это то, что называется функцией более высокого порядка). Эта функция оценивается для каждого элемента в массиве, и если она возвращает значение true, этот элемент добавляется в результирующий массив. Материал «_.isTranslated» опять-таки просто синтаксический сахар. Мы могли бы также написать функцию следующим образом:

1
val translated = docs.filter( (doc: DocumentationFile) => doc.isTranslated )

Функциональные и императивные: варьировать или не менять

Теперь мне нужно рассчитать количество и размер переведенных и еще не переведенных файлов. Подсчет файлов довольно прост, нужно просто использовать «translation.length», чтобы узнать, сколько файлов уже переведено. Но для подсчета их размера я должен суммировать размер каждого из них.

Это была моя первая попытка:

1
2
var translatedLength = 0L
translated.foreach( translatedLength += _.length )

В scala мы можем объявлять переменные с ключевыми словами « var » и « val », первые изменчивы, а последние — неизменяемы. Изменяемые переменные предназначены для чтения и записи, в то время как неизменяемые переменные не могут быть переназначены после того, как их значение установлено (представьте их как окончательные переменные в Java).

Хотя scala позволяет вам работать в императивном или функциональном стиле, он действительно поощряет более поздний. Программирование на языке scala , своего рода библиотека Библии, даже учит, как реорганизовать ваш код, чтобы избежать использования изменяемых переменных, и привыкнуть к более функциональному стилю программирования.
Я нашел несколько способов рассчитать его в более функциональном стиле (благодаря переполнению стека !)

01
02
03
04
05
06
07
08
09
10
val translatedLength: Long = translated.fold(0L)( (acum: Long, element: DocumentFile) => acum + element.length )
 
//type inference to the rescue
val translatedLength = translated.foldLeft(0L)( (acum, element) => acum + element.length )
 
//syntactic sugar
val translatedLength = translated.foldLeft(0L)( _ + _.length )
 
// yes, if statement is also an expression, just like the a ? b : c java operator.
val translatedLength = if (translated.length == 0) 0 else translated.map(_.length).sum

Я наконец решил эту простую и короткую форму:

1
2
val translatedLength = translated.map(_.length).sum
val docsLength = docs.map(_.length).sum

Параметры по умолчанию и передача функций в качестве аргументов

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

01
02
03
04
05
06
07
08
09
10
11
println(
  "translated size: " + asKB(translatedLength) + "/" + asKB(docsLength) + " " +
  translatedLength * 100 / docsLength + "% "
)
 
println(
  "translated files: " + translated.length + "/" + docs.length + " " +
  translated.length * 100 / docs.length + "% "
)
 
def asKB(length: Long) = (length / 1000) + "kb"

И это был выход:

1
2
3
translated size: 256kb/612kb 41%
 
translated files: 24/64 37%

Ну, это сработало, но это определенно можно улучшить, слишком много дублирования кода.
Поэтому я создал функцию, которая позаботилась обо всем этом:

01
02
03
04
05
06
07
08
09
10
11
12
def status(
  title: String = "status",
  current: Long, total: Long,
  format: (Long) => String = (x) => x.toString): String = {
 
  val percent = current * 100 / total
 
  title + ": " + format(current) + "/" + format(total) + " " +
  percent + "%" +
  " (pending " + format(total - current) + " " +
  (100-percent) + "%)"
}

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

1
2
3
4
5
6
7
println(
  status("translated size", translatedLength, docsLength, (length) => asKB(length) )
)
 
println(
  status("translated files", translated.length, docs.length)
)

Вот и все.
Достигнуть такого рода вещей действительно легко, используя scala в качестве языка сценариев, и по пути вы можете выучить пару интересных концепций и сделать первые шаги в функциональном программировании.
Это полный сценарий, здесь у вас есть GitHub Gist, и вы также можете найти его в проекте документации Play Испанский .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#!/bin/sh
exec scala "$0" "$@"
!#
 
import java.io._
 
val docs = new File(".").listFiles
  .filter(_.getName.endsWith(".textile"))   // process only textile files
  .map(new DocumentationFile(_))
 
val translated = docs.filter(_.isTranslated)    // only already translated files
 
val translatedLength = translated.map(_.length).sum
val docsLength = docs.map(_.length).sum
 
println(
  status("translated size", translatedLength, docsLength, (length) => asKB(length) )
)
 
println(
  status("translated files", translated.length, docs.length)
)
 
def status(
  title: String = "status",
  current: Long, total: Long,
  format: (Long) => String = (x) => x.toString): String = {
 
  val percent = current * 100 / total
 
  title + ": " + format(current) + "/" + format(total) + " " +
  percent + "%" +
  " (pending " + format(total - current) + " " +
  (100-percent) + "%)"
}
 
def asKB(length: Long) = (length / 1000) + "kb"
 
class DocumentationFile(val file: File) {
 
  val name = file.getName
  val length = file.length
  val isTranslated = (firstLine.indexOf("Esta página todavía no ha sido traducida al castellano") == -1)
 
  override def toString = "name: " + name + ", length: " + length + ", isTranslated: " + isTranslated
   
def firstLine = new BufferedReader(new FileReader(file)).readLine
 
}

Ссылка: Первые шаги со Scala, попрощайтесь с bash-сценариями… от нашего партнера по JCG Себастьяна Скарано на платформе « Развлекайся с Play»! блог

Статьи по Теме :