Trambolin işlevi nedir?


94

İş yerindeki son tartışmalarda, birisi bir trambolin işlevinden bahsetti.

Wikipedia'daki açıklamayı okudum . İşlevsellik hakkında genel bir fikir vermek yeterlidir, ancak biraz daha somut bir şey istiyorum.

Bir trambolini gösterecek basit bir kod parçanız var mı?


2
Microsoft dünyasında, trambolinler genellikle 'thunks' olarak adlandırılır. Andrei Alexandrescu'nun "Modern C ++ Tasarımı" ndan [1] [1] [1]: books.google.com/…
Michael Burr


Bu temelde setjmp / lomgjmp ile uygulayabileceğiniz bazı işlevlerin genelleştirilmiş biçimidir, yani yığın akışını önlemek içindir.
Ingo

12
neden biri yığın aşımından kaçınmak istesin?
Nikole

Yanıtlar:


71

Wikipedia'da açıklandığı gibi LISP 'trambolin' duygusu da vardır:

Bazı LISP uygulamalarında kullanılan bir trambolin, thunk-dönen işlevleri yinelemeli olarak çağıran bir döngüdür. Bir programın tüm kontrol transferlerini ifade etmek için tek bir trambolin yeterlidir; bu şekilde ifade edilen bir program tramplenle veya "tramplenle taranmış tarzda"; Bir programı tramplenli stile dönüştürmek tramplen yapmaktır. Trampollenmiş işlevler, yığın yönelimli dillerde kuyruk özyinelemeli işlev çağrıları uygulamak için kullanılabilir

Diyelim ki Javascript kullanıyoruz ve saf Fibonacci fonksiyonunu devam eden tarzda yazmak istiyoruz. Bunu yapmamızın nedeni, örneğin, Scheme'i JS'ye taşımak veya sunucu tarafı işlevlerini çağırmak için yine de kullanmamız gereken CPS ile oynamak.

Yani, ilk girişim

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

Ancak bunu n = 25Firefox'ta çalıştırmak 'Çok fazla özyineleme!' Hatası veriyor. Şimdi tam da bu, tramplenin çözdüğü problemdir (Javascript'te eksik kuyruk arama optimizasyonu). Bir işleve (özyinelemeli) bir çağrı yapmak yerine, returnbir döngüde yorumlanacak o işlevi çağırmak için bir talimat (thunk) edelim .

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

Farklı dillerde trambolinler ile uygulanan faktörsel fonksiyon için birkaç örnek ekleyeyim:

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 (büyük sayı uygulaması olmadan şanssız):

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

Açıklamanız, özellikle C örneği ve efemient'in aşağıdaki yuvalanmış işlevler hakkındaki cevabı nihayet trambolinleri anlamamı sağladı. Durumu bir kapanış gibi güncellemek için kullanılabilen bir tür yardımcı işlev.
Bayt

Scala kodu şu şekilde düzeltilmelidir if (n < 2) Done(product), SO 1 sembolü düzenlememe izin vermedi ...
Fazla

21

Çevrimiçi bir oyun için hile karşıtı yamada kullandığım bir örnek vereceğim.

Oyun tarafından yüklenen tüm dosyaları değişiklik için tarayabilmem gerekiyordu. Bunu yapmanın en sağlam yolu, CreateFileA için bir trambolin kullanmaktı. Bu yüzden, oyun başlatıldığında GetProcAddress kullanarak CreateFileA için adresi bulacaktım, daha sonra işlevin ilk birkaç baytını değiştiriyor ve bazı şeyler yapacağım kendi "trambolin" işlevime atlayacak derleme kodunu ekliyordum ve daha sonra jmp kodumdan sonra CreateFile'da bir sonraki konuma geri atlardım. Bunu güvenilir bir şekilde yapabilmek bundan biraz daha zordur, ancak temel kavram sadece bir işlevi bağlamak, onu başka bir işleve yönlendirmeye zorlamak ve ardından orijinal işleve geri dönmektir.

Düzenleme: Microsoft, bakabileceğiniz bu tür şeyler için bir çerçeveye sahiptir. Aranan güzergah dışı


9

Şu anda bir Şema yorumlayıcısı için kuyruk arama optimizasyonunu uygulama yollarını deniyorum ve bu nedenle şu anda trambolinin benim için uygun olup olmayacağını anlamaya çalışıyorum.

Anladığım kadarıyla, temelde bir trambolin işlevi tarafından gerçekleştirilen bir dizi işlev çağrısıdır. Her işleve thunk denir ve program sona erene kadar (boş devam) hesaplamadaki bir sonraki adımı döndürür.

İşte trambolin anlayışımı geliştirmek için yazdığım ilk kod parçası:

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

sonuçlanır:

meincompi $ ./trampoline 
*boing* thunk 1
*boing* thunk 2
*boing* last thunk
got off the trampoline - happy happy joy joy !

7

İç içe geçmiş işlevlere bir örnek:

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

comparharici bir işlev olamaz, çünkü nbytesyalnızca sort_bytesarama sırasında var olan kullanır . Bazı mimarilerde, küçük bir saplama işlevi - trambolin - çalışma zamanında üretilir ve güncel çağrının yığın konumunu içerir sort_bytes. Çağrıldığında, comparkoda atlar ve bu adresi iletir.

Bu karışıklık, ABI'nin bir işlev göstericisinin aslında bir "şişman işaretçi" olduğunu, hem çalıştırılabilir koda bir işaretçi hem de verilere başka bir işaretçi içeren bir yapı olduğunu belirttiği PowerPC gibi mimarilerde gerekli değildir. Ancak, x86'da bir işlev işaretçisi yalnızca bir işaretleyicidir.


0

C için, bir trambolin bir işlev göstericisi olacaktır:

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");

Düzenleme: Derleyici tarafından örtük olarak daha fazla ezoterik trambolin üretilecektir. Böyle bir kullanım bir atlama tablosu olabilir. (Açıkça daha karmaşık olanlar olsa da, daha aşağıda karmaşık kodlar üretmeye başlarsınız.)


0

Artık C # Yerel İşlevlere sahip olduğuna göre , Bowling Oyunu kodlama katası bir trambolin ile zarif bir şekilde çözülebilir:

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

Yöntem Game.RollMany, birkaç rulo ile çağrılır: yedek parça veya vuruş yoksa tipik olarak 20 rulo.

İlk satır hemen trambolin işlevini çağırır: return Trampoline(1, 0, rs.ToList());. Bu yerel fonksiyon, roll dizisini özyinelemeli olarak dolaşır. Yerel fonksiyon (trambolin), çapraz geçişin iki ek değerle başlamasına izin verir: frame1 ile başla ve rsf(şimdiye kadarki sonuç) 0.

Yerel işlev içinde beş durumu ele alan üçlü operatör vardır:

  • Oyun 11. karede bitiyor: şimdiye kadar sonucu geri verin
  • Daha fazla zar yoksa oyun biter: şimdiye kadar sonucu iade edin
  • Vuruş: çerçeve puanını hesaplayın ve geçişe devam edin
  • Yedek: çerçeve puanını hesaplayın ve geçişe devam edin
  • Normal puan: çerçeve puanını hesaplayın ve geçişe devam edin

Gezinmeye devam etmek trambolini tekrar çağırarak yapılır, ancak şimdi güncellenmiş değerlerle.

Daha fazla bilgi için şunu arayın: " kuyruk özyineleme toplayıcı ". Derleyicinin kuyruk özyinelemesini optimize etmediğini unutmayın. Bu çözüm ne kadar zarif olursa olsun, muhtemelen oruç tutulmayacaktır.


-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
bunun neden bir trambolin olduğuna dair herhangi bir yorum veya açıklama ekleyebilir misiniz?
prasun
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.