[js] pattern-matching in a functional way
От: A13x США  
Дата: 14.05.10 10:13
Оценка: 38 (3)
Всем привет.

Недавно потребовалось заиметь простой и легко расширяемый способ сопоставления по образцу.
Было решено сделать конструирование сопоставляющей функции в функциональном стиле, иными словами нечто вроде такого:

// сопоставляющая функция. Её можно инициализировать, чтобы в случае отстутствия шаблона выкидывалось исключение
// к примеру так: matcher = function() { throw new Error("no applicable pattern found") }
var matcher

matcher = combine(PATTERN1, CALLBACK1(OBJ, .. OPTIONAL_ARGS){...}, matcher)
matcher = combine(PATTERN2, CALLBACK2(OBJ, .. OPTIONAL_ARGS){...}, matcher)
// промежуточные результаты matcher можно так же сохранять и использовать в сопоставлении
matcher = combine(PATTERN3, CALLBACK3(OBJ, .. OPTIONAL_ARGS){...}, matcher)

...

// сопоставление
matcher(OBJ, ... OPTIONAL_ARGS)


Пример использования:

    var matcher = function(val, arg) {
        print("matcher fallback: val = " + val + ", arg = " + arg)
    }

    matcher = pm.combine({type: "string"}, function(val, arg) {
        print({expr: "matcher(stringVal, arg)", value: "val = " + val + ", arg = " + arg})
    }, matcher)

    matcher = pm.combine({instanceOf: Function}, function(val, arg) {
        print({expr: "matcher(functionVal, arg)", value: "val = " + val + ", arg = " + arg})
    }, matcher)

    matcher = pm.combine({scheme: {key: "number", value: "any"}}, function(val, arg) {
        print({expr: "matcher({key:number, value:any}, arg)", value: "val = (" + val.key + "," + val.value + "), arg = " + arg})
    }, matcher)

    matcher(5, "one")
    matcher("str", "two")
    matcher(new Function("return 1"), "three")
    matcher({key: 12, value: 34}, "four")
    matcher({key: "some", value: "unk"}, "five")


Реализация (простая как 3 рубля. Расширение сопоставителя — простое добавление):

// namespace
var pm = {}

/**
 * Matcher functions constructors are used in pm.combine method.
 * Each key in this object corresponds to the certain pattern member.
 */
pm._matcherConstructors = {
    instanceOf: function (matcher, instanceTarget) {
        return function (obj) {
            if (obj instanceof instanceTarget) {
                return matcher.apply(this, arguments)
            }
            return false
        }
    },

    type: function (matcher, typeId) {
        return function (obj) {
            if (typeof(obj) === typeId) {
                return matcher.apply(this, arguments)
            }
            return false
        }
    },

    scheme: function (matcher, scheme) {
        return function (obj) {
            if (typeof(obj) !== "object") {
                return false
            }
            for (var i in scheme) {
                if (i in obj) {
                    var target = obj[i]
                    var source = scheme[i]
                    var sourceType = typeof(source)
                    if (sourceType === "string") {
                        if (source === "any" || source == typeof(target)) {
                            continue
                        }

                        return false
                    }

                    if (source !== target) {
                        return false
                    }
                }
                else {
                    return false
                }
            }
            return matcher.apply(this, arguments)
        }
    }
}

/**
 * Creates pattern matching function that accepts the pattern given.
 * The latter combined patterns takes priority over the previously declared ones.
 * @param pattern Pattern to match the target object.
 * @param callback User-defined callback to accept target object as well as the accompanying arguments.
 * @param prevMatcher Previous matcher function created by combine method or null or undefined.
 * @returns Matcher function to be used as follows: matcher.call(objectToBeMatched, optionalArguments...).
 */
pm.combine = function(pattern, callback, prevMatcher) {
    var matcher = function() {
        callback.apply(this, arguments)
        return true
    }

    // join visitor function according to the pattern given
    for (var i in pattern) {
        if (!(i in pm._matcherConstructors)) {
            throw new Error("unexpected pattern tag: " + i)
        }

        matcher = pm._matcherConstructors[i](matcher, pattern[i])
    }

    // if prev matcher either undefined or null - create new function
    if (prevMatcher == null) {
        return matcher
    }
    else {
        return function() {
            if (matcher.apply(this, arguments)) {
                return true
            }
            return prevMatcher.apply(this, arguments)
        }
    }
}

