ฟังก์ชั่นแทรมโพลีนคืออะไร?


94

ในระหว่างการสนทนาล่าสุดในที่ทำงานมีคนพูดถึงฟังก์ชันแทรมโพลีน

ผมได้อ่านคำอธิบายที่วิกิพีเดีย มันเพียงพอที่จะให้แนวคิดทั่วไปเกี่ยวกับฟังก์ชันการทำงาน แต่ฉันต้องการบางสิ่งที่เป็นรูปธรรมมากกว่านี้

คุณมีโค้ดง่ายๆที่จะแสดงให้เห็นถึงแทรมโพลีนหรือไม่?


2
ในโลกของ Microsoft มักเรียกแทรมโพลีนว่า 'thunks' [นี่คือหน้า] [1] จาก "การออกแบบ C ++ สมัยใหม่" ของ Andrei Alexandrescu ---- [1]: books.google.com/…
Michael Burr


เป็นพื้นฐานรูปแบบทั่วไปของฟังก์ชันบางอย่างที่คุณสามารถใช้กับ setjmp / lomgjmp ได้กล่าวคือเพื่อหลีกเลี่ยงการไหลเวียนของซ้อน
Ingo

12
ทำไมทุกคนถึงต้องการหลีกเลี่ยง stackoverflow?
Nikole

คำตอบ:


71

นอกจากนี้ยังมีความรู้สึก LISP ของ 'trampoline' ตามที่อธิบายไว้ใน Wikipedia:

ใช้ในการใช้งาน LISP บางอย่างแทรมโพลีนเป็นลูปที่เรียกใช้ฟังก์ชัน thunk-return ซ้ำ ๆ แทรมโพลีนตัวเดียวเพียงพอที่จะแสดงการควบคุมการถ่ายโอนโปรแกรมทั้งหมด โปรแกรมที่แสดงออกมาคือ trampolined หรือใน "trampolined style"; การแปลงโปรแกรมเป็นรูปแบบ trampolined คือ trampolining ฟังก์ชัน Trampolined สามารถใช้เพื่อใช้การเรียกฟังก์ชันหางซ้ำในภาษาแบบเรียงซ้อน

สมมติว่าเราใช้ Javascript และต้องการเขียนฟังก์ชัน Fibonacci ที่ไร้เดียงสาในรูปแบบการส่งต่อแบบต่อเนื่อง เหตุผลที่เราจะทำเช่นนี้ไม่เกี่ยวข้องเช่นพอร์ต 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 ทำให้เกิดข้อผิดพลาด 'Too much recursion!' ตอนนี้เป็นปัญหา (ไม่มีการเพิ่มประสิทธิภาพการโทรหางใน Javascript) ที่ trampolining แก้ได้ แทนที่จะทำการเรียก (เรียกซ้ำ) ไปยังฟังก์ชันให้เรา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]});
}

39

ให้ฉันเพิ่มตัวอย่างสำหรับฟังก์ชันแฟกทอเรียลที่ใช้กับแทรมโพลีนในภาษาต่างๆ:

สกาล่า:

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;
}

คำอธิบายของคุณโดยเฉพาะตัวอย่าง C เช่นเดียวกับคำตอบของ ephemient ด้านล่างเกี่ยวกับฟังก์ชันซ้อนกันทำให้ฉันเข้าใจ trampolines ในที่สุด ประเภทของฟังก์ชันตัวช่วยที่สามารถใช้เพื่ออัปเดตสถานะได้เหมือนกับการปิด
Byte

ควรแก้ไขรหัสสกาล่าเป็นif (n < 2) Done(product)ดังนั้นไม่อนุญาตให้ฉันแก้ไข 1 สัญลักษณ์ ...
สูงสุด

21

ฉันจะยกตัวอย่างที่ฉันใช้ในโปรแกรมแก้ไขการโกงสำหรับเกมออนไลน์

ฉันจำเป็นต้องสามารถสแกนไฟล์ทั้งหมดที่เกมโหลดเพื่อแก้ไขได้ ดังนั้นวิธีที่มีประสิทธิภาพที่สุดที่ฉันพบคือการใช้แทรมโพลีนสำหรับ CreateFileA ดังนั้นเมื่อเกมเปิดตัวฉันจะพบที่อยู่สำหรับ CreateFileA โดยใช้ GetProcAddress จากนั้นฉันจะแก้ไขฟังก์ชันสองสามไบต์แรกและใส่รหัสแอสเซมบลีที่จะข้ามไปยังฟังก์ชัน "แทรมโพลีน" ของฉันเองซึ่งฉันจะทำบางอย่างและ จากนั้นฉันจะข้ามกลับไปที่ตำแหน่งถัดไปใน CreateFile หลังจากรหัส jmp ของฉัน เพื่อให้สามารถทำได้อย่างน่าเชื่อถือนั้นค่อนข้างยุ่งยากกว่านั้นเล็กน้อย แต่แนวคิดพื้นฐานคือเพียงแค่เชื่อมต่อฟังก์ชันหนึ่งบังคับให้เปลี่ยนเส้นทางไปยังฟังก์ชันอื่นจากนั้นข้ามกลับไปที่ฟังก์ชันเดิม

แก้ไข: Microsoft มีกรอบสำหรับสิ่งประเภทนี้ที่คุณสามารถดูได้ เรียกว่าทางอ้อม


9

ฉันกำลังทดลองวิธีใช้การเพิ่มประสิทธิภาพการโทรหางสำหรับล่าม 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

นี่คือตัวอย่างของฟังก์ชันที่ซ้อนกัน:

#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

สำหรับ 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");

แก้ไข: แทรมโพลีนลึกลับอื่น ๆ จะถูกสร้างขึ้นโดยปริยายโดยคอมไพเลอร์ การใช้งานอย่างหนึ่งจะเป็นตารางกระโดด (แม้ว่าจะมีสิ่งที่ซับซ้อนกว่าอย่างชัดเจนยิ่งคุณเริ่มพยายามสร้างโค้ดที่ซับซ้อนมากขึ้น)


0

ตอนนี้ C # มีฟังก์ชั่นท้องถิ่นแล้วการเขียนโค้ดกะตะของเกมโบว์ลิ่งสามารถแก้ไขได้อย่างหรูหราด้วยแทรมโพลีน:

using System.Collections.Generic;
using System.Linq;

class Game
{
    internal static int RollMany(params int[] rs) 
    {
        return Trampoline(1, 0, rs.ToList());

        int Trampoline(int frame, int rsf, IEnumerable<int> rs) =>
              frame == 11             ? rsf
            : rs.Count() == 0         ? rsf
            : rs.First() == 10        ? Trampoline(frame + 1, rsf + rs.Take(3).Sum(), rs.Skip(1))
            : rs.Take(2).Sum() == 10  ? Trampoline(frame + 1, rsf + rs.Take(3).Sum(), rs.Skip(2))
            :                           Trampoline(frame + 1, rsf + rs.Take(2).Sum(), rs.Skip(2));
    }
}

วิธีGame.RollManyนี้เรียกว่าด้วยจำนวนม้วน: โดยทั่วไป 20 ม้วนหากไม่มีอะไหล่หรือนัดหยุดงาน

บรรทัดแรกเรียกใช้ฟังก์ชันแทรมโพลีนทันที: return Trampoline(1, 0, rs.ToList());. ฟังก์ชันโลคัลนี้จะเคลื่อนที่ผ่านอาร์เรย์ของโรลแบบวนซ้ำ ฟังก์ชันเฉพาะที่ (แทรมโพลีน) ช่วยให้การข้ามผ่านเริ่มต้นด้วยค่าเพิ่มเติมสองค่า: เริ่มต้นด้วยframe1 และrsf (ผลลัพธ์จนถึงตอนนี้) 0

ภายในฟังก์ชันโลคัลมีตัวดำเนินการ ternary ที่จัดการห้ากรณี:

  • เกมจบลงที่เฟรมที่ 11: ส่งคืนผลลัพธ์จนถึงตอนนี้
  • เกมจะจบลงหากไม่มีการหมุนอีกต่อไป: ส่งคืนผลลัพธ์จนถึงตอนนี้
  • Strike: คำนวณคะแนนเฟรมและข้ามผ่านต่อไป
  • สำรอง: คำนวณคะแนนเฟรมและข้ามผ่านต่อไป
  • คะแนนปกติ: คำนวณคะแนนเฟรมและข้ามผ่านต่อไป

การข้ามผ่านต่อไปทำได้โดยการเรียกแทรมโพลีนอีกครั้ง แต่ตอนนี้มีค่าที่อัปเดตแล้ว

สำหรับข้อมูลเพิ่มเติมค้นหา: " tail recursion acculator " โปรดทราบว่าคอมไพเลอร์ไม่ได้ปรับแต่งการเรียกซ้ำของหาง ดังนั้นวิธีการแก้ปัญหานี้อาจจะสง่างามก็น่าจะไม่ใช่การอดอาหาร


-2
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();
}
// ...

3
คุณสามารถเพิ่มความคิดเห็นหรือคำอธิบายว่าเหตุใดจึงเป็นแทรมโพลีน
ประอาทิตย์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.