Blog (3)
Komentarze (1.8k)
Recenzje (0)
@FrankfurteriumCoś o Scali

Coś o Scali

16.04.2012 02:28

Na samym wstępie – witam wszystkich. To mój pierwszy wpis nie tylko na blogu dobrychprogramów, ale i ogólnie w życiu. Prawdopodobnie nigdy bym go nie popełnił, gdyby nie nieopatrzna wzmianka pod którymś z wpisów dotyczących chyba wszystkim znanego Hostowego Challenge'u. Napisałem, że w Scali można sprawę załatwić w sposób równie dobry, a przy tym krótszy. Kilka osób wyraziło chęć przyjrzenia się takiej implementacji, więc... Czemu nie? Ale najpierw króciutko o języku.

Coś (króciutkiego) o pochodzeniu Scali

Scala to dzieło człowieka mocno zasłużonego w dziedzinie rozwoju Javy. Martin Odersky, bo o nim mowa, uznał, że Java jako język rozwija się zbyt ślamazarnie i coraz mniej nadaje się do podejmowania współczesnych nam wyzwań. Nie odrzucił jej jednak jako substytutu zła, szatana i polskiej służby zdrowia, ale zapragnął stworzyć dla niej coś, czym dla C był C++. Tyle że w wersji turbo.

Coś o zapożyczeniu z innych języków

Scala jest... skalowalna. Chodzi nie tylko o zastosowanie, ale i składnię. Pan Odersky może i jest jednym z tatusiów chrzestnych Javy, ale nie przeszkodziło mu to zauważyć, że niektóre sprawy w innych językach zorganizowano lepiej. Na przykład brak konieczności ciągłego wklepywania średników i typów zmiennych/funkcji. Te ciągle są poddawane silnej kontroli typów, ale w 90% sytuacji kompilator zrozumie, że skoro pakujemy do zmiennej/stałej łańcuch znaków, to nie oczekujemy raczej liczby zmiennoprzecinkowej. Mechanizm działa też z bardziej skomplikowanymi tworami (btw. w Scali nie ma typów prostych, wszystko jest obiektem) i zrozumie, jaką i jakiego typu utworzyć kolekcję. Zamiast długaśnego:

val t: Tuple2[Int, String] = new Tuple2(1, ”2”);

wystarczy:

val t = (1, ”2”).

Jeżeli jesteśmy „nieopatrzeni” i wolimy dokładnie widzieć, co w czym deklarujemy, możemy zachować

val t = Tuple2(1, ”2”)

Początkowo mnogość możliwości zapisu może wydawać się dziwna, ale trzykrotne zmniejszenie liczby znaków w deklaracji głupiej tupli chyba jest warte lekkiej zmiany przyzwyczajeń.

Coś o programowaniu funkcyjnym

C++ rozwinął C o między innymi mechanizmy obiektowości. Java miała je od początku, podobnie Scala (pełna obiektowość z kilkoma przydatnymi patentami). Novum stanowi pierwiastek programowania funkcyjnego, które ostatnimi laty przeżywa mały renesans. Nie chciałbym zagłębiać się w temat, o którym mam jeszcze nikłe pojęcie, ale po napisaniu pewnej ilości programów człowiek zaczyna dostrzegać zalety takiego podejścia. Autor języka w swoim (naprawdę świetnie napisanym) podręczniku wyraźnie je premiuje i co rusz pokazuje dowody, że tak jest zwięźlej i czytelniej, chociaż początkowo trudno się przestawić na konstrukcje raczej rzadko spotykane w światku języków imperatywnych.

Takie krótkie:

def list() = linesList.foreach(line => if(isHost(line)) println(line))

można rozpisać na javowe:

void list() {
	foreach(string line : linesList) {
		if (isHost(line)) println(line);
	}
}

Co ciekawe, zaprezentowana postać funkcyjna została poddana lekkiemu tuningowi, bo na początku wyglądała tak:

def list(): Unit = { linesList.foreach((line: String) => {if (isHost(line)) println(line)}) }

Chwała najwyższemu, ewidentnie nadmiarowe nawiasy można pomijać. Podobnie jest z niektórymi nawiasami i  kropkami między obiektem a wywoływaną metodą. Jeżeli człowiek się postara, funkcja może wyglądać niemal jak zdanie napisane (jakimś mocno pierwotnym :p) językiem naturalnym. Przykład: pizza add pepperoni, czyli pizza.add(pepperoni). Śliczne, chociaż prawdę mówiąc, sam się jeszcze do tego nie przyzwyczaiłem...

Oczywiście wszystko jest opcjonalne. Nikt nie będzie nam kasował kropek, nawiasów i średników. Pisanie długich deklaracji z uwzględnieniem każdego typu, każdego new i return (też są w dużej mierze opcjonalne) nie jest karane. Można mieszać programowanie funkcyjne i obiektowe, a nawet walnąć focha i postanowić „Tę klasę napiszę w czystej Javie” (i to będzie działać. Z poziomu Scali będzie można jej używać a nawet dziedziczyć). Tak na marginesie – pisząc w Scali, bez większych problemów możemy czerpać z przebogatego skarbca bibliotek javowych, po prostu importując to, czego nam potrzeba. Zaleta jak stąd (Polska, woj. dolnośląskie) do Mexico City.

Coś o tym, gdzie Scalę można wykorzystać