/**
 * Helper function that initializes matcher for all the types of objects with
 * the callback that throws an error.
 */
pm.unknownObjectMatcher = function() {
    throw new Error("unknown object matched")
}


Буду рад любым комментариям
Re[4]: [js] pattern-matching in a functional way
От: Воронков Василий Россия  
Дата: 18.05.10 12:05
Оценка: 38 (1)
Здравствуйте, c-smile, Вы писали:

CS>вопрос состоит в том что стоит ли овчинка генерализации (на уровне библиотеки или языка) или нет (это я уже про tiscript)?


ПМ это не просто набор условий, это прежде всего средства для деконструкции выражений. Т.е. очень важен биндинг. В языках, где есть ПМ, зачастую присутствуют и структуры данных вроде алгебраических типов, анализировать которые можно *только* через ПМ. Или те же связные списки, которые можно разбирать "рекурсивно". Например, функция вычисления длины:

let length(list) = 
  | x::xs -> 1 + length(xs)
  | [] -> 0


x::xs — образец, в котором x это первый элемент списка, а xs — хвост, т.е. все остальные элементы, кроме этого. В ФЯ очень много функций, включая такие ФВП как fold и проч. построены по такой схеме.

Без специальный структур данных ценность ПМ несколько падает, но в принципе он все равно может быть полезен. Например, есть паттерн:

[|x,y,z|]

Этот паттерн проверяет, что длина массива равна трем и связывает все элементы массива с переменными x, y и z. Лаконично и понятно, в отличие от императивных проверок.
Re[6]: [js] pattern-matching in a functional way
От: z00n  
Дата: 19.05.10 04:24
Оценка: 18 (1)
Здравствуйте, c-smile, Вы писали:

CS>На самом деле я ищу приемлемую форму вот этого: http://goessner.net/articles/jsont/

CS>По идее это должно быть выражаемо именно через ПМ но что-то никак каменный цветок не выходит...

C PM было бы как-то так:

// псевдокд JS:

var transform_link = function ( {link:{uri:x1,title:x2}} ) {
    return make_link_helper(x1,x2);
};


Hyperlua это (и многое другое) умеет :

-- реальный Hyperlua код:

local fun transform_link
  | {link = {uri = x1, title = x2}} -> make_link_helper(x1,x2)
end


-- Lua код после компиляции (`#x' это length(x), `~=' значит "неравно") :

local function transform_link(_u0)
  if type(_u0)=='table' and #_u0==0 and _u0.link~=nil and
     type(_u0.link)=='table' and #_u0.link==0 and
     _u0.link.uri~=nil and _u0.link.title~=nil
  then
    return make_link_helper(_u0.link.uri, _u0.link.title)
  else error'pattern-match error'
  end
end
Re: [js] pattern-matching in a functional way
От: c-smile Канада http://terrainformatica.com
Дата: 16.05.10 23:55
Оценка:
Здравствуйте, A13x, Вы писали:

A>Буду рад любым комментариям


А зачем это всё? Можешь привести типовую задачу где это нужно?
Re[2]: [js] pattern-matching in a functional way
От: kochetkov.vladimir Россия https://kochetkov.github.io
Дата: 17.05.10 06:52
Оценка:
Здравствуйте, c-smile, Вы писали:
CS>Здравствуйте, A13x, Вы писали:

A>>Буду рад любым комментариям

CS>А зачем это всё? Можешь привести типовую задачу где это нужно?

В смысле, зачем нужен паттерн-матчинг вообще, или зачем он нужен конкретно в js?
... << RSDN@Home 1.2.0 alpha 4 rev. 1468>>

[Интервью] .NET Security — это просто
Автор: kochetkov.vladimir
Дата: 07.11.17
Re[2]: [js] pattern-matching in a functional way
От: A13x США  
Дата: 17.05.10 07:12
Оценка:
Здравствуйте, c-smile, Вы писали:

CS>Здравствуйте, A13x, Вы писали:


A>>Буду рад любым комментариям


CS>А зачем это всё? Можешь привести типовую задачу где это нужно?


В моем конкретном случае pattern matching понадобился для реализации паттерна "посетитель".
Конкретно — в функцию приходят разнородные объекты, в зависимости от типа объекта (строка, { expr: STRING, val: ANY }, { expr: STRING, val: ANY, source: ANY }, { snippet: ANY }, и т.п., здесь заглавными буквами описан тип) требовалось выполнять разные действия.
Изначально очень не хотелось громоздить кучу if с typeof-ами и in-ами, т.к. это снижало наглядность кода, хотелось более общего и расширяемого решения. В результате пришел к такому вот pattern matching'у.

BTW, упомянутые объекты не могли содержать функции, поэтому реализовать паттерн "посетитель" в чистом виде не удавалось, плюс к тому нужно было различать некоторые объекты имеющие одинаковый тип, но разное содержимое.
Re[3]: [js] pattern-matching in a functional way
От: z00n  
Дата: 17.05.10 07:48
Оценка:
Здравствуйте, A13x, Вы писали:

A>Изначально очень не хотелось громоздить кучу if с typeof-ами и in-ами, т.к. это снижало наглядность кода, хотелось более общего и расширяемого решения. В результате пришел к такому вот pattern matching'у.

Интересно, а если бы у вас был бы внешний компилятор PM, например встроеный в "Closure Compiler" (http://code.google.com/closure/compiler/) — вы бы им пользовались?
Re[4]: [js] pattern-matching in a functional way
От: A13x США  
Дата: 17.05.10 08:56
Оценка:
Здравствуйте, z00n, Вы писали:

Z>Здравствуйте, A13x, Вы писали:


A>>Изначально очень не хотелось громоздить кучу if с typeof-ами и in-ами, т.к. это снижало наглядность кода, хотелось более общего и расширяемого решения. В результате пришел к такому вот pattern matching'у.

Z>Интересно, а если бы у вас был бы внешний компилятор PM, например встроеный в "Closure Compiler" (http://code.google.com/closure/compiler/) — вы бы им пользовались?

Я думал об этом, но показалось, что будет как то неудобно — хотелось бы после редактирования кода сразу смотреть на результат без промежуточной компиляции.
Впрочем, думаю, в итоге можно прийти к компромиссу между отладочным режимом и оптимизированной сборкой в closure compiler — выделить задачу pattern matching-а в отдельную функцию, в которой использовать ту или иную реализацию в зависимости от константы USE_COMPILED_PM_ENGINE. Соответственно при сборке closure compiler'ом выставлять эту константу/дефайн в true и использовать "скомпилированную" функцию матчинга.
Но пока такой необходимости нет.

Кстати, а разве компилятор PM встроен в closure compiler? Я что-то не припоминаю такого.
Re[5]: [js] pattern-matching in a functional way
От: z00n  
Дата: 17.05.10 11:57
Оценка:
Здравствуйте, A13x, Вы писали:

A>Здравствуйте, z00n, Вы писали:


Z>>Здравствуйте, A13x, Вы писали:


A>>>Изначально очень не хотелось громоздить кучу if с typeof-ами и in-ами, т.к. это снижало наглядность кода, хотелось более общего и расширяемого решения. В результате пришел к такому вот pattern matching'у.

Z>>Интересно, а если бы у вас был бы внешний компилятор PM, например встроеный в "Closure Compiler" (http://code.google.com/closure/compiler/) — вы бы им пользовались?

A>Я думал об этом, но показалось, что будет как то неудобно — хотелось бы после редактирования кода сразу смотреть на результат без промежуточной компиляции.


Можно компиляцию повесить на кнопку Save (я примерно так и делал, только на отдельную комбинацию кнопок в емаксе).

A>Впрочем, думаю, в итоге можно прийти к компромиссу между отладочным режимом и оптимизированной сборкой в closure compiler — выделить задачу pattern matching-а в отдельную функцию, в которой использовать ту или иную реализацию в зависимости от константы USE_COMPILED_PM_ENGINE. Соответственно при сборке closure compiler'ом выставлять эту константу/дефайн в true и использовать "скомпилированную" функцию матчинга.

A>Но пока такой необходимости нет.

A>Кстати, а разве компилятор PM встроен в closure compiler? Я что-то не припоминаю такого.


Я у меня есть такой компилятор для Lua, который работает под JVM. Его можно было-бы переписать для Javascript использовав Closure compiler как фронт-энд — но я что-то не думаю, что есть спрос — потому и спросил
Re[6]: [js] pattern-matching in a functional way
От: A13x США  
Дата: 17.05.10 14:28
Оценка:
Здравствуйте, z00n, Вы писали:

Z>Можно компиляцию повесить на кнопку Save (я примерно так и делал, только на отдельную комбинацию кнопок в емаксе).


Ну, во-первых, я не использую кнопку Save (в IntelliJIdea просто нет такой кнопки), во-вторых один из проектов в котором я принимаю участие содержит около 200 яваскрипт файлов. Учитывая довольно медленную компиляцию closure в advanced режиме получим довольно внушительную задержку.

Z>Я у меня есть такой компилятор для Lua, который работает под JVM. Его можно было-бы переписать для Javascript использовав Closure compiler как фронт-энд — но я что-то не думаю, что есть спрос — потому и спросил


Не думаю, что для этой задачи стоило бы использовать closure compiler.
Скорее можно было бы держать описание шаблонов (patterns) и название операций в отдельном JSON файлике по которому бы внешний тул генерировал js файл с функцией сопоставления (match).
Re[7]: [js] pattern-matching in a functional way
От: z00n  
Дата: 17.05.10 21:24
Оценка:
Здравствуйте, A13x, Вы писали:

A>Здравствуйте, z00n, Вы писали:


Z>>Можно компиляцию повесить на кнопку Save (я примерно так и делал, только на отдельную комбинацию кнопок в емаксе).


A>Ну, во-первых, я не использую кнопку Save (в IntelliJIdea просто нет такой кнопки), во-вторых один из проектов в котором я принимаю участие содержит около 200 яваскрипт файлов. Учитывая довольно медленную компиляцию closure в advanced режиме получим довольно внушительную задержку.


А зачем их перекомпилировать каждый раз? Я встроил это компилятор и даже не требовалось использовать внешнюю систему сборки.

Z>>Я у меня есть такой компилятор для Lua, который работает под JVM. Его можно было-бы переписать для Javascript использовав Closure compiler как фронт-энд — но я что-то не думаю, что есть спрос — потому и спросил


A>Не думаю, что для этой задачи стоило бы использовать closure compiler.

Только как фронтэнд — я думаю там обычный рукописный парсер — врядли он сам по себе медленный.

A>Скорее можно было бы держать описание шаблонов (patterns) и название операций в отдельном JSON файлике по которому бы внешний тул генерировал js файл с функцией сопоставления (match).


По моему это ужасно неудобно Идеальным вариантом было бы поддержание яваскриптом PM изначально — разве нет? Вы просто создаете такой язык: JS + PM, даете файлам другое расширение, перед использованием компилируете в читаемый, отформатированный яваскрипт, в котором все случае использования PM заменены на дерево if-ов.
Re[3]: [js] pattern-matching in a functional way
От: c-smile Канада http://terrainformatica.com
Дата: 17.05.10 23:06
Оценка:
Здравствуйте, kochetkov.vladimir, Вы писали:

KV>Здравствуйте, c-smile, Вы писали:

CS>>Здравствуйте, A13x, Вы писали:

A>>>Буду рад любым комментариям

CS>>А зачем это всё? Можешь привести типовую задачу где это нужно?

KV>В смысле, зачем нужен паттерн-матчинг вообще, или зачем он нужен конкретно в js?


конкретно в JS, но и вообще также интересно.

В JS имплементация оного это просто набор

var obj = ...
if( test(obj, pattern1) ) ...;
else if( test(obj, pattern2) ) ...;
else if( test(obj, pattern3) ) ...;


вопрос состоит в том что стоит ли овчинка генерализации (на уровне библиотеки или языка) или нет (это я уже про tiscript)?
Re[4]: [js] pattern-matching in a functional way
От: FR  
Дата: 18.05.10 03:56
Оценка:
Здравствуйте, c-smile, Вы писали:


CS>конкретно в JS, но и вообще также интересно.


Вообще очень хорошая вещь, прилично увеличивает мощность языка.

CS>В JS имплементация оного это просто набор


CS>
CS>var obj = ...
CS>if( test(obj, pattern1) ) ...;
CS>else if( test(obj, pattern2) ) ...;
CS>else if( test(obj, pattern3) ) ...;
CS>


Еще биндинг переменных нужен.

CS>вопрос состоит в том что стоит ли овчинка генерализации (на уровне библиотеки или языка) или нет (это я уже про tiscript)?


Стоит, но вопрос что разбирать в JS в функциональных языках вся мощь ПМ держится на разборе алгебраических типов данных.
Хотя если посмотреть на динамический Эрланг то наверно можно многое позаимствовать.
Re[5]: [js] pattern-matching in a functional way
От: c-smile Канада http://terrainformatica.com
Дата: 18.05.10 23:21
Оценка:
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Здравствуйте, c-smile, Вы писали:


CS>>вопрос состоит в том что стоит ли овчинка генерализации (на уровне библиотеки или языка) или нет (это я уже про tiscript)?


ВВ>ПМ это не просто набор условий, это прежде всего средства для деконструкции выражений. Т.е. очень важен биндинг. В языках, где есть ПМ, зачастую присутствуют и структуры данных вроде алгебраических типов, анализировать которые можно *только* через ПМ. Или те же связные списки, которые можно разбирать "рекурсивно". Например, функция вычисления длины:


ВВ>
ВВ>let length(list) = 
ВВ>  | x::xs -> 1 + length(xs)
ВВ>  | [] -> 0
ВВ>


Ну как бы car/cdr и без ПМ можно использовать.

function length(elem)
{
  return elem? 
     1 + length( elem.next ):
     0;
}


ВВ>x::xs — образец, в котором x это первый элемент списка, а xs — хвост, т.е. все остальные элементы, кроме этого. В ФЯ очень много функций, включая такие ФВП как fold и проч. построены по такой схеме.


ВВ>Без специальный структур данных ценность ПМ несколько падает, но в принципе он все равно может быть полезен. Например, есть паттерн:


ВВ>[|x,y,z|]


ВВ>Этот паттерн проверяет, что длина массива равна трем и связывает все элементы массива с переменными x, y и z. Лаконично и понятно, в отличие от императивных проверок.


Это уже более интересно.

На самом деле я ищу приемлемую форму вот этого: http://goessner.net/articles/jsont/
По идее это должно быть выражаемо именно через ПМ но что-то никак каменный цветок не выходит...
Re[6]: [js] pattern-matching in a functional way
От: Воронков Василий Россия  
Дата: 19.05.10 00:44
Оценка:
Здравствуйте, c-smile, Вы писали:

CS>Ну как бы car/cdr и без ПМ можно использовать.


Все, что можно делать с помощью ПМ-а, можно в принципе и без ПМ-а, если только в языке нет каких-либо специальных ограничений.
Просто ПМ декларативен и часто позволяет сильно "ужать" код. Паттерны же могут быть и вложенными. x::xs — это простейший пример. Можно и так:

x:(e1, e2)::xs

Тут мы уже хотим, чтобы второй элемент был кортежем и связываем его элементы с переменными.

ВВ>>Без специальный структур данных ценность ПМ несколько падает, но в принципе он все равно может быть полезен. Например, есть паттерн:

ВВ>>[|x,y,z|]
ВВ>>Этот паттерн проверяет, что длина массива равна трем и связывает все элементы массива с переменными x, y и z. Лаконично и понятно, в отличие от императивных проверок.
CS>Это уже более интересно.

CS>На самом деле я ищу приемлемую форму вот этого: http://goessner.net/articles/jsont/

CS>По идее это должно быть выражаемо именно через ПМ но что-то никак каменный цветок не выходит...

JSONT — это типа XSLT для JSON? Может, и стоит тогда посмотреть на ПМ в XSLT? Ведь задачи ровно те же самые. Нечто подобное XPath вполне прокатило бы.
Кстати, для трансформации JSON-а я сейчас XSLT и использую, вполне удобно получается.
Re[6]: [js] pattern-matching in a functional way
От: Mamut Швеция http://dmitriid.com
Дата: 24.05.10 13:58
Оценка:
ВВ>>[|x,y,z|]

ВВ>>Этот паттерн проверяет, что длина массива равна трем и связывает все элементы массива с переменными x, y и z. Лаконично и понятно, в отличие от императивных проверок.


CS>Это уже более интересно.


CS>На самом деле я ищу приемлемую форму вот этого: http://goessner.net/articles/jsont/

CS>По идее это должно быть выражаемо именно через ПМ но что-то никак каменный цветок не выходит...


Не знаю, насколько это будет полезно, но обработка подобного в терминах Erlang'а была бы, как показано ниже. Самое главное, что вне зависимости от структуры, подход всегда одинаковый. Указываем на входе функции, параметр какого типа нам нужен, и какие именно значения из этого параметра нам нужны — и вперед

(сорри за Erlang, код не самый красивый, нет некоторых проверок)

Оригинал:
JSON:

{ "line": { "p1": {"x":2, "y":3},
            "p2": {"x":4, "y":5} }}

JSONT:

{ "self": "<svg>{line}</svg>",
  "line": "<line x1=\"{$.p1.x}\" y1=\"{$.p1.y}\"" +
                "x2=\"{$.p2.x}\" y2=\"{$.p2.y}\" />" }


RESULT:
<svg><line x1="2" y1="3"x2="4" y2="5" /></svg>


На Erlang'е:
%% [] -> список/массив
%% {} -> кортеж (тупл)
%% с маленькой буквы — атомы, http://www.trapexit.org/Atom
%% с большой буквы — переменные
%% последняя строка в функции является ее возвращаемым значением
%% _ — это placeholder на случай, если значение нам не нужно

parse_data(Data) ->
    self(Data).    

self(Data) ->
    %% строк в Erlang'е нет, есть списки
    ["<svg>", elements(Data) ,"</svg>"].

%% Head содержит первый элемент
%% Tail — остаток списка
elements([Head|Tail]) ->
    [element(Head), elements(Tail)].


%% принимаем только структуру вида
%% {line, {p1, {X1, Y2}},
%%        {p2, {X2, Y2}}}
%% при этом значения координат сразу закидываем
%% в соответствующие переменные (X1, X2, Y1, Y2)

element({line, {p1, {X1, Y2}},
               {p2, {X2, Y2}}}) ->
    ["<line x1=\"", X1, "\" y1=\"", Y1, "\" x2=\"", X2, "\" y1=\"", Y2, "\" />"];

%% принимаем только структуру вида
%% {circle, {center, {X, Y}},
%%          {radius, R}}
%% при этом значения координат сразу закидываем
%% в соответствующие переменные (X, Y, R)

element({circle, {center, {X, Y}},
                 {radius, R}}) ->
    ["<circle x=\"", X, "\" y=\"", Y, "\" r=\"", R, "\" />"];

%% любые другие элементы просто пропускаем
element(_) ->
    "".



Такой код с легкостью пробежится по списку такого типа:
[
   {line, {p1, {0, 0}},
          {p2, {10, 10}}},
   {line, {p1, {0, 0}},
          {p2, {-10, 10}}},
   {circle, {center, {0, 0}},
            {r, 5}},
   {line, {p1, {10, 0}},
          {p2, {-10, -10}}}
]



для следующего примера код придется переписать, естественно, но он будет выглядеть, в целом, так же.
%% данные
%% ["red", "green", "blue"]
%% результат:
%% <ul>
%%  <li>red</li>
%%  <li>green</li>
%%  <li>blue</li>
%% </ul>

parse_data(Data) -> self(Data).

self(Data) - > ["<ul>", elements(Data), "</ul>"].

elements([Head|Tail]) ->
    [element(Head), elements(Tail)].

element(E) -> ["<li>", E, "</li>"].

%% или, еще проще с list comprehensions:
%% self(Data) - > [
%%     "<ul>"
%%   , [["<li>", E, "</li>"] || E <- Data]
%%   , "</ul>"
%% ].




последний пример:
Оригинал:
{ "color": "blue",
  "closed": true,
  "points": [[10,10],[20,10],[20,20],[10,20]] }

+

{ "self": "<svg><{closed} stroke=\"{color}\" points=\"{points}\" />"+
          "</svg>",
  "closed": function(x){return x ? "polygon" : "polyline";}, 
  "points[*][*]": "{$} " }
=

<svg><polygon stroke="blue" points="10 10 20 10 20 20 10 20 " /></svg>


Erlang:
Data = {
    {color, "blue"},
    {closed, true},
    {points, [[10,10],[20,10],[20,20],[10,20]]}
}.

parse_data(Data) -> self(Data).

self(Data) -> ["<svg>", elements(Data) ,"</svg>"].

% тут варианты извращений, все можно сделать проще, естественно :)
elements({Color, Closed, Points}) ->
    ["<", type(Closed), stroke(Color), points(Points), "/>"].

closed({closed, true}) ->
    "polygon ";
closed(_) ->
    "polyline ";

stroke({color, Color}) ->
    ["stroke=\"", Color, "\" "];

%% одна функция points разбирает принципиально разные структуры данных
points({points, Point}) ->
    ["stroke=\"", points(Points), "\" "];

points([[X, Y]|T]) ->
    [X, Y, points(T)].


dmitriid.comGitHubLinkedIn
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.