diff --git a/enumerator.c b/enumerator.c
index 340aa75..680a96e 100644
--- a/enumerator.c
+++ b/enumerator.c
@@ -121,18 +121,29 @@ struct enumerator {
     VALUE stop_exc;
     VALUE size;
     VALUE (*size_fn)(ANYARGS);
+    VALUE procs;
 };
 
 static VALUE rb_cGenerator, rb_cYielder;
 
 struct generator {
     VALUE proc;
+    VALUE obj;
+    VALUE hybrid;
 };
 
 struct yielder {
     VALUE proc;
 };
 
+struct proc_entry {
+    VALUE proc;
+    VALUE memo;
+    NODE * (*proc_fn)(ANYARGS);
+    VALUE (*size_fn)(ANYARGS);
+};
+
+
 static VALUE generator_allocate(VALUE klass);
 static VALUE generator_init(VALUE obj, VALUE proc);
 
@@ -151,6 +162,7 @@ enumerator_mark(void *p)
     rb_gc_mark(ptr->feedvalue);
     rb_gc_mark(ptr->stop_exc);
     rb_gc_mark(ptr->size);
+    rb_gc_mark(ptr->procs);
 }
 
 #define enumerator_free RUBY_TYPED_DEFAULT_FREE
@@ -182,6 +194,40 @@ enumerator_ptr(VALUE obj)
     return ptr;
 }
 