Jak to w przypadku języków ogólnego przeznaczenia – wszędzie. A dokładniej wszędzie, gdzie mamy JVM. Możemy pisać proste skrypty konsolowe (przykład za moment), aplikacje okienkowe (biblioteki graficznej z prawdziwego zdarzenia na razie brak, chociaż Scala świetnie działa ze Swingiem i są już wstępne wersje działającego Qt) i coraz głośniej robi się o Scali jako narzędziu w światku EE. Do czystoscalowego frameworka Lift dołączył chwalony za podobieństwo do Railsów Play2, szybko pojawia się cała masa narzędzi wspomagających (ORM, sterowanie zdarzeniami itd.). Fachowcy piszą o bardzo przyjemnym tworzeniu DSL‑i (języków dziedzinowych).

Póki co Scala to język dla pasjonatów, ale i na naszym rynku powoli pojawiają się ogłoszenia, gdzie „Znajomość Scali będzie atutem” i coś czuję, że za jakiś czas będzie się dało na tym zarobić. Zwłaszcza że niektórzy ludzie związani z JVM typują Scalę na następcę Javy.

Coś o tym, czym Scala nie jest

Na pewno nie jest lekiem na całe zło. Tak naprawdę Scala stanowi swoistą „nakładkę” na Javę. Wygenerowany kod bajtowy będzie bardzo podobny do tego wyplutego z czystej Javy. Różnica jest taka, że przy podobnym efekcie piszemy zdecydowanie mniej, wygodniej i szybciej. Jeżeli chodzi o wydajność, różne są benchmarki i różne są wyniki. Wedle wszystkich Scala plasuje się w czołówce języków JVM. Czasem wyprzedza Javę, czasem nie i nie nastawiałbym się na 200% boost pisanych aplikacji. Żeby to ładnie zobrazować:

Java to ogromne i solidnie zbudowane zamczysko, niestety zaniedbane i nadgryzione zębem czasu. Dalej spełnia swoją rolę, chociaż z oddali widać lepiej utrzymane twierdze i nowsze, lepiej zaprojektowane pałace. Scala to próba gruntownej renowacji. Naprawa ubytków, odmalowanie ścian, naprawa schodów, dobudowanie wind i podjazdów dla inwalidów. Nie zwiększa możliwości obronnych, ale sprawia, że wewnątrz dużo przyjemniej się mieszka, a zwiedzającym łatwiej jest wszędzie dotrzeć.

Coś o tym, czego nie opisałem, a co też jest fajne

W tym krótkim wpisie nie liznąłem nawet zagadnienia programowania obiektowego (może kiedyś...), chociaż dopiero tam Scala pokazuje pazurki. Znów jest zwięźlej (cała klasa wraz ze zmiennymi, konstruktorem parametrowym, metodami isEqual, hashCode i toString w pojedynczej linii), ale nie tylko. Na zachętę wspomnę tylko o nowym podejściu do interfejsów (nazwanych traits – cechami), w których wreszcie możemy implementować całe metody.

Coś praktycznego

W zasadzie ten wpis to bardzo ogólna... Nawet nie charakterystyka, ale raczej moje własne odczucia w stosunku do języka programowania, w którym zakochałem się już od pierwszego wejrzenia. Wybaczcie mizerną liczbę konkretów, ale tak to już jest w pierwszych fazach zakochania – zaburzona koncentracja, wyidealizowany obraz wybranki. Prawie jej nie znam, a już wydaje mi się, że chciałbym z nią spędzić życie. W przyszłości pewnie pochwalę się kilkoma przejawami tego, co tam razem zmajstrujemy.

Poniżej obiecana implementacja skryptu do edycji pliku hosts. Podejście proste i minimalistyczne. Nie jest to najzwięźlejsza z możliwych implementacja, ale w kodzie chciałem zobrazować parę ciekawostek wymienionych w tekście.

import scala.io.Source.fromFile
import scala.util.matching.Regex
import java.io.{FileWriter, File}  //  Import two classes from one package.

//  Scala is statically typing language with well designed type inference. 
val root = System.getenv("SystemRoot")  //  val root: String = System.getenv("SystemRoot")
val host = """\system32\drivers\etc\hosts"""
val linesList = fromFile(root+host, "utf-8").getLines.toList

if (!args.isEmpty) args(0) match {
      case "list"   =>  list()
      case "add"    =>  if (args.length > 2) add()
      case "del"    =>  if (args.length > 1) del()
      case _        =>  println("lol, wut?")
} else println("Wut?")

	
def isHost(line: String) = !line.trim.isEmpty && line.trim.head.isDigit
//  Without return function value = last value of block/line
	
def list() = linesList.foreach(line => if(isHost(line)) println(line))
//  Equal to:
//  def list(): Unit = { linesList.foreach((line: String) => {if (isHost(line)) println(line)}) }

def add() {
//  FileWriter -> pure Java class. 
	val fw = new FileWriter(root+host, true)
	fw write("\n\t" + args(1) + "\t\t" + args(2))
	fw close
//  In Scala you can remove some dots and parentheses ( fw.write, fw.close() ).
}

def del() {
	val pattern = new Regex("(\\s|^|#)"+args(1)+"(\\s|$|#)")
	val newFile = linesList.filterNot(line => isHost(line) && !pattern.findFirstIn(line).isEmpty)
	
	val fw = new FileWriter(root+host, false)
	for(i <- 0 to newFile.length - 2) fw.write(newFile(i)+"\n")
	fw.write(newFile(newFile.length - 1))
	fw.close
}

I to tyle, jeśli chodzi o mój pierwszy (może nie ostatni) w życiu wpis na bloga ;‑)

Wybrane dla Ciebie
Komentarze (25)