Изучаем 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

results matching ""

    No results matching ""