Изучаем MoonScript за 15 минут
Перевод: Михаил Радюк - Twitter @torabora08
Оригинал статьи на английском тут.
-- Два тире начинают комментарий. Комментарии могут продолжаться до конца строки.
-- MoonScript скомпилированный в Lua не содержит комментариев.
-- Примечание: в MoonScript не используются 'do', 'then', или 'end' как в Lua,
-- вместо этого используется синтаксис с отступом, скорее похожий на Python.
--------------------------------------------------
-- 1. Присваивание
--------------------------------------------------
hello = "world"
a, b, c = 1, 2, 3
hello = 123 -- Перезаписывает `hello`, который выше.
x = 0
x += 10 -- x = x + 10
s = "hello "
s ..= "world" -- s = s .. "world"
b = false
b and= true or false -- b = b and (true or false)
--------------------------------------------------
-- 2. Литералы и операторы
--------------------------------------------------
-- Литералы работают практически также как и в Lua. Строки могут быть
-- разорваны без использования знака \.
some_string = "exa
mple" -- local some_string = "exa\mple"
-- Строки также могут включать интерполированные значения или значения,
-- которые определяются и потом помещаются внуть строки
some_string = "This is an #{some_string}" -- Превращается в 'This is an exa\mple'
--------------------------------------------------
-- 2.1. Литералы функций
--------------------------------------------------
-- При записи функций используются стрелки:
my_function = -> -- компилируется в `function() end`
my_function() -- вызывает пустую функцию
-- Функции могут вызываться без использования скобок. Скобки
-- могут быть использованы для приоритета над другими функциями
func_a = -> print "Hello World!"
func_b = ->
value = 100
print "The value: #{value}"
-- Если функция не требует параметров, она может быть вызвана либо с помощью `()` либо с `!`.
func_a!
func_b()
-- Функции могут использовать аргументы перед стрелкой, указанные списком
-- имён аргументов, заключенных в скобки.
sum = (x, y)-> x + y -- Из функции возвращается крайнее выражение.
print sum(5, 10)
-- В Lua есть идиома передачи первого аргумента в функцию как объекта,
-- в качестве объекта "собственного" объекта. Использование толстой стрелки (=>) вместо тонкой (->)
-- автоматически создаёт переменную "себя". `@x` это сокращенная запись `self.x`.
func = (num)=> @value + num
-- Аргументы по-умолчанию могут быть также использованы с литералами функций:
a_function = (name = "something", height=100)->
print "Hello, I am #{name}.\nMy height is #{height}."
-- От того, что аргументы по-умолчанию вычисляются в теле функции во время компиляции в Lua,
-- вы можете ссылаться на предыдущие аргументы
some_args = (x = 100, y = x + 1000)-> print(x + y)
--------------------------------------------------
-- Некоторые аспекты
--------------------------------------------------
-- Знак минуса играет две роли: унарного оператора отрицания и бинарного
-- оператора вычитания. Рекомендуется всегда использовать пробелы между
-- бинарными операторами во избежание коллизий.
a = x - 10 -- a = x - 10
b = x-10 -- b = x - 10
c = x -y -- c = x(-y)
d = x- z -- d = x - z
-- Когда нет пробела между переменной и строковым литералом
-- вызов функции берет приоритет над последующим выражением:
x = func"hello" + 100 -- func("hello") + 100
y = func "hello" + 100 -- func("hello" + 100)
-- Аргументы в функции могут располагаться на нескольких строках в зависимости от того,
-- насколько аргументы выделены отступами. Отступы также могут быть вложенными.
my_func 5, -- вызывается как my_func(5, 8, another_func(6, 7, 9, 1, 2), 5, 4)
8, another_func 6, 7, -- вызывается как
9, 1, 2, -- another_func(6, 7, 9, 1, 2)
5, 4
-- Если вызов функции стоит в начале блока, то выделение отступами может отличаться
-- от уровня отступов, используемых в блоке
if func 1, 2, 3, -- called as func(1, 2, 3, "hello", "world")
"hello",
"world"
print "hello"
--------------------------------------------------
-- 3. Таблицы
--------------------------------------------------
-- Таблицы определяются фигурными скобками, как в Lua:
some_values = {1, 2, 3, 4}
-- В таблицах можно использовать новую строку вместо запятых.
some_other_values = {
5, 6
7, 8
}
-- Назначение выполняется через `:` вместо `=`:
profile = {
name: "Bill"
age: 200
"favorite food": "rice"
}
-- Фигурные скобки могут быть опущены для таблиц типа `ключ: значение`.
y = type: "dog", legs: 4, tails: 1
profile =
height: "4 feet",
shoe_size: 13,
favorite_foods: -- вложенная таблица
foo: "ice cream",
bar: "donuts"
my_function dance: "Tango", partner: "none" -- :( вечно одинок
-- Таблицы, построенные из переменных могут использовать те же имена
-- используя `:` как префиксный оператор.
hair = "golden"
height = 200
person = {:hair, :height}
-- Как и в Lua, ключи могут быть нестрочными и нечисловыми значениями при применении `[]`.
t =
[1 + 2]: "hello"
"hello world": true -- Можно использовать строчные литералы без `[]`.
--------------------------------------------------
-- 3.1. Табличные генераторы
--------------------------------------------------
-- Генераторы списков
-- Создаётся копия списка, но с удвоением всех элементов. Использование
-- звёздочки перед именем переменной или таблицы применяется для перебора значений таблицы.
items = {1, 2, 3, 4}
doubled = [item * 2 for item in *items]
-- -- `when` используется когда переменная должна быть включена
slice = [item for item in *items when i > 1 and i < 3]
-- Конструкции `for` внутри списочных генераторов могут быть соединены
x_coords = {4, 5, 6, 7}
y_coords = {9, 2, 3}
points = [{x,y} for x in *x_coords for y in *y_coords]
-- Числовые циклы for также могут использоваться в генераторах:
evens = [i for i=1, 100 when i % 2 == 0]
-- Табличные генераторы очень похожи, но используют `{` и `}`
-- и берут два значения для каждой итерации.
thing = color: "red", name: "thing", width: 123
thing_copy = {k, v for k, v in pairs thing}
-- Таблицы могут быть сделаны "плоскими" из пар "ключ-значение" в массиве при помощи `unpack`
-- для возврата обоих значений, используя первое как ключ и второе как значение.
tuples = {{"hello", "world"}, {"foo", "bar"}}
table = {unpack tuple for tuple in *tuples}
-- Слайсинг (slicing) выполняется чтобы перебрать только определенную часть массива.
-- Для перебора используется нотация `*`, но ещё добавляется `[начало, конец, шаг]`
-- Следующий пример также показывает, что данный синтаксис может использоваться и в цикле `for`,
-- также как и другие генераторы.
for item in *points[1, 10, 2]
print unpack item
-- Любые нежелательные значения могут быть отброшены. Вторая запятая
-- не требуется если не указан шаг.
words = {"these", "are", "some", "words"}
for word in *words[,3]
print word
--------------------------------------------------
-- 4. Управляющие структуры
--------------------------------------------------
have_coins = false
if have_coins
print "Got coins"
else
print "No coins"
-- Используйте `then` для однострочного `if`
if have_coins then "Got coins" else "No coins"
-- `unless` это `if` наоборот
unless os.date("%A") == "Monday"
print "It is not Monday!"
-- `if` и `unless` могут использоваться как выражения
is_tall = (name)-> if name == "Rob" then true else false
message = "I am #{if is_tall "Rob" then "very tall" else "not so tall"}"
print message -- "I am very tall"
-- `if`, `elseif`, и `unless` могут вычислять присвоение также как и выражения.
if x = possibly_nil! -- назначает `x` равным `possibly_nil()` и вычисляет `x`
print x
-- Условия могут ставиться как после операторов, так и перед ними.
-- Это называется "декоратор строки".
is_monday = os.date("%A") == "Monday"
print("It IS Monday!") if isMonday
print("It is not Monday..") unless isMonday
--print("It IS Monday!" if isMonday) -- Это не оператор, не работает
--------------------------------------------------
-- 4.1 Циклы
--------------------------------------------------
for i = 1, 10
print i
for i = 10, 1, -1 do print i -- Используйте `do` для однострочных циклов.
i = 0
while i < 10
continue if i % 2 == 0 -- Оператор сontinue; пропускает оставшуюся часть цикла.
print i
-- Циклы можно использовать в качестве декоратора строки, также как и условия
print "item: #{item}" for item in *items
-- При использовании циклов как выражений создается таблица-массив. Последний оператор
-- в блоке приведен к выражению и добавлен в таблицу.
my_numbers = for i = 1, 6 do i -- {1, 2, 3, 4, 5, 6}
-- используйте `continue` для отфильтровки значений
odds = for i in *my_numbers
continue if i % 2 == 0 -- работает противоположно `when` в генераторах!
i -- Добавлена только чтобы вернуть таблицу если нечетная
-- Цикл `for` возвращает `nil` когда он последний оператор в функции
-- Используйте явный `return` для создания таблицы.
print_squared = (t) -> for x in *t do x*x -- возвращает `nil`
squared = (t) -> return for x in *t do x*x -- возвращает новую таблицу квадратов
-- Указанное ниже делает то же, что и `(t) -> [i for i in *t when i % 2 == 0]`
-- Но генератор списков создаёт лучший и более читабельный код!
filter_odds = (t) ->
return for x in *t
if x % 2 == 0 then x else continue
evens = filter_odds(my_numbers) -- {2, 4, 6}
--------------------------------------------------
-- 4.2 Операторы выбора
--------------------------------------------------
-- Операторы выбора это сокращенный способ написания множества операторов `if`
-- проверяющих одно и то же значение. Значение оценивается единожды.
name = "Dan"
switch name
when "Dave"
print "You are Dave."
when "Dan"
print "You are not Dave, but Dan."
else
print "You are neither Dave nor Dan."
-- Операторы `switch` могут быть также использованы как выражения, а также при сравнении множества
-- значений. Значения должны быть на той же строке, что и `when`, если они в одном выражении.
b = 4
next_even = switch b
when 1 then 2
when 2, 3 then 4
when 4, 5 then 6
else error "I can't count that high! D:"
--------------------------------------------------
-- 5. Объектно-ориентированное программирование
--------------------------------------------------
-- Классы создаются при помощи ключевого слова `class` стоящего рядом с идентификатором,
-- обычно записанного КэмелКейсом. Значения, характерные для класса могут использовать @
-- как идентификатор вместо записи `self.value`.
class Inventory
new: => @items = {}
add_item: (name)=> -- обратите внимание на использование толстой стрелки для классов!
@items[name] = 0 unless @items[name]
@items[name] += 1
-- Функция `new` внутри класса является особенной, потому что она вызывается
-- когда создается экземпляр класса.
-- Создать экземпляр класса можно просто вызвав класс как функцию.
-- При вызове функций класса используется \ для отделения экземпляра от вызываемой функции.
inv = Inventory!
inv\add_item "t-shirt"
inv\add_item "pants"
-- Значения, определённые в классе - но не функция new() - будут общими
-- для всех экземпляров класса.
class Person
clothes: {}
give_item: (name)=>
table.insert @clothes name
a = Person!
b = Person!
a\give_item "pants"
b\give_item "shirt"
-- выводит как "pants" так и "shirt"
print item for item in *a.clothes
-- Экземпляры класса имеют значение `.__class`, которое равно классу объекта,
-- чей экземпляр создан.
assert(b.__class == Person)
-- Переменные, объявленные в теле класса используя `=` являются локальными,
-- таким образом данные "частные" переменные доступны только в текущем контексте.
class SomeClass
x = 0
reveal: ->
x += 1
print x
a = SomeClass!
b = SomeClass!
print a.x -- nil
a.reveal! -- 1
b.reveal! -- 2
--------------------------------------------------
-- 5.1 Наследование
--------------------------------------------------
-- Ключевое слово `extends` используется для наследования свойств и методов
-- из другого класса.
class Backpack extends Inventory
size: 10
add_item: (name)=>
error "backpack is full" if #@items > @size
super name -- вызывает Inventory.add_item со значением `name`.
-- Т.к. метод `new` не был добавлен, вместо него будет использован
-- метод `new` из класса `Inventory`. Если мы хотим использовать конструктор с применением конструктора из
-- класса `Inventory`, мы должны использовать магическую функцию `super` при вызове `new()`.
-- Когда класс расширяет другой класс, он вызывает метод `__inherited`
-- в родительском классе (если он есть). Он всегда вызывается родительским и дочерним объектом.
class ParentClass
@__inherited: (child)=>
print "#{@__name} был наследован классом #{child.__name}"
a_method: (a, b) => print a .. ' ' .. b
-- Будет напечатано 'ParentClass был наследован классом MyClass'
class MyClass extends ParentClass
a_method: =>
super "hello world", "from MyClass!"
assert super == ParentClass
--------------------------------------------------
-- 6. область видимости
--------------------------------------------------
-- Все значения по-умолчанию локальны. Для объявления переменной как глобальной
-- используется ключевое слово `export`.
export var_1, var_2
var_1, var_3 = "hello", "world" -- var_3 локальная, var_1 - нет.
export this_is_global_assignment = "Hi!"
-- Для того, чтобы сделать классы глобальными также используется `export`.
-- Кроме того, все переменные в КэмелКейсе могут быть автоматически экспортированы при помощи
-- `export ^`, и все значения могут быть экспортированы указанием `export *`.
-- `do` позволяет вам вручную создать область видимости, в которой требуются локальные переменные.
do
x = 5
print x -- nil
-- Здесь мы используем `do` как выражение для создания замыкания
counter = do
i = 0
->
i += 1
return i
print counter! -- 1
print counter! -- 2
-- Ключевое слово `local` используется для определения переменных до их назначения
local var_4
if something
var_4 = 1
print var_4 -- работает, потому что `var_4` была назначена в данной области видимости, не в области `if`
-- Ключевое слово `local` может быть использовано также для затенения существующей переменной
x = 10
if false
local x
x = 12
print x -- 10
-- Используйте `local *` для объявления всех идущих следом переменных.
-- Таким же образом, для объявления всех переменных в КэмелКейсе используйте `local ^`.
local *
first = ->
second!
second = ->
print data
data = {}
--------------------------------------------------
-- 6.1 Import
--------------------------------------------------
-- Значения из таблицы могут быть перенесены в текущую область видимости при помощи ключевых слов `import` и `from`.
-- Имена в списке импорта могут предваряться `\` если они являются модульной функцией.
import insert from table -- local insert = table.insert
import \add from state: 100, add: (value)=> @state + value
print add 22
-- Как и в таблицах, запятые можно исключить из списка импорта для обеспечения более длинных списков
import
asdf, gh, jkl
antidisestablishmentarianism
from {}
--------------------------------------------------
-- 6.2 With
--------------------------------------------------
-- Оператор `with` можно использовать для быстрого вызова и присваивания значений
-- в экземпляре класса (объекте).
file = with File "lmsi15m.moon" -- `file` это значение `set_encoding()`.
\set_encoding "utf8"
create_person = (name, relatives)->
with Person!
.name = name
\add_relative relative for relative in *relatives
me = create_person "Ryan", {"sister", "sister", "brother", "dad", "mother"}
with str = "Hello" -- назначение как выражение! :D
print "original: #{str}"
print "upper: #{\upper!}"
--------------------------------------------------
-- 6.3 Деструктуризация
--------------------------------------------------
-- Деструктуризация может применяться с массивами, таблицами и вложенными таблицами для конвертирования их в локальные переменные
obj2 =
numbers: {1, 2, 3, 4}
properties:
color: "green"
height: 13.5
{numbers: {first, second}, properties: {:color}} = obj2
print first, second, color -- 1 2 green
-- `first` и `second` возвращают [1] и [2] потому что они обрабатываются как массив, но
-- `:color` принимается как `color: color`, таким образом он выставляет себя в значение `color`.
-- Деструктуризацию можно использовать вместо импорта.
{:max, :min, random: rand} = math -- переименовывает math.random в rand
-- Деструктуризацию можно выполнить везде, где может быть выполнено присвоение
for {left, right} in *{{"hello", "world"}, {"egg", "head"}}
print left, right