+static void
+proc_entry_mark(void *p)
+{
+    struct proc_entry *ptr = p;
+    rb_gc_mark(ptr->proc);
+    rb_gc_mark(ptr->memo);
+}
+
+#define proc_entry_free RUBY_TYPED_DEFAULT_FREE
+
+static size_t
+proc_entry_memsize(const void *p)
+{
+    return p ? sizeof(struct proc_entry) : 0;
+}
+
+static const rb_data_type_t proc_entry_data_type = {
+    "proc_entry",
+    {
+	proc_entry_mark,
+	proc_entry_free,
+	proc_entry_memsize,
+    },
+};
+
+static struct proc_entry *
+proc_entry_ptr(VALUE proc_entry) {
+    struct proc_entry *ptr;
+
+    TypedData_Get_Struct(proc_entry, struct proc_entry, &proc_entry_data_type, ptr);
+
+    return ptr;
+}
+
 /*
  * call-seq:
  *   obj.to_enum(method = :each, *args)                 -> enum
@@ -873,13 +919,69 @@ enumerator_rewind(VALUE obj)
     return obj;
 }
 
+static struct generator * generator_ptr(VALUE obj);
+
+static VALUE
+append_method(VALUE obj, VALUE str, ID default_method)
+{
+    VALUE method;
+
+    method = rb_attr_get(obj, id_method);
+    if (NIL_P(method)) {
+        rb_str_buf_cat2(str, ":");
+        rb_str_buf_cat2(str, rb_id2name(default_method));
+    }
+    else if (method != Qfalse) {
+        Check_Type(method, T_SYMBOL);
+        rb_str_buf_cat2(str, ":");
+        rb_str_buf_cat2(str, rb_id2name(SYM2ID(method)));
+    }
+    return str;
+}
+
+static VALUE
+append_args(VALUE obj, VALUE str, VALUE default_args)
+{
+    VALUE eargs;
+    int tainted, untrusted;
+
+    eargs = rb_attr_get(obj, id_arguments);
+    if (NIL_P(eargs)) {
+        eargs = default_args;
+    }
+    if (eargs != Qfalse) {
+        long   argc = RARRAY_LEN(eargs);
+        VALUE *argv = RARRAY_PTR(eargs);
+
+        if (argc > 0) {
+            rb_str_buf_cat2(str, "(");
+
+            while (argc--) {
+                VALUE arg = *argv++;
+
+                rb_str_concat(str, rb_inspect(arg));
+                rb_str_buf_cat2(str, argc > 0 ? ", " : ")");
+
+                if (OBJ_TAINTED(arg)) tainted = TRUE;
+                if (OBJ_UNTRUSTED(arg)) untrusted = TRUE;
+            }
+        }
+    }
+
+    if (tainted) { OBJ_TAINT(str); }
+    if (untrusted) { OBJ_UNTRUST(str); }
+    return str;
+}
+
 static VALUE
 inspect_enumerator(VALUE obj, VALUE dummy, int recur)
 {
     struct enumerator *e;
+    struct generator *g;
     const char *cname;
-    VALUE eobj, eargs, str, method;
+    VALUE eobj, str;
     int tainted, untrusted;
+    int i;
 
     TypedData_Get_Struct(obj, struct enumerator, &enumerator_data_type, e);
 
@@ -895,53 +997,45 @@ inspect_enumerator(VALUE obj, VALUE dummy, int recur)
 	return str;
     }
 
-    eobj = rb_attr_get(obj, id_receiver);
-    if (NIL_P(eobj)) {
-	eobj = e->obj;
+    if (e->procs) {
+        g = generator_ptr(e->obj);
+        eobj = g->obj;
+    } else {
+        eobj = rb_attr_get(obj, id_receiver);
+        if (NIL_P(eobj)) {
+            eobj = e->obj;
+        }
     }
 
     tainted   = OBJ_TAINTED(eobj);
     untrusted = OBJ_UNTRUSTED(eobj);
 
-    /* (1..100).each_cons(2) => "#<Enumerator: 1..100:each_cons(2)>" */
-    str = rb_sprintf("#<%s: ", cname);
-    rb_str_concat(str, rb_inspect(eobj));
-    method = rb_attr_get(obj, id_method);
-    if (NIL_P(method)) {
-	rb_str_buf_cat2(str, ":");
-	rb_str_buf_cat2(str, rb_id2name(e->meth));
-    }
-    else if (method != Qfalse) {
-	Check_Type(method, T_SYMBOL);
-	rb_str_buf_cat2(str, ":");
-	rb_str_buf_cat2(str, rb_id2name(SYM2ID(method)));
-    }
-
-    eargs = rb_attr_get(obj, id_arguments);
-    if (NIL_P(eargs)) {
-	eargs = e->args;
-    }
-    if (eargs != Qfalse) {
-	long   argc = RARRAY_LEN(eargs);
-	VALUE *argv = RARRAY_PTR(eargs);
-
-	if (argc > 0) {
-	    rb_str_buf_cat2(str, "(");
-
-	    while (argc--) {
-		VALUE arg = *argv++;
-
-		rb_str_concat(str, rb_inspect(arg));
-		rb_str_buf_cat2(str, argc > 0 ? ", " : ")");
+    /* (1..100).each_cons(2) => "#<Enumerator: 1..100:each_cons(2)>"
+     * In case procs chained enumerator traversing all proc entries manually
+     */
+    if (e->procs) {
+        if (strcmp(rb_obj_classname(eobj), cname) == 0) {
+            str = rb_inspect(eobj);
+        } else {
+            str = rb_sprintf("#<%s: ", cname);
+            rb_str_concat(str, rb_inspect(eobj));
+            rb_str_buf_cat2(str, ">");
+        }
+        for (i = 0; i < RARRAY_LEN(e->procs); i++) {
+            str = rb_str_concat(rb_sprintf("#<%s: ", cname), str);
+            append_method(RARRAY_AREF(e->procs, i), str, e->meth);
+            append_args(RARRAY_AREF(e->procs, i), str, e->args);
+            rb_str_buf_cat2(str, ">");
+        }
+    } else {
+        str = rb_sprintf("#<%s: ", cname);
+        rb_str_concat(str, rb_inspect(eobj));
+        append_method(obj, str, e->meth);
+        append_args(obj, str, e->args);
 
-		if (OBJ_TAINTED(arg)) tainted = TRUE;
-		if (OBJ_UNTRUSTED(arg)) untrusted = TRUE;
-	    }
-	}
+        rb_str_buf_cat2(str, ">");
     }
 
-    rb_str_buf_cat2(str, ">");
-
     if (tainted) OBJ_TAINT(str);
     if (untrusted) OBJ_UNTRUST(str);
     return str;
@@ -971,21 +1065,41 @@ enumerator_inspect(VALUE obj)
  *   (1..100).drop_while.size # => nil
  */
 
+static struct generator * generator_ptr(VALUE obj);
+
 static VALUE
 enumerator_size(VALUE obj)
 {
     struct enumerator *e = enumerator_ptr(obj);
-
-    if (e->size_fn) {
-	return (*e->size_fn)(e->obj, e->args, obj);
-    }
-    if (rb_obj_is_proc(e->size)) {
-        if (e->args)
-	    return rb_proc_call(e->size, e->args);
-        else
-            return rb_proc_call_with_block(e->size, 0, 0, Qnil);
+    long i = 0;
+    struct generator *g;
+    struct proc_entry *entry;
+    VALUE receiver;
+
+    if (e->procs) {
+        g = generator_ptr(e->obj);
+        receiver = rb_check_funcall(g->obj, id_size, 0, 0);
+        for(i = 0; i < RARRAY_LEN(e->procs); i++) {
+            entry = proc_entry_ptr(RARRAY_AREF(e->procs, i));
+            if (entry->size_fn) {
+                receiver = (*entry->size_fn)(RARRAY_AREF(e->procs, i), receiver);
+            } else {
+                return Qnil;
+            }
+        }
+        return receiver;
+    } else {
+        if (e->size_fn) {
+            return (*e->size_fn)(e->obj, e->args, obj);
+        }
+        if (rb_obj_is_proc(e->size)) {
+            if (e->args)
+                return rb_proc_call(e->size, e->args);
+            else
+                return rb_proc_call_with_block(e->size, 0, 0, Qnil);
+        }
+        return e->size;
     }
-    return e->size;
 }
 
 /*
@@ -1101,6 +1215,8 @@ generator_mark(void *p)
 {
     struct generator *ptr = p;
     rb_gc_mark(ptr->proc);
+    rb_gc_mark(ptr->obj);
+    rb_gc_mark(ptr->hybrid);
 }
 
 #define generator_free RUBY_TYPED_DEFAULT_FREE
@@ -1225,10 +1341,12 @@ generator_each(int argc, VALUE *argv, VALUE obj)
 }
 
 /* Lazy Enumerator methods */
+
 static VALUE
 enum_size(VALUE self)
 {
-    VALUE r = rb_check_funcall(self, id_size, 0, 0);
+    VALUE r;
+    r = rb_check_funcall(self, id_size, 0, 0);
     return (r == Qundef) ? Qnil : r;
 }
 
@@ -1277,6 +1395,106 @@ lazy_init_block_i(VALUE val, VALUE m, int argc, VALUE *argv)
     return Qnil;
 }
 
+static VALUE
+lazy_init_yielder(VALUE val, VALUE m, int argc, VALUE *argv)
+{
+    VALUE yielder = RARRAY_AREF(m, 0);
+    VALUE procs_array = RARRAY_AREF(m, 1);
+    struct proc_entry *entry;
+    VALUE memos = rb_attr_get(yielder, id_memo);
+    long i = 0;
+    NODE *result;
+
+    result = NEW_MEMO(Qtrue, rb_enum_values_pack(argc, argv), Qfalse);
+
+    for (i = 0; i < RARRAY_LEN(procs_array); i++) {
+        entry = proc_entry_ptr(RARRAY_AREF(procs_array, i));
+        if (RTEST(result->u1.value)) {
+            (*entry->proc_fn)(RARRAY_AREF(procs_array, i), result, memos, i);
+        }
+    }
+
+    if (RTEST(result->u1.value)) {
+        rb_funcall2(yielder, id_yield, 1, &(result->u2.value));
+    }
+    if (RTEST(result->u3.value)) {
+        rb_iter_break();
+    }
+    return result->u2.value;
+    return Qnil;
+}
+
+static VALUE
+lazy_init_block(VALUE val, VALUE m, int argc, VALUE *argv)
+{
+    VALUE procs = RARRAY_AREF(m, 1);
+
+    rb_ivar_set(val, id_memo, rb_ary_new2(RARRAY_LEN(procs)));
+    rb_block_call(RARRAY_AREF(m, 0), id_each, 0, 0,
+            lazy_init_yielder, rb_ary_new3(2, val, procs));
+    return Qnil;
+}
+
+static VALUE
+lazy_generator_init(VALUE enumerator, VALUE procs)
+{
+    VALUE generator;
+    VALUE obj;
+    struct generator *gen_ptr;
+    struct generator *old_gen_ptr;
+    struct enumerator *e = enumerator_ptr(enumerator);
+
+    if (RARRAY_LEN(procs) > 0) {
+        old_gen_ptr = generator_ptr(e->obj);
+        if (old_gen_ptr->hybrid) {
+            obj = enumerator;
+        } else {
+            obj = old_gen_ptr->obj;
+        }
+    } else {
+        obj = enumerator;
+    }
+
+    generator = generator_allocate(rb_cGenerator);
+
+    rb_block_call(generator, id_initialize, 0, 0,
+            lazy_init_block, rb_ary_new3(2, obj, procs));
+
+    gen_ptr = generator_ptr(generator);
+    gen_ptr->obj = obj;
+
+    return generator;
+}
+
+static VALUE
+create_proc_entry(VALUE memo, NODE * (*proc_fn)(ANYARGS))
+{
+    struct proc_entry *entry;
+    VALUE entry_obj;
+
+    entry_obj = TypedData_Make_Struct(rb_cObject, struct proc_entry,
+            &proc_entry_data_type, entry);
+    if (rb_block_given_p()) {
+        entry->proc = rb_block_proc();
+    }
+    entry->proc_fn = proc_fn;
+    entry->memo = memo;
+
+    return entry_obj;
+}
+
+static VALUE
+lazy_add_proc(VALUE enum_obj, VALUE memo, NODE * (*proc_fn)(ANYARGS))
+{
+    struct enumerator *ptr;
+    VALUE entry;
+
+    entry = create_proc_entry(memo, proc_fn);
+    ptr = enumerator_ptr(enum_obj);
+    rb_ary_push(ptr->procs, entry);
+
+    return entry;
+}
 /*
  * call-seq:
  *   Lazy.new(obj, size=nil) { |yielder, *values| ... }
@@ -1328,6 +1546,22 @@ lazy_initialize(int argc, VALUE *argv, VALUE self)
 }
 
 static VALUE
+lazy_proc_entry_set_method(VALUE proc_entry, VALUE args, VALUE (*size_fn)(ANYARGS)) {
+    ID id = rb_frame_this_func();
+    struct proc_entry *e = proc_entry_ptr(proc_entry);
+    rb_ivar_set(proc_entry, id_method, ID2SYM(id));
+    if (NIL_P(args)) {
+	/* Qfalse indicates that the arguments are empty */
+	rb_ivar_set(proc_entry, id_arguments, Qfalse);
+    }
+    else {
+	rb_ivar_set(proc_entry, id_arguments, args);
+    }
+    e->size_fn = size_fn;
+    return proc_entry;
+}
+
+static VALUE
 lazy_set_method(VALUE lazy, VALUE args, VALUE (*size_fn)(ANYARGS))
 {
     ID id = rb_frame_this_func();
@@ -1344,6 +1578,42 @@ lazy_set_method(VALUE lazy, VALUE args, VALUE (*size_fn)(ANYARGS))
     return lazy;
 }
 
+static VALUE
+lazy_copy(int argc, VALUE *argv, VALUE obj)
+{
+    struct enumerator *new_e;
+    struct generator *g;
+    VALUE new_obj;
+    VALUE new_generator;
+    VALUE new_procs;
+    struct enumerator *e = enumerator_ptr(obj);
+
+    if (RTEST(e->procs)) {
+        new_procs = rb_ary_new4(RARRAY_LEN(e->procs), RARRAY_PTR(e->procs));
+    } else {
+        new_procs = rb_ary_new();
+    }
+
+    new_generator = lazy_generator_init(obj, new_procs);
+    g = generator_ptr(new_generator);
+    g->hybrid = Qfalse;
+
+    new_obj = enumerator_init_copy(enumerator_allocate(rb_cLazy), obj);
+    new_e = enumerator_ptr(new_obj);
+    new_e->obj = new_generator;
+    new_e->procs = new_procs;
+    new_e->meth = rb_to_id(sym_each);
+
+    if (argc > 0) {
+        new_e->meth = rb_to_id(*argv++);
+        new_e->args = rb_ary_new4(argc - 1, argv);
+    } else {
+        new_e->meth = id_each;
+        new_e->args = rb_ary_new4(argc, argv);
+    }
+    return new_obj;
+}
+
 /*
  * call-seq:
  *   e.lazy -> lazy_enumerator
@@ -1431,24 +1701,34 @@ lazy_to_enum(int argc, VALUE *argv, VALUE self)
 }
 
 static VALUE
-lazy_map_func(VALUE val, VALUE m, int argc, VALUE *argv)
+lazy_map_size(VALUE entry, VALUE receiver)
 {
-    VALUE result = rb_yield_values2(argc - 1, &argv[1]);
+    return receiver;
+}
 
-    rb_funcall(argv[0], id_yield, 1, result);
-    return Qnil;
+static NODE *
+lazy_map_func(VALUE proc_entry, NODE *result, VALUE memos, int memo_index)
+{
+    struct proc_entry *entry = proc_entry_ptr(proc_entry);
+    result->u2.value = rb_proc_call_with_block(entry->proc, 1, &(result->u2.value), Qnil);
+    return result;
 }
 
 static VALUE
 lazy_map(VALUE obj)
 {
+    VALUE new_enum;
+    VALUE entry;
+
     if (!rb_block_given_p()) {
 	rb_raise(rb_eArgError, "tried to call lazy map without a block");
     }
 
-    return lazy_set_method(rb_block_call(rb_cLazy, id_new, 1, &obj,
-					 lazy_map_func, 0),
-			   Qnil, lazy_receiver_size);
+    new_enum = lazy_copy(0, 0, obj);
+    entry = lazy_add_proc(new_enum, Qnil, lazy_map_func);
+    lazy_proc_entry_set_method(entry, Qnil, lazy_map_size);
+
+    return new_enum;
 }
 
 static VALUE
@@ -1536,84 +1816,82 @@ lazy_flat_map(VALUE obj)
 			   Qnil, 0);
 }
 
-static VALUE
-lazy_select_func(VALUE val, VALUE m, int argc, VALUE *argv)
+static NODE *
+lazy_select_func(VALUE proc_entry, NODE *result, VALUE memos, int memo_index)
 {
-    VALUE element = rb_enum_values_pack(argc - 1, argv + 1);
-
-    if (RTEST(rb_yield(element))) {
-	return rb_funcall(argv[0], id_yield, 1, element);
-    }
-    return Qnil;
+    struct proc_entry *entry = proc_entry_ptr(proc_entry);
+    result->u1.value = rb_proc_call_with_block(entry->proc, 1, &(result->u2.value), Qnil);
+    return result;
 }
 
 static VALUE
 lazy_select(VALUE obj)
 {
+    VALUE new_enum;
+    VALUE entry;
+
     if (!rb_block_given_p()) {
 	rb_raise(rb_eArgError, "tried to call lazy select without a block");
     }
 
-    return lazy_set_method(rb_block_call(rb_cLazy, id_new, 1, &obj,
-					 lazy_select_func, 0),
-			   Qnil, 0);
+    new_enum = lazy_copy(0, 0, obj);
+    entry = lazy_add_proc(new_enum, Qnil, lazy_select_func);
+    lazy_proc_entry_set_method(entry, Qnil, 0);
+    return new_enum;
 }
 
-static VALUE
-lazy_reject_func(VALUE val, VALUE m, int argc, VALUE *argv)
-{
-    VALUE element = rb_enum_values_pack(argc - 1, argv + 1);
-
-    if (!RTEST(rb_yield(element))) {
-	return rb_funcall(argv[0], id_yield, 1, element);
-    }
-    return Qnil;
+static NODE *
+lazy_reject_func(VALUE proc_entry, NODE *result, VALUE memos, int memo_index) {
+    struct proc_entry *entry = proc_entry_ptr(proc_entry);
+    result->u1.value = !RTEST(rb_proc_call_with_block(entry->proc, 1, &(result->u2.value), Qnil));
+    return result;
 }
 
 static VALUE
 lazy_reject(VALUE obj)
 {
+    VALUE new_enum, entry;
+
     if (!rb_block_given_p()) {
 	rb_raise(rb_eArgError, "tried to call lazy reject without a block");
     }
 
-    return lazy_set_method(rb_block_call(rb_cLazy, id_new, 1, &obj,
-					 lazy_reject_func, 0),
-			   Qnil, 0);
+    new_enum = lazy_copy(0, 0, obj);
+    entry = lazy_add_proc(new_enum, Qnil, lazy_reject_func);
+    lazy_proc_entry_set_method(entry, Qnil, 0);
+    return new_enum;
 }
 
-static VALUE
-lazy_grep_func(VALUE val, VALUE m, int argc, VALUE *argv)
-{
-    VALUE i = rb_enum_values_pack(argc - 1, argv + 1);
-    VALUE result = rb_funcall(m, id_eqq, 1, i);
-
-    if (RTEST(result)) {
-	rb_funcall(argv[0], id_yield, 1, i);
-    }
-    return Qnil;
+static NODE *
+lazy_grep_func(VALUE proc_entry, NODE *result, VALUE memos, int memo_index) {
+    struct proc_entry *entry = proc_entry_ptr(proc_entry);
+    result->u1.value = rb_funcall(entry->memo, id_eqq, 1, result->u2.value);
+    return result;
 }
 
-static VALUE
-lazy_grep_iter(VALUE val, VALUE m, int argc, VALUE *argv)
-{
-    VALUE i = rb_enum_values_pack(argc - 1, argv + 1);
-    VALUE result = rb_funcall(m, id_eqq, 1, i);
+static NODE *
+lazy_grep_iter(VALUE proc_entry, NODE *result, VALUE memos, int memo_index) {
+    struct proc_entry *entry = proc_entry_ptr(proc_entry);
+    result->u1.value = rb_funcall(entry->memo, id_eqq, 1, result->u2.value);
 
-    if (RTEST(result)) {
-	rb_funcall(argv[0], id_yield, 1, rb_yield(i));
+    if (RTEST(result->u1.value)) {
+        result->u2.value = rb_proc_call_with_block(entry->proc, 1, &(result->u2.value), Qnil);
     }
-    return Qnil;
+
+    return result;
 }
 
 static VALUE
 lazy_grep(VALUE obj, VALUE pattern)
 {
-    return lazy_set_method(rb_block_call(rb_cLazy, id_new, 1, &obj,
-					 rb_block_given_p() ?
-					 lazy_grep_iter : lazy_grep_func,
-					 pattern),
-			   rb_ary_new3(1, pattern), 0);
+    VALUE new_enum, entry;
+
+    new_enum = lazy_copy(0, 0, obj);
+    entry = lazy_add_proc(new_enum, pattern, rb_block_given_p() ?
+					 lazy_grep_iter : lazy_grep_func);
+    lazy_proc_entry_set_method(entry, rb_ary_new3(1, pattern), 0);
+
+    return new_enum;
 }
 
 static VALUE
@@ -1709,79 +1987,94 @@ lazy_zip(int argc, VALUE *argv, VALUE obj)
 }
 
 static VALUE
-lazy_take_func(VALUE val, VALUE args, int argc, VALUE *argv)
+lazy_take_size(VALUE entry, VALUE receiver)
+{
+    long len = NUM2LONG(RARRAY_AREF(rb_ivar_get(entry, id_arguments), 0));
+    if (NIL_P(receiver) || (FIXNUM_P(receiver) && FIX2LONG(receiver) < len))
+	return receiver;
+    return LONG2NUM(len);
+}
+
+static NODE *
+lazy_take_func(VALUE proc_entry, NODE *result, VALUE memos, int memo_index)
 {
     long remain;
-    VALUE memo = rb_attr_get(argv[0], id_memo);
+    struct proc_entry *entry = proc_entry_ptr(proc_entry);
+    VALUE memo = rb_ary_entry(memos, memo_index);
+
     if (NIL_P(memo)) {
-	memo = args;
+	memo = entry->memo;
     }
 
-    rb_funcall2(argv[0], id_yield, argc - 1, argv + 1);
-    if ((remain = NUM2LONG(memo)-1) == 0) {
-	return Qundef;
-    }
-    else {
-	rb_ivar_set(argv[0], id_memo, LONG2NUM(remain));
-	return Qnil;
+    remain = NUM2LONG(memo);
+    if (remain == 0) {
+        result->u1.value = Qfalse;
+        result->u3.value = Qtrue;
+    } else if(--remain == 0) {
+        result->u3.value = Qtrue;
     }
-}
-
-static VALUE
-lazy_take_size(VALUE generator, VALUE args, VALUE lazy)
-{
-    VALUE receiver = lazy_size(lazy);
-    long len = NUM2LONG(RARRAY_AREF(rb_ivar_get(lazy, id_arguments), 0));
-    if (NIL_P(receiver) || (FIXNUM_P(receiver) && FIX2LONG(receiver) < len))
-	return receiver;
-    return LONG2NUM(len);
+    rb_ary_store(memos, memo_index, LONG2NUM(remain));
+    return result;
 }
 
 static VALUE
 lazy_take(VALUE obj, VALUE n)
 {
     long len = NUM2LONG(n);
-    VALUE lazy;
+    VALUE new_enum;
+    int argc = 0;
+    VALUE argv[2];
+    VALUE entry;
 
     if (len < 0) {
 	rb_raise(rb_eArgError, "attempt to take negative size");
     }
+
     if (len == 0) {
-	VALUE len = INT2FIX(0);
-	lazy = lazy_to_enum_i(obj, sym_cycle, 1, &len, 0);
-    }
-    else {
-	lazy = rb_block_call(rb_cLazy, id_new, 1, &obj,
-					 lazy_take_func, n);
+       argv[0] = sym_cycle;
+       argv[1] = INT2NUM(0);
+       argc = 2;
     }
-    return lazy_set_method(lazy, rb_ary_new3(1, n), lazy_take_size);
+
+    new_enum = lazy_copy(argc, argv, obj);
+    entry = lazy_add_proc(new_enum, n, lazy_take_func);
+    lazy_proc_entry_set_method(entry, rb_ary_new3(1, n), lazy_take_size);
+
+    return new_enum;
 }
 
-static VALUE
-lazy_take_while_func(VALUE val, VALUE args, int argc, VALUE *argv)
-{
-    VALUE result = rb_yield_values2(argc - 1, &argv[1]);
-    if (!RTEST(result)) return Qundef;
-    rb_funcall2(argv[0], id_yield, argc - 1, argv + 1);
-    return Qnil;
+static NODE *
+lazy_take_while_func(VALUE proc_entry, NODE *result, VALUE memos, int memo_index) {
+    struct proc_entry *entry = proc_entry_ptr(proc_entry);
+    result->u1.value = rb_proc_call_with_block(entry->proc, 1, &(result->u2.value), Qnil);
+    if (!RTEST(result->u1.value)) {
+        result->u1.value = Qfalse;
+        result->u2.value = Qundef;
+        result->u3.value = Qtrue;
+    }
+    return result;
 }
 
 static VALUE
 lazy_take_while(VALUE obj)
 {
+    VALUE new_enum, entry;
+
     if (!rb_block_given_p()) {
 	rb_raise(rb_eArgError, "tried to call lazy take_while without a block");
     }
-    return lazy_set_method(rb_block_call(rb_cLazy, id_new, 1, &obj,
-					 lazy_take_while_func, 0),
-			   Qnil, 0);
+
+    new_enum = lazy_copy(0, 0, obj);
+    entry = lazy_add_proc(new_enum, Qnil, lazy_take_while_func);
+    lazy_proc_entry_set_method(entry, Qnil, 0);
+
+    return new_enum;
 }
 
 static VALUE
-lazy_drop_size(VALUE generator, VALUE args, VALUE lazy)
+lazy_drop_size(VALUE proc_entry, VALUE receiver)
 {
-    long len = NUM2LONG(RARRAY_AREF(rb_ivar_get(lazy, id_arguments), 0));
-    VALUE receiver = lazy_size(lazy);
+    long len = NUM2LONG(RARRAY_AREF(rb_ivar_get(proc_entry, id_arguments), 0));
     if (NIL_P(receiver))
 	return receiver;
     if (FIXNUM_P(receiver)) {
@@ -1791,58 +2084,77 @@ lazy_drop_size(VALUE generator, VALUE args, VALUE lazy)
     return rb_funcall(receiver, '-', 1, LONG2NUM(len));
 }
 
-static VALUE
-lazy_drop_func(VALUE val, VALUE args, int argc, VALUE *argv)
+static NODE *
+lazy_drop_func(VALUE proc_entry, NODE *result, VALUE memos, int memo_index)
 {
     long remain;
-    VALUE memo = rb_attr_get(argv[0], id_memo);
+    struct proc_entry *entry = proc_entry_ptr(proc_entry);
+    VALUE memo = rb_ary_entry(memos, memo_index);
+
     if (NIL_P(memo)) {
-	memo = args;
-    }
-    if ((remain = NUM2LONG(memo)) == 0) {
-	rb_funcall2(argv[0], id_yield, argc - 1, argv + 1);
+	memo = entry->memo;
     }
-    else {
-	rb_ivar_set(argv[0], id_memo, LONG2NUM(--remain));
+    remain = NUM2LONG(memo);
+    if (remain-- > 0) {
+        result->u1.value = Qfalse;
+	rb_ary_store(memos, memo_index, LONG2NUM(remain));
     }
-    return Qnil;
+
+    return result;
 }
 
 static VALUE
 lazy_drop(VALUE obj, VALUE n)
 {
     long len = NUM2LONG(n);
+    VALUE new_enum, entry;
+    VALUE argv[2];
+    argv[0] = sym_each;
+    argv[1] = n;
 
     if (len < 0) {
 	rb_raise(rb_eArgError, "attempt to drop negative size");
     }
-    return lazy_set_method(rb_block_call(rb_cLazy, id_new, 1, &obj,
-					 lazy_drop_func, n),
-			   rb_ary_new3(1, n), lazy_drop_size);
+
+    new_enum = lazy_copy(2, argv, obj);
+    entry = lazy_add_proc(new_enum, n, lazy_drop_func);
+    lazy_proc_entry_set_method(entry, rb_ary_new3(1, n), lazy_drop_size);
+
+    return new_enum;
 }
 
-static VALUE
-lazy_drop_while_func(VALUE val, VALUE args, int argc, VALUE *argv)
-{
-    VALUE memo = rb_attr_get(argv[0], id_memo);
-    if (NIL_P(memo) && !RTEST(rb_yield_values2(argc - 1, &argv[1]))) {
-	rb_ivar_set(argv[0], id_memo, memo = Qtrue);
+static NODE *
+lazy_drop_while_func(VALUE proc_entry, NODE* result, VALUE memos, int memo_index) {
+    struct proc_entry *entry = proc_entry_ptr(proc_entry);
+    VALUE memo = rb_ary_entry(memos, memo_index);
+
+    if(NIL_P(memo)) {
+        memo = entry->memo;
     }
-    if (memo == Qtrue) {
-	rb_funcall2(argv[0], id_yield, argc - 1, argv + 1);
+
+    if (!RTEST(memo)) {
+        result->u1.value = !RTEST(rb_proc_call_with_block(entry->proc, 1, &(result->u2.value), Qnil));
+        if (result->u1.value) {
+            rb_ary_store(memos, memo_index, Qtrue);
+        }
     }
-    return Qnil;
+    return result;
 }
 
 static VALUE
 lazy_drop_while(VALUE obj)
 {
+    VALUE new_enum, entry;
+
     if (!rb_block_given_p()) {
 	rb_raise(rb_eArgError, "tried to call lazy drop_while without a block");
     }
-    return lazy_set_method(rb_block_call(rb_cLazy, id_new, 1, &obj,
-					 lazy_drop_while_func, 0),
-			   Qnil, 0);
+
+    new_enum = lazy_copy(0, 0, obj);
+    entry = lazy_add_proc(new_enum, Qfalse, lazy_drop_while_func);
+    lazy_proc_entry_set_method(entry, Qnil, 0);
+
+    return new_enum;
 }
 
 static VALUE
