Всем привет. Я думаю все замечали за собой, что когда долго пишешь код на одном языке то при переключении на другой обязательно пытаешься их сравнивать. Я решил описать эти попытки сравнения, вдруг комуто поможет, что не привычно и непонятно при переходе с C# на Scala, синтаксис, сравним типы, нюансы.
Scala является полноценным функциональным языком, рассматрим ее объектно-ориентированную сторону т.к. это ближе к C#.
За ранее предупреждаю я ни коем образом не хочу агитировать за тот или другой язык, я просто постараюсь сравнить глазами разработчика на С#, мир не должен сойтись клином на одном языке, т.к. опыт работы с другими часто помогает писать лучше на своем основном.
Первые "принципы" Scala которые надо освоить, начав работать с данным языком.
- Минимальное количество кода, минимальное количество нажатий на клавиатуру.
- Меньше кода меньше ошибок, но такая минификация все таки сказывается на читабельности, особенно на первых порах.
- Не должно быть null обьектов
- Scala имеет средства борьбы с этим злом :)
- Нет операторов таких как: return, break, continue, swith(){ case }
- Здесь конечно могут возникнуть некоторые затруднения, привычка от которой надо избавляться на время работы со Scala, стараться проектировать методы так чтобы нам не нужны были выходы из него во множестве мест. Как плюс можно отметить, что данных подход в последствии даст более ясный и чистый код "хотя чисто мое субьективное мнение :)". P.S. return есть в Scala но пользоваться им не рекомендуют.
- Нет static т.е. нет статик классов или полей или свойств вообще нет статики.
- Довольно часто такие обьекты приносят больше вреда чем пользы. В Scala пошли другим путем, статиков нет но есть специальный обьект который является реализацией паттерна Signelton.
- if-else и for не statements а expressions.
-
Это просто обалденная вещь :) Для if-else это означает, что он своего рода функция которая принимает предикат if(predicate) похожая ситуация и for. Можно написать вот так!
val x = if (a > b) a else b
- По умолчанию в Scala все классы
public
и все методыvitrual
- Обьяснять по этому пункту я думаю не нужно, решайте сами удобно Вам это или нет, но помнить об этом нужно.
Сравнение Scala - C#.
- class
- Как в C# мы не можем наследоваться от множества классов, мы должны создать обьект с помощью
new
, и можно создаватьabstract class
. - trait (признак, примесь)
-
Вот тут возникают некоторые сложности при сравнении с C#.
-
Во первых, можно частично сравнить с C#
interface
:- Scala класс может наследоваться от одного класса и множества
trait
это называется mixing - Можно описывать абстрактные методы/свойства для последующей реализации в наследумых классах
- Нельзя создать конструктор
- Scala класс может наследоваться от одного класса и множества
-
Во вторых, можно сравнить с С#
abstract class
:- Можно создавать как абстрактные так реализованные методы/свойства
- Нельзя создавать обьект через
new
- Может наследоваться от других классов
-
Во первых, можно частично сравнить с C#
- object
-
Это механизм борьбы со
static
. Можно думать обobject
как о скрытом от нас реализации паттерна Signelton. Я не буду вдаваться в подробности данного паттерна, но все его свойства присутствуют при созданииobject
и не надо делать ни каких лишних тело движений.Еще некоторые свойства которые надо знать.
- Можно наследоваться от класса и множества
trait
- Нельзя наследоваться от другого
object
- Можно наследоваться от класса и множества
- case class
-
Рассмотрим его как обычный класс со следующими плюшками :)
- можно создавать обьект класса без оператора
new
- можно использовать
case class
дляmatch
-
хорошей практикой использования
case class
является инициализация его через конструктор с параметрами т.к. он автоматически имплементирует методыtoString, equals, hashCode
на основе аргументов конструктора. Также подразумевается использвать его как обьект хранения неизменяемых данных.
- можно создавать обьект класса без оператора
Подытожим этот абзац еще некоторой интересной информацией, Scala не имеет partial class
, и в этом нет особой нужды т.к. в trait
есть возможность реализовать функционал, прибавляем к этому наследование от множества trait
и можно просто спроектировать разделение функционала с
помощью наследования. Возникает ли проблема ромба при множественного наследования, в Scala это как раз и решается с помощью
trait
правильней сказать они были задуманы для решения проблем множественного наследования. trait
занимает промежуточное место между
интерфейсом и классом и он подмешиваеться в класс (как это происходит мы увидим ниже). Важным моментов для trait
является порядок наследования,
подробней об этом можно почитать если искать по фразе Scala''s Stackable Trait Pattern. Коротко для тех кому лень, если класс наследуется от множества в котором
классы или реализации какимто образом повторяются то последние подмешанные trait
в списке наследования перекроют предыдущие т.е. (Trait1, Trait2, Trait3, Trait2) -> (Trait1, Trait3, Trait2).
Таблица соответствий типов Scala - C#.
С# | Scala | Scala Описание |
---|---|---|
byte | Byte | 8 bit -128 to 127 |
short | Short | 16 bit -32768 to 32767 |
int | Int | 32 bit -2147483648 to 2147483647 |
long | Long | 64 bit -9223372036854775808 to 9223372036854775807 |
float | Float | 32 bit |
double | Double | 64 bit |
char | Char | 16 bit |
string | String | |
bool | Boolean | true/false |
с этим типом сложнее, можно ассоциировать его с void но у Unit имеет больше возможностей |
Unit | означает что функция возвращает пустое значение (но все таки значение). |
null | Null | null это trait т.е. это ссылочный тип и им можно обнулить любой ссылочный обьект |
Nothing |
это тоже trait , он более обобщенный чем Null , обычно такое сильно обобщенное "ничто" используется для методов выбрасывающих исключение
def error(message: String): Nothing = throw new RuntimeException(message)ну собственно по этой причине я употребил фразу "все таки значение" при описании Unit .
Nothing не имеет значения, и обычно сигнализирует, что что то пошло не так.
|
|
Nil | это обьект обозначающий пустую коллекцию | |
AnyVal | супер тип для значимых типов | |
AnyRef | супер тип для ссылочных типов |
Добавлю к ней цепочку конвертации AnyVal
типов, это бывает полезно знать :)
Byte -> Double -> Short -> Int -> Long -> Float -> Double Char -> Int
Практика
Чтобы быстрее понять синтаксис расмотрим код Scala и C#.C#
public class User { public string Name { get; set; } public int Age { get; set; } public string Phone { get; set; } public User(string name, int age, string phone = "") { Phone = phone; Age = age; Name = name; } } var user = new User("Name",18); var name = user.Name;
Scala
class User(var name:String,var age:Int,var phone:String = "")Из данного примера видим как "магическим" способом 11 строк кода превратились в одну, и я предлагаю разобрать этот пример по частям и немного углубиться в понимании процесса.val user = new User("Name",18) val name = user.Name /* or => val name = user Name */
- Создание конструктора
- Нам не надо создавать отдельный блок для конструктора, конструктор прописывается в
()
рядом и именем класса - Описание переменных
- Нет нужды описывать переменные внутри класса т.к. Scala создает публичные поля по переменным проинициализированым в конструкторе, соответственно отпадает необходимость в установке значений с констукторе.
- Getter & Setter
- Как Вы могли заменить я создал поля использовав
get; set;
в Scala это заменяется операторомvar
- Значение по умолчанию
- С этим я думаю должно быть понятно т.к. синтаксис похож на C#
- Также нужно всегда указывать тип явно.
- Readonly поля
- Readonly поля определяются оператором
val
;
в конце строки-
В Scala нет необходимости ставить точку с запятой в конце выражения т.к. компилятор сам опеределяет конец выражения.
Ни кто конечно вам не запрещает их ствать, но они нужны только в ситуации когда необходимо написать несколько выражений в одну строчку.
val expr1 = 1 + 2; val expr2 = expr2 + 5
- Оператор доступа к членам
-
Мы можем использовать как
.
так и пробел, выбирайте сами что Вам ближе по душе. Считается, что нажать пробел "быстрее/проще" и можно сократить время написание кода, но не факт :) плюс читабельность может пострадать.
Рассмотрим еще примеры инициализации.
val name : String = "Name" //в большинстве случаев нам не нужно писать тип явно, Scala может это сделать за нас val name = "Name" //=> name : String
Инициализация полей с помощью val
или var
как вы заметили не отличается.
Методы в Scala инициализируются с помощью оператора def
т.е definition
def init() : Unit = { /* code */ } // как и в примере с переменными возвращаемый тип Unit можно упустить def init() = { /* code */ }В примере выше метод
init
не принимает параметры по этому скобки можно тоже упустить, и знак равно т.к. метод ни ничего не возвращает.
def init { /* code */ }
От определения метода (со скобками или без) зависит то так Вы его и будете вызывать.
И еще пару примеров определения методов для закрепления. Я немного забегу вперед потому, что я не смогу закончить рассказ про методы, если не расскажу как определять возвращаемый тип без оператора return
, но в Scala все просто, тип возращаемого значения определятся автоматически из контекста.
Простой пример, это когда код метода заканчивается определением которое возвращает какое либо значение, то Scala определит, что данное значение и будет возвращено методом.
//если метод возвращает значение, то оператор равно упускать нельзя, это будет ошибкой //явно указываем возвращаемый тим def addition(a : Int, b : Int ) : Int = { a + b } // в данном примере тип определяется автоматически и будет тоже Int def addition(a : Int, b : Int ) = { a + b } //если в фигурных скобках простое выражение можно сами скобки упустить def addition(a : Int, b : Int ) = a + bНадо также помнить, что вызов функций и переменных может быть одинаковым, Вы должны сами за этим следить если это необходимо.
class UserVal { val name = "Name" } class UserDef { def name = "Name" } val n1 = new UserVal().name val n2 = new UserDef().nameДавайте посмотрим примеры создания
trait, object, case class
и наследование, хотя синтаксис инициализации у них одинаковый, но наглядный пример никогда не помешает.
trait Tr { } object Obj { } case class Cs(val someVal : Int = 0)
В C# мы добавляем наследование с помощью оператора :
и далее через ,
перечисляем базовые class/interface
. В Scala похожая ситуация только операторы отличаются, чтобы добавить базовый class/trait
нужно использовать оператор extends
и имя базового обьекта, а все последующие перечисляем через оператор with
. Действуют теже правила, что и для C#, первым базовым обьектом должен идти класс потом интерфейсы, в Scala так же, сначала класс потом trait
.
class Base { }trait Tr extends Base { } trait Tr2 { } case class Cs(val someVal : Int = 0) extends Base with Tr2
Заключение
Вот собственно и всё что я хотел Вам рассказать в первой статье. Пишите что Вам понравилось или не понравилось в комментариях. Eсли Вы нашли ошибки в статье напишите об этом пожалуйста чтобы я мог исправить и не вводить ни кого в заблуждение.
Всем хорошего дня, спасибо.