Skip to content

Instantly share code, notes, and snippets.

@Heimdell
Last active January 13, 2018 18:27
Show Gist options
  • Select an option

  • Save Heimdell/a079930bdd0c3d2a9cf3f37ee4431e76 to your computer and use it in GitHub Desktop.

Select an option

Save Heimdell/a079930bdd0c3d2a9cf3f37ee4431e76 to your computer and use it in GitHub Desktop.

Кодировка

Программный код должен быть в кодировке UTF-8. Символы вне диапазона [32.. 127] допускаются только внутри строковых литералов. Табуляция запрещена.

Лексемы

Виды лексем:

  1. Пунктуация

    Один символ из набора {}[]();..

  2. Строковый литерал

    Множество блоков "[^"]*" , при склеивании блоков между ними вставляется символ ", если между блоками нет пробела. Или, один блок '[^']*'.

    "hello ""damn"" world"

    'hello "damn" world'

  3. Число

    Обычное число с плавающей точкой и возможной экспонентой.

    [-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?

    10.234e-12

  4. Комментарий

    -- Обычный комментарий (считается пробельным символом)
    --- Документирующий комментарий (может использоваться только в начале файла и перед определениями).
    
  5. Имя

    Состоит из непустой последовательности непробельных символов в диапазоне [32.. 127], исключая символы {}[]();. и '".

    TheModule, TheConstructor, TheType, THE-CONSTANT, ordinary-name

Литералы

Число

Литерал числа представляет из себя лексему "Число", описанную выше.

Строка

Литерал строки представляет из себя лексему "Строка", описанную выше.

Функция

Функция может быть задана определением или лямбдой:

(f, g)(x, y) -> g(f(x), f(y))

Есть вариант лямбды с безымянным аргументом, к которому можно обращаться, как к it.

-> it + 4

Объект

Содержит определения

{
; foo = 1
; bar = "2"
; { -- 'sin', 'cos' и 'tangent' не просочатся в окружающий объект,
    --  в отличие от 'Math.abs' и 'abs-tangent'
  ; import   native.math         { sin, cos }
  ; reexport native.math as Math { abs }
  ; abs-tangent(x) = Math.abs(tangent(x))
  ; local tangent(x) = cos(x) / sin(x)
  }
; inverse-tangent(x) = Math.abs(1 / tangent(x))

  -- 'a' видно в внутреннем объекте, но не видно во внешнем
  -- 'b' видно в обоих объектах
  --  оба имени не экспортируются
; private
  { ~ a = 1      -- слова private, local и ~ означают одно и то же
  ;   b = 2 + a
  }
}

Операторы

Любое имя можно использовать как оператор.

list map(toString) filter(-> it length () < 4) join(",")

будет воспринято парсером, как

join(",", filter(-> less(4, length(it)), map(toString, list)))

Т.е.,

foo bar(a, b, c) === bar(a, b, c, foo)

Возможно передать более одного списка аргументов, если функция является каррированной.

foo bar(a, b)(c, d) roll(e, f) === roll(e, f, bar(a, b)(c, d, foo))

Выражения

Выражение может быть

  • Литералом
  • Вызовом функции
  • Условным выражением
  • if-выражением
    if {
        | a > 2       ?  a
        | not(big(a)) ?  0
        | else        ? -1
    }
    
  • case без захвата
    case list {
        | Cons(x, xs) -> x
        | Nil         -> 0
    }
    
  • case с захватом
    case list as l {
      | Cons(x, xs) -> l Cons x
      | Nil         -> l
    }
    
  • Лямбда-case без захвата
    is-empty = case {
      | Cons? -> True
      | Nil?  -> False
    }
    
  • Лямбда-case с захватом
    dup-head = case as l {
      | Cons(y, _) -> l Cons y
      | Nil        -> l
    }
    
  • Специальным оператором
    foo = some-object with {
      ; a = 1
      ; b -> it + 4
      ; c deleted
    }
    

Определение

Является:

  • определением [функции];
  • импортом;
  • объектом (локальные определения не попадут во внешний объект). По умолчанию экспортируется. Для подавления экспорта можно использовать слово private.

private

Подавляет экпорт следующего за ним объявления или блока объявлений. Может быть заменено на local или ~

private foo = 1

private {
  ; bar(x) = x * x
  ; y = 1
  ; open module('data.list') only 'sort'
}

Определение функции

Имеет вид:

main =
	input('Your name?').then (name) ->
	log('Hello, ' + name + '!')

или, например

--- Не экспортируется (private).
--- `on(.name, equal)` возвращает функцию
--- `(x, y) -> equal(.name(x), .name(y))`
private on(f, g2)(x, y) =
  g2(f(x), f(y))

или, в форме оператора

; a <=> b = if {
  | a < b ? Less
  | b < a ? Greater
  | else  ? Equal
}

Импорт

Импорт модуля

--- не реэкспортируются
import data.list   as List
import data.string as String only {sort; split; join}
--- ИЛИ
import {
  ; data.list   as List
  ; data.string as String only {
    ; sort
    ; split
    ; join
  }
}

--- импортируются и реэкспортируются
; reexport control.monad { when; guard }

Отсутсвие ключевого слова return

Функция возвращает просто своё тело, поэтому никакого смысла писать return нет, что позволит позже объявлять return как метод. Если необходимо сделать какие-то действия с побочным эффектом, то можно сделать так:

do-stuff (a, b, c) ({log, pure, modify}) =
  log(a)                          then _ ->
  modify (state -> a / b + state) then x ->
  return (x * c)  -- return - это функция!

Да, это выглядит как говно.

Здесь последний аргумент функции - это контекст (ну или монада).

В последствии я планирую ввести синтаксис, чтобы писать так:

do-stuff (a, b, c) = do {log, modify} (
  ; log (a)
  ; x <- modify (-> it + a/b)
  ; return (x * c)
)

Монада

Монада, в случае этого языка - это модуль-абстракция над каким-то типом-контейнером, который объявляет:

  1. Операцию return (или pure), которая делает из любого объекта контейнер с одним-единственным этим объектом.
  2. Операцию join, которая из двух вложенных контейнеров с какими-то объектами делает 1 контейнер.
  3. Контейнер, возвращаемый return, должен иметь операцию then, которая цепляет к нему коллбэк, как Promise#then.
  4. Так же у контейнера должна быть функция map.

Монада позволяет строить последовательные вычисления, которые автоматически будут асинхронными: ввод-вывод, например.

Do-синтаксис

Есть несколько форм:

  • Конкретный модуль-монада
    do module1 (
      ; e  <- action(args)
      ; e2 <- action(2, ...args2, e)
        ...
      ; return eN
    )
    
    Здесь захватывается конкретный toplevel-модуль, и все его методы доступны внутри блока.
  • Конкретный модуль-монада, отдельные методы
    do module1 {action1, action2} (
      ; e  <- action(args)
      ; e2 <- action(2, ...args2, e)
        ...
      ; return eN
    )
    
    Здесь из конкретного toplevel-модуля захватываются только упомянутые действия.
  • Лямбда с захватом конкретных методов
    do {action1, action2} (
      ; e  <- action(args)
      ; e2 <- action(2, ...args2, e)
        ...
      ; return eN
    )
    
    Этот блок аналогичен следующему:
    (some-module) -> do some-module {action1, action2} (
      ; e  <- action(args)
      ; e2 <- action(2, ...args2, e)
        ...
      ; return eN
    )
    
    Так же можно использовать do {...} для захвата всех методов
  • Вложенный do-блок
    do (
      ; e  <- action(args)
      ; e2 <- action(2, ...args2, e)
        ...
      ; return eN
    )
    
    Используется внутри других do-блоков, повторяет их захват.

Let-выражения

То, что в жаваскрипте можно записать как

let qux = (foo, bar) -> {
  let x = foo
  let y = bar.list
  var z = []
  return asPromise(fs.readFile, "ctx.ini").then(res => {
    for (var i = 0; i < y.length; i++) {
      console.log(y[i])
      z.push(y[i] + res.length)
    }
    return z
  })
}

здесь будет выглядеть, как

qux (foo, bar) = do (IO)
  x = foo
  y = bar.list
  res <- readFile "ctx.ini"
  z <- y for (elem) -> do
    log(elem)
    return (elem + length(res))
  return z

Действие функции for(mapper) (containter) покажу на примере:

res <- [1, 2, 3] for (elem) -> do
  print(elem)
  return (elem + 1)

эквивалентно

printAndInc (x) = do
  print(x)
  return (elem + 1) 

res <- (
  printAndInc(1) then (a) ->
  printAndInc(2) then (b) ->
  printAndInc(3) map (c) -> [a, b, c]
)

Обе версии печатают 1\n2\n3\n и возвращают [2,3,4].

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment