[ XTX ] 3. Пользовательские функции

Блог о компьютерах и еде :)

Модератор: ANDLL

ANDLL
Великий гастроном
Великий гастроном
Аватара пользователя
 
Сообщения: 3450
Зарегистрирован: 29.06.2003 (Вс) 18:55

[ XTX ] 3. Пользовательские функции

Сообщение ANDLL » 27.03.2009 (Пт) 23:15

Итак, в версии номер 3 появилась первая фича специфичная для функционального языка программирования - теперь у нас есть рекурсивные пользовательские функции.

Настало время написать на нашем языке первую программу с функцией.
Вообще, напомним что XTX будет исповедовать функциональную парадигму программирования. И разумеется первое что мы напишем на нем - программу которая считает факториал.
Для отступления, напомним как выглядит эта программа при императивном подходе(C#):
Код: Выделить всё
int F(int n)
{
int r = 1;
for(int i=1; i<=n; i++)
r*=i;
return r;
}
Аналогичная функция при функциональном подходе выглядит вот так:(F#):
Код: Выделить всё
let rec F n = if n=1 then 1 else n*F(n-1)

На нашем языке эта же функция выглядит вот так:
Код: Выделить всё
<?xml version="1.0" encoding="utf-8" ?>
<concat>
  <f isdefinition = "1">
    <if>
      <equals>
        <?param *[1]?>
        <n>1</n>
      </equals>
      <n>1</n>
      <multiple>
        <?param *[1]?>
        <f>
          <dec>
            <?param *[1]?>
          </dec>
        </f>
      </multiple>
    </if>
  </f>
  <f>
    <n>10</n>
  </f>
</concat>

Немного длинновато? :)
Если разобраться, то функция вводится очень просто - любой узел с атрибутом isdefinition = "1" является объявлением функции.
Вызов функции выглядит так: <имя функции>узел-параметр</имя функции>
Что бы получить значение параметра из функции используется конструкция <?param xpath-выражение?>
Когда интерпретатор встречает такой узел, он вычисляет значение xpath-выражение относительно узла-параметра и выполняет содержащийся в нем код.
Благодаря этому можно передавать в функцию произвольное число параметров - и это совершенно естественно и это делается без каких либо специальных конструкций языка, куда более естественно чем тот же paramarray.

Тут есть кое что особенное
В вычислении параметров есть одна довольно существенная деталь. Во всех нормальных яп параметры сначала вычисляются, а потом передаются в вызывающую функцию. В XTX это не так - в функцию передаются не параметры, а скорее ссылка на код, который вычисляет эти параметры. Параметры вычисляются в момент первого обращения к ним из вызванной функции. У этого явления есть одно важное преимущество - на XTX пользователю можно написать, к примеру функцию iif, которая не будет вычислять значение "неверной" ветки. Huh?

Ну и в конце в очередной раз отмечу что функции - это всего лишь расширение языка, приведем модуль который его реализует(C#):
Код: Выделить всё
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using XTX.parser;
using System.Xml;
using XTX.parser.extensions;

namespace XTX.library
{
    namespace XTX.library
    {
        class Functions
        {
            IRunTime w;
            Stack<StackFrame> paramStack = new Stack<StackFrame>();

            public Functions(IRunTime _w)
            {
                if (_w == null)
                    throw new ArgumentNullException();
                w = _w;
            }

            [Preparser()]
            public void createFunctions(XmlNode e)
            {
                foreach (XmlNode n in e.SelectNodes("//*[@isdefinition = '1']"))
                {
                    w.add_object_handleMap(new Function(this, n));
                    w.runTimeSkip(n);
                }
            }

            [RegisterAction()]
            public object param(XmlNode e)
            {
                string xpath;
                if (e.NodeType == XmlNodeType.Element)
                    xpath = w.extractParam(e, "w").Value;
                else if (e.NodeType == XmlNodeType.ProcessingInstruction)
                    xpath = e.Value;
                else
                    throw new ArgumentException();
                StackFrame localFrame = null;
                try
                {
                    localFrame = paramStack.Pop();
                    object value;
                    if (localFrame.paramCache.TryGetValue(xpath, out value))
                        return using_cache(value);
                    XmlNode param = localFrame.node.SelectSingleNode(xpath);
                    if (param == null)
                        return w.warn_null();
                    value = w.handle(param);
                    localFrame.paramCache.Add(xpath, value);
                    return not_using_cache(value);
                }
                finally
                {
                    if (localFrame != null)
                        paramStack.Push(localFrame);
                }
            }

#region("Cache statistics")
            bool print_statistic = false;
            int cache_usage;
            private object using_cache(object value)
            {
                if (print_statistic)
                {
                    cache_usage++;
                    Console.WriteLine("Cache is used " + cache_usage + " times");
                }
                return value;
            }
            int cache_nonusage;
            private object not_using_cache(object value)
            {
                if (print_statistic)
                {
                    cache_nonusage++;
                    Console.WriteLine("Cache was not used " + cache_nonusage + " times");
                }
                return value;
            }
#endregion

            protected class Function
            {
                Functions f;
                IRunTime w;
                XmlNode n;

                public Function(Functions f, XmlNode n)
                {
                    if (f == null || n==null)
                        throw new ArgumentNullException();
                    this.f = f;
                    this.n = n;
                    w = f.w;
                }
               
                [RegisterAction(dynamicName=true)]
                public object run(XmlNode e)
                {
                    if (e == null)
                        return n.Name;
                    try
                    {
                        // How to guarantee that pop operation runs only if push will be successful?
                        f.paramStack.Push(new StackFrame(e));
                        return w.handle(w.xmlFirstChild(n));
                    }
                    finally
                    {
                        f.paramStack.Pop();
                    }
                }
            }

            private class StackFrame
            {
                public XmlNode node;
                public Dictionary<string, object> paramCache = new Dictionary<string, object>();

                public StackFrame(XmlNode _node)
                {
                    if (_node == null)
                        throw new ArgumentNullException("_node");
                    node = _node;
                }

            }
        }
    }
}
Гастрономия - наука о пище, о ее приготовлении, употреблении, переварении и испражнении.
Блог

Вернуться в Великий гастроном andll

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 4

    TopList  
cron