Что такое функция батута? - PullRequest
       12

Что такое функция батута?

84 голосов
/ 10 октября 2008

Во время недавних обсуждений на работе кто-то упомянул функцию батута.

Я прочитал описание в Википедии . Достаточно дать общее представление о функциональности, но я бы хотел кое-что более конкретное.

У вас есть простой фрагмент кода, который иллюстрирует батут?

Ответы [ 7 ]

60 голосов
/ 29 января 2009

Существует также LISP-ощущение «батут», как описано в Википедии:

Используется в некоторых реализациях LISP, батут это петля, которая итеративно вызывает возвратные функции. одного батута достаточно для выразить все контрольные передачи программа; программа так выражена батут или в «батутном стиле»; преобразование программы в батуте стиль прыжки на батуте. Trampolined функции могут быть использованы для реализации вызовы хвостовой рекурсивной функции в стековые языки

Допустим, мы используем Javascript и хотим написать простую функцию Фибоначчи в стиле продолжения передачи. Причина, по которой мы это сделаем, не имеет значения - например, перенести Scheme на JS или поиграть с CPS, который мы все равно должны использовать для вызова серверных функций.

Итак, первая попытка

function fibcps(n, c) {
    if (n <= 1) {
        c(n);
    } else {
        fibcps(n - 1, function (x) {
            fibcps(n - 2, function (y) {
                c(x + y)
            })
        });
    }
}

Но выполнение этого с n = 25 в Firefox приводит к ошибке «Слишком много рекурсии!». Теперь это именно та проблема (отсутствует оптимизация хвостового вызова в Javascript), которую решает прыжок на батуте. Вместо того, чтобы (рекурсивно) вызывать функцию, давайте return инструкция (thunk) для вызова этой функции, которая будет интерпретироваться в цикле.

function fibt(n, c) {
    function trampoline(x) {
        while (x && x.func) {
            x = x.func.apply(null, x.args);
        }
    }

    function fibtramp(n, c) {
        if (n <= 1) {
            return {func: c, args: [n]};
        } else {
            return {
                func: fibtramp,
                args: [n - 1,
                    function (x) {
                        return {
                            func: fibtramp,
                            args: [n - 2, function (y) {
                                return {func: c, args: [x + y]}
                            }]
                        }
                    }
                ]
            }
        }
    }

    trampoline({func: fibtramp, args: [n, c]});
}
34 голосов
/ 12 августа 2012

Позвольте мне добавить несколько примеров факториальной функции, реализованной с помощью батутов, на разных языках:

Scala:

sealed trait Bounce[A]
case class Done[A](result: A) extends Bounce[A]
case class Call[A](thunk: () => Bounce[A]) extends Bounce[A]

def trampoline[A](bounce: Bounce[A]): A = bounce match {
  case Call(thunk) => trampoline(thunk())
  case Done(x) => x
}

def factorial(n: Int, product: BigInt): Bounce[BigInt] = {
    if (n <= 2) Done(product)
    else Call(() => factorial(n - 1, n * product))
}

object Factorial extends Application {
    println(trampoline(factorial(100000, 1)))
}

Java:

import java.math.BigInteger;

class Trampoline<T> 
{
    public T get() { return null; }
    public Trampoline<T>  run() { return null; }

    T execute() {
        Trampoline<T>  trampoline = this;

        while (trampoline.get() == null) {
            trampoline = trampoline.run();
        }

        return trampoline.get();
    }
}

public class Factorial
{
    public static Trampoline<BigInteger> factorial(final int n, final BigInteger product)
    {
        if(n <= 1) {
            return new Trampoline<BigInteger>() { public BigInteger get() { return product; } };
        }   
        else {
            return new Trampoline<BigInteger>() { 
                public Trampoline<BigInteger> run() { 
                    return factorial(n - 1, product.multiply(BigInteger.valueOf(n)));
                } 
            };
        }
    }

    public static void main( String [ ] args )
    {
        System.out.println(factorial(100000, BigInteger.ONE).execute());
    }
}

C (неудачно без реализации больших чисел):

#include <stdio.h>

typedef struct _trampoline_data {
  void(*callback)(struct _trampoline_data*);
  void* parameters;
} trampoline_data;

void trampoline(trampoline_data* data) {
  while(data->callback != NULL)
    data->callback(data);
}

//-----------------------------------------

typedef struct _factorialParameters {
  int n;
  int product;
} factorialParameters;

void factorial(trampoline_data* data) {
  factorialParameters* parameters = (factorialParameters*) data->parameters;

  if (parameters->n <= 1) {
    data->callback = NULL;
  }
  else {
    parameters->product *= parameters->n;
    parameters->n--;
  }
}

int main() {
  factorialParameters params = {5, 1};
  trampoline_data t = {&factorial, &params};

  trampoline(&t);
  printf("\n%d\n", params.product);

  return 0;
}
18 голосов
/ 10 октября 2008

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

Мне нужно было иметь возможность сканировать все файлы, которые загружались игрой, для модификации. Таким образом, самый надежный способ сделать это - использовать батут для CreateFileA. Поэтому, когда игра была запущена, я бы нашел адрес для CreateFileA, используя GetProcAddress, затем я изменил бы первые несколько байтов функции и вставил ассемблерный код, чтобы перейти к моей собственной функции «батута», где я бы сделал некоторые вещи, и Затем я бы перейти к следующему месту в CreateFile после моего кода JMP. Надежно сделать это надежнее, но основная идея - просто перехватить одну функцию, принудительно перенаправить ее на другую функцию и затем вернуться к исходной функции.

Редактировать: Microsoft имеет структуру для такого рода вещей, на которую вы можете посмотреть. Называется Обходы

7 голосов
/ 11 июля 2009

В настоящее время я экспериментирую со способами реализации оптимизации хвостового вызова для интерпретатора Scheme, и в настоящий момент я пытаюсь выяснить, будет ли батут для меня осуществимым.

Насколько я понимаю, это в основном просто серия вызовов функций, выполняемых функцией батута. Каждая функция называется thunk и возвращает следующий шаг в вычислении, пока программа не завершится (пустое продолжение).

Вот первый фрагмент кода, который я написал, чтобы улучшить мое понимание батута:

#include <stdio.h>

typedef void *(*CONTINUATION)(int);

void trampoline(CONTINUATION cont)
{
  int counter = 0;
  CONTINUATION currentCont = cont;
  while (currentCont != NULL) {
    currentCont = (CONTINUATION) currentCont(counter);
    counter++;
  }
  printf("got off the trampoline - happy happy joy joy !\n");
}

void *thunk3(int param)
{
  printf("*boing* last thunk\n");
  return NULL;
}

void *thunk2(int param)
{
  printf("*boing* thunk 2\n");
  return thunk3;
}

void *thunk1(int param)
{
  printf("*boing* thunk 1\n");
  return thunk2;
}

int main(int argc, char **argv)
{
  trampoline(thunk1);
}

Результат:

meincompi $ ./trampoline 
*boing* thunk 1
*boing* thunk 2
*boing* last thunk
got off the trampoline - happy happy joy joy !
7 голосов
/ 10 октября 2008

Вот пример вложенных функций:

#include <stdlib.h>
#include <string.h>
/* sort an array, starting at address `base`,
 * containing `nmemb` members, separated by `size`,
 * comparing on the first `nbytes` only. */
void sort_bytes(void *base,  size_t nmemb, size_t size, size_t nbytes) {
    int compar(const void *a, const void *b) {
        return memcmp(a, b, nbytes);
    }
    qsort(base, nmemb, size, compar);
}

compar не может быть внешней функцией, поскольку она использует nbytes, которая существует только во время вызова sort_bytes. На некоторых архитектурах небольшая функция-заглушка - батут - генерируется во время выполнения и содержит расположение в стеке текущего вызова sort_bytes. При вызове он переходит на код compar, передавая этот адрес.

Этот беспорядок не требуется в таких архитектурах, как PowerPC, где ABI указывает, что указатель функции на самом деле является «толстым указателем», структурой, содержащей как указатель на исполняемый код, так и другой указатель на данные. Однако в x86 указатель на функцию является просто указателем.

0 голосов
/ 08 ноября 2015
typedef void* (*state_type)(void);
void* state1();
void* state2();
void* state1() {
  return state2;
}
void* state2() {
  return state1;
}
// ...
state_type state = state1;
while (1) {
  state = state();
}
// ...
0 голосов
/ 29 января 2009

Для C батутом будет указатель на функцию:

size_t (*trampoline_example)(const char *, const char *);
trampoline_example= strcspn;
size_t result_1= trampoline_example("xyzbxz", "abc");

trampoline_example= strspn;
size_t result_2= trampoline_example("xyzbxz", "abc");

Edit: более эзотерические батуты будут неявно генерироваться компилятором Одним из таких применений будет таблица прыжков. (Хотя есть и более сложные из них, чем дальше вы начинаете создавать сложный код.)

...