javascript: ฟังก์ชันนิรนามเรียกซ้ำ?


121

สมมติว่าฉันมีฟังก์ชันเรียกซ้ำพื้นฐาน:

function recur(data) {
    data = data+1;
    var nothing = function() {
        recur(data);
    }
    nothing();
}

ฉันจะทำสิ่งนี้ได้อย่างไรหากฉันมีฟังก์ชันที่ไม่ระบุตัวตนเช่น ...

(function(data){
    data = data+1;
    var nothing = function() {
        //Something here that calls the function?
    }
    nothing();
})();

ฉันต้องการวิธีเรียกฟังก์ชันที่เรียกฟังก์ชันนี้ ... ฉันเคยเห็นสคริปต์ที่ไหนสักแห่ง (จำไม่ได้ว่าที่ไหน) ที่สามารถบอกชื่อฟังก์ชันที่เรียกคุณได้ แต่ฉันจำไม่ได้ ข้อมูลนั้นในขณะนี้


มีเหตุผลที่คุณต้องการสิ่งนี้หรือคุณแค่อยากรู้? สำหรับฉันแล้วมันจะชัดเจนกว่าที่จะตั้งชื่อ ...
rfunduk

1
@thenduks: ด้วยเหตุผลเดียวกันว่าทำไมจึงใช้ฟังก์ชันนิรนาม เพียงแค่ว่าบางครั้งการเรียกซ้ำเป็นสิ่งที่จำเป็น
โผล่

5
น่าเสียดายที่arguments.calleeมีอยู่และ functnio นี้ไม่ได้ทำประโยชน์อะไรเลย ฉันถูกมองขึ้นY :PCombinator ประณามสิ่งนั้นจะไม่มีประโยชน์ ...
Kobi

1
ใช่เมื่อเชื่อมโยง Kobi ให้ใช้ตัวรวมจุดคงที่เช่น Y เพื่อทำฟังก์ชันเรียกซ้ำแบบไม่ระบุชื่อโดยไม่มีอาร์กิวเมนต์
เรือกลไฟ 25

1
ดูw3future.com/weblog/stories/2002/02/22/…สำหรับตัวอย่าง Y combinator ใน JS
เรือกลไฟ 25

คำตอบ:


145

คุณสามารถตั้งชื่อฟังก์ชันได้แม้ว่าคุณจะสร้างฟังก์ชันเป็นค่าและไม่ใช่คำสั่ง "การประกาศฟังก์ชัน" กล่าวอีกนัยหนึ่ง:

(function foo() { foo(); })();

เป็นฟังก์ชันวนซ้ำแบบสแต็ก ตอนนี้ที่กล่าวว่าคุณอาจไม่ได้อาจไม่ต้องการที่จะทำเช่นนี้โดยทั่วไปเพราะมีปัญหาบางอย่างที่แปลกกับการใช้งานจาวาสคริต่างๆ ( หมายเหตุ - นั่นเป็นความคิดเห็นที่ค่อนข้างเก่าปัญหาบางส่วน / หลาย / ทั้งหมดที่อธิบายไว้ในบล็อกโพสต์ของ Kangax อาจได้รับการแก้ไขในเบราว์เซอร์ที่ทันสมัยกว่า)

เมื่อคุณตั้งชื่อแบบนั้นชื่อจะมองไม่เห็นภายนอกฟังก์ชัน (ก็ไม่ควรจะเป็นนั่นคือความแปลกอย่างหนึ่ง) มันเหมือนกับ "letrec" ใน Lisp

สำหรับarguments.calleeสิ่งนี้ไม่ได้รับอนุญาตในโหมด "เข้มงวด" และโดยทั่วไปถือว่าเป็นสิ่งที่ไม่ดีเนื่องจากทำให้การเพิ่มประสิทธิภาพบางอย่างทำได้ยาก นอกจากนี้ยังช้ากว่าที่คาดไว้มาก

แก้ไข - หากคุณต้องการให้มีเอฟเฟกต์ของฟังก์ชัน "ไม่ระบุตัวตน" ที่สามารถเรียกตัวเองได้คุณสามารถทำสิ่งนี้ได้ (สมมติว่าคุณกำลังส่งผ่านฟังก์ชันนี้เป็นการโทรกลับหรืออะไรทำนองนั้น):

asyncThingWithCallback(params, (function() {
  function recursive() {
    if (timeToStop())
      return whatever();
    recursive(moreWork);
  }
  return recursive;
})());

สิ่งที่ทำคือกำหนดฟังก์ชันด้วยคำสั่งประกาศฟังก์ชันที่ดีปลอดภัยไม่เสียใน IE สร้างฟังก์ชันท้องถิ่นที่มีชื่อจะไม่ก่อให้เกิดเนมสเปซทั่วโลก ฟังก์ชัน wrapper (ไม่ระบุชื่ออย่างแท้จริง) จะส่งคืนฟังก์ชันโลคัลนั้น


เราสามารถหลีกเลี่ยงการสร้างมลพิษให้กับเนมสเปซทั่วโลกด้วยวิธีอื่นด้วย ES5 sctrict ได้หรือไม่ (ฉันยังไม่ได้อ่านลึกถึง ES5)
ไม่ระบุตัวตน

@pointy คุณช่วยดูเควสนี้ได้ไหม stackoverflow.com/questions/27473450/…
Gladson Robinson

ฉันเดาว่ามันไม่สามารถใช้(() => { call_recursively_self_here() })()และเรียกตัวเองซ้ำได้ใช่ไหม? ฉันต้องตั้งชื่อให้
Qwerty

1
@Qwerty คุณสามารถทำอะไรบางอย่างเช่นตัวอย่างสุดท้ายในคำตอบของฉัน ผูกฟังก์ชันลูกศรกับตัวแปรโลคัลในฟังก์ชัน wrapper เพื่อให้ฟังก์ชันลูกศรของคุณสามารถอ้างถึงตัวมันเองด้วยชื่อตัวแปร จากนั้น Wrapper จะส่งกลับตัวแปร (ซึ่งหมายถึงฟังก์ชันลูกศร)
Pointy

1
@Pointy แฮกเกอร์บางคนอาจจะพบแอปพลิเคชัน;)
Kamil Kiełczewski

31

ผู้คนพูดถึง Y combinator ในความคิดเห็น แต่ไม่มีใครเขียนเป็นคำตอบ

Y combinator สามารถกำหนดได้ใน javascript ดังนี้: (ขอบคุณ steamer25 สำหรับลิงค์)

var Y = function (gen) {
  return (function(f) {
    return f(f);
  }(function(f) {
    return gen(function() {
      return f(f).apply(null, arguments);
    });
  }));
}

และเมื่อคุณต้องการส่งผ่านฟังก์ชันนิรนามของคุณ:

(Y(function(recur) {
  return function(data) {
    data = data+1;
    var nothing = function() {
      recur(data);
    }
    nothing();
  }
})());

สิ่งที่สำคัญที่สุดที่ควรทราบเกี่ยวกับโซลูชันนี้คือคุณไม่ควรใช้


16
"สิ่งที่สำคัญที่สุดที่ควรทราบเกี่ยวกับโซลูชันนี้คือคุณไม่ควรใช้มัน" ทำไม?
nyuszika7h

7
มันจะไม่เร็ว มันน่าเกลียดที่จะใช้งานจริง (แม้ว่าจะสวยงามตามแนวคิดก็ตาม!) คุณหลีกเลี่ยงการตั้งชื่อแท็กหรือชื่อตัวแปรให้กับฟังก์ชันของคุณ (และฉันไม่เห็นว่าเหตุใดจึงน่าเป็นห่วง) แต่คุณยังคงตั้งชื่อให้เป็นพารามิเตอร์ของฟังก์ชันภายนอกที่ส่งผ่านไปยัง Y ดังนั้นคุณจึงไม่ ได้รับทุกสิ่งโดยการผ่านปัญหาทั้งหมดนี้
zem

อย่าลืมพูดถึงฟังก์ชั่นนี้ไม่ปลอดภัยในสแต็ก การวนซ้ำเพียงสองสามพันครั้งจะส่งผลให้สแต็กล้น
ขอบคุณ

สวัสดีฉันขอเสนอการปรับเปลี่ยนที่ "สะอาดกว่า" เล็กน้อยเนื่องจาก .apply (null, arguments) ดูเหมือนจะน่าเกลียดสำหรับฉัน: var Y = function (gen) {return (function (f) {return f (f);} (function (f) {return gen (ฟังก์ชัน (x) {return f (f) (x);});})); } หรือเทียบเท่า ((ฟังก์ชัน (x) {return y} เท่ากับ (x => y))) โดยใช้สัญลักษณ์ลูกศร (รหัส js ที่ถูกต้อง): var Y = gen => (f => f (f)) (f = > gen (x => f (f) (x)))
myfirstAnswer

23

U combinator

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

ในตัวอย่างด้านล่างเราไม่มีเงื่อนไขการออกดังนั้นเราจะวนซ้ำไปเรื่อย ๆ จนกว่าสแต็กล้นจะเกิดขึ้น

const U = f => f (f) // call function f with itself as an argument

U (f => (console.log ('stack overflow imminent!'), U (f)))

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

const log = x => (console.log (x), x)

const U = f => f (f)

// when our function is applied to itself, we get the inner function back
U (f => x => x > 0 ? U (f) (log (x - 1)) : 0)
// returns: (x => x > 0 ? U (f) (log (x - 1)) : 0)
// where f is a reference to our outer function

// watch when we apply an argument to this function, eg 5
U (f => x => x > 0 ? U (f) (log (x - 1)) : 0) (5)
// 4 3 2 1 0

สิ่งที่ไม่ปรากฏในทันทีที่นี่คือฟังก์ชันของเราเมื่อนำไปใช้กับตัวเองครั้งแรกโดยใช้ตัวUรวมกันจะส่งกลับฟังก์ชันที่รออินพุตแรก หากเราตั้งชื่อให้สิ่งนี้สามารถสร้างฟังก์ชันเรียกซ้ำได้อย่างมีประสิทธิภาพโดยใช้ lambdas (ฟังก์ชันที่ไม่ระบุชื่อ)

const log = x => (console.log (x), x)

const U = f => f (f)

const countDown = U (f => x => x > 0 ? U (f) (log (x - 1)) : 0)

countDown (5)
// 4 3 2 1 0

countDown (3)
// 2 1 0

สิ่งนี้ไม่ใช่การเรียกซ้ำโดยตรง - ฟังก์ชันที่เรียกตัวเองโดยใช้ชื่อของตัวเอง คำจำกัดความของเราcountDownไม่ได้อ้างอิงตัวเองภายในร่างกายและยังสามารถเรียกซ้ำได้

// direct recursion references itself by name
const loop = (params) => {
  if (condition)
    return someValue
  else
    // loop references itself to recur...
    return loop (adjustedParams)
}

// U combinator does not need a named reference
// no reference to `countDown` inside countDown's definition
const countDown = U (f => x => x > 0 ? U (f) (log (x - 1)) : 0)

วิธีลบการอ้างอิงตัวเองออกจากฟังก์ชันที่มีอยู่โดยใช้ U combinator

ที่นี่ฉันจะแสดงวิธีใช้ฟังก์ชันเรียกซ้ำที่ใช้การอ้างอิงถึงตัวมันเองและเปลี่ยนเป็นฟังก์ชันที่ใช้ U combinator แทนการอ้างอิงตัวเอง

const factorial = x =>
  x === 0 ? 1 : x * factorial (x - 1)
  
console.log (factorial (5)) // 120

ตอนนี้ใช้ U combinator เพื่อแทนที่การอ้างอิงภายใน factorial

const U = f => f (f)

const factorial = U (f => x =>
  x === 0 ? 1 : x * U (f) (x - 1))

console.log (factorial (5)) // 120

รูปแบบการแทนที่พื้นฐานคือสิ่งนี้ โปรดทราบว่าเราจะใช้กลยุทธ์ที่คล้ายกันในหัวข้อถัดไป

// self reference recursion
const foo =         x => ...   foo (nextX) ...

// remove self reference with U combinator
const foo = U (f => x => ... U (f) (nextX) ...)

Y combinator

ที่เกี่ยวข้อง: ตัวรวม U และ Y อธิบายโดยใช้การเปรียบเทียบกระจก

ในส่วนก่อนหน้านี้เราได้เห็นวิธีการเปลี่ยนการเรียกซ้ำการอ้างอิงตัวเองเป็นฟังก์ชันเรียกซ้ำที่ไม่ต้องอาศัยฟังก์ชันที่ตั้งชื่อโดยใช้ U combinator มีความน่ารำคาญเล็กน้อยที่ต้องจำไว้ว่าให้ส่งฟังก์ชันให้ตัวเองเป็นอาร์กิวเมนต์แรกเสมอ Y-combinator สร้างขึ้นจาก U-combinator และกำจัดบิตที่น่าเบื่อนั้นออกไป นี่เป็นสิ่งที่ดีเพราะการลบ / ลดความซับซ้อนเป็นเหตุผลหลักที่เราสร้างฟังก์ชัน

ก่อนอื่นมาหา Y-combinator ของเราเอง

// standard definition
const Y = f => f (Y (f))

// prevent immediate infinite recursion in applicative order language (JS)
const Y = f => f (x => Y (f) (x))

// remove reference to self using U combinator
const Y = U (h => f => f (x => U (h) (f) (x)))

ตอนนี้เราจะมาดูกันว่าการใช้งานเปรียบเทียบกับ U-combinator อย่างไร สังเกตว่าจะเกิดขึ้นอีกแทนที่จะU (f)เรียกว่าf ()

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

Y (f => (console.log ('stack overflow imminent!'),  f ()))

ตอนนี้ฉันจะสาธิตการcountDownใช้โปรแกรมY- คุณจะเห็นว่าโปรแกรมแทบจะเหมือนกัน แต่ Y combinator ช่วยให้ทุกอย่างสะอาดขึ้น

const log = x => (console.log (x), x)

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const countDown = Y (f => x => x > 0 ? f (log (x - 1)) : 0)

countDown (5)
// 4 3 2 1 0

countDown (3)
// 2 1 0

และตอนนี้เราจะเห็นfactorialเช่นกัน

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const factorial = Y (f => x =>
  x === 0 ? 1 :  x * f (x - 1))

console.log (factorial (5)) // 120

อย่างที่คุณเห็นfกลายเป็นกลไกในการเรียกซ้ำตัวเอง เราเรียกมันว่าเป็นฟังก์ชันธรรมดา เราสามารถเรียกมันได้หลายครั้งด้วยข้อโต้แย้งที่แตกต่างกันและผลลัพธ์จะยังคงถูกต้อง และเนื่องจากเป็นพารามิเตอร์ฟังก์ชันธรรมดาเราจึงสามารถตั้งชื่อตามที่เราต้องการได้เช่นrecurด้านล่าง -

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const fibonacci = Y (recur => n =>
  n < 2 ? n : recur (n - 1) +  (n - 2))

console.log (fibonacci (10)) // 55


U และ Y Combinator ที่มีพารามิเตอร์มากกว่า 1 ตัว

ในตัวอย่างข้างต้นเราได้เห็นวิธีที่เราสามารถวนซ้ำและส่งผ่านอาร์กิวเมนต์เพื่อติดตาม "สถานะ" ของการคำนวณของเรา แต่ถ้าเราต้องติดตามสถานะเพิ่มเติมล่ะ?

เราสามารถใช้ข้อมูลประกอบเช่น Array หรือบางสิ่ง ...

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const fibonacci = Y (f => ([a, b, x]) =>
  x === 0 ? a : f ([b, a + b, x - 1]))

// starting with 0 and 1, generate the 7th number in the sequence
console.log (fibonacci ([0, 1, 7])) 
// 0 1 1 2 3 5 8 13

แต่สิ่งนี้ไม่ดีเพราะเป็นการเปิดเผยสถานะภายใน (ตัวนับaและb) คงจะดีไม่น้อยหากเราสามารถโทรหาfibonacci (7)เพื่อรับคำตอบที่เราต้องการได้

การใช้สิ่งที่เรารู้เกี่ยวกับฟังก์ชัน curried (ลำดับของฟังก์ชัน unary (1-paramter) เราสามารถบรรลุเป้าหมายได้อย่างง่ายดายโดยไม่ต้องปรับเปลี่ยนคำจำกัดความของเราYหรือพึ่งพาข้อมูลผสมหรือคุณลักษณะของภาษาขั้นสูง

ดูคำจำกัดความของfibonacciด้านล่างอย่างใกล้ชิด เรากำลังสมัครทันที0และ1ที่ผูกพันaและbตามลำดับ ตอนนี้ fibonacci xเป็นเพียงการรอคอยสำหรับอาร์กิวเมนต์สุดท้ายที่จะจัดจำหน่ายซึ่งจะถูกผูกไว้กับ เมื่อเราเรียกคืนเราต้องเรียกf (a) (b) (x)(ไม่ใช่f (a,b,x)) เพราะฟังก์ชันของเราอยู่ในรูปแบบ curried

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const fibonacci = Y (f => a => b => x =>
  x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1)

console.log (fibonacci (7)) 
// 0 1 1 2 3 5 8 13


รูปแบบการจัดเรียงนี้มีประโยชน์สำหรับการกำหนดฟังก์ชันทุกประเภท ด้านล่างเราจะเห็นทั้งสองฟังก์ชั่นอื่น ๆ ที่กำหนดไว้โดยใช้YCombinator ( rangeและreduce) และอนุพันธ์ของ,reducemap

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const range = Y (f => acc => min => max =>
  min > max ? acc : f ([...acc, min]) (min + 1) (max)) ([])

const reduce = Y (f => g => y => ([x,...xs]) =>
  x === undefined ? y : f (g) (g (y) (x)) (xs))
  
const map = f =>
  reduce (ys => x => [...ys, f (x)]) ([])
  
const add = x => y => x + y

const sq = x => x * x

console.log (range (-2) (2))
// [ -2, -1, 0, 1, 2 ]

console.log (reduce (add) (0) ([1,2,3,4]))
// 10

console.log (map (sq) ([1,2,3,4]))
// [ 1, 4, 9, 16 ]


มันเป็น OMG ที่ไม่ระบุชื่อทั้งหมด

เนื่องจากเรากำลังทำงานกับฟังก์ชันบริสุทธิ์ที่นี่เราจึงสามารถแทนที่ฟังก์ชันที่มีชื่อสำหรับคำจำกัดความได้ ดูสิ่งที่เกิดขึ้นเมื่อเราใช้ fibonacci และแทนที่ฟังก์ชันที่มีชื่อด้วยนิพจน์

/* const U = f => f (f)
 *
 * const Y = U (h => f => f (x => U (h) (f) (x)))
 *
 * const fibonacci = Y (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1)
 *
 */

/*
 * given fibonacci (7)
 *
 * replace fibonacci with its definition
 * Y (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
 *
 * replace Y with its definition
 * U (h => f => f (x => U (h) (f) (x))) (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
//
 * replace U with its definition
 * (f => f (f)) U (h => f => f (x => U (h) (f) (x))) (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
 */

let result =
  (f => f (f)) (h => f => f (x => h (h) (f) (x))) (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
  
console.log (result) // 13

และคุณมีมัน - fibonacci (7)คำนวณซ้ำโดยไม่ใช้อะไรเลยนอกจากฟังก์ชันที่ไม่ระบุ


14

อาจง่ายที่สุดในการใช้ "วัตถุที่ไม่ระบุตัวตน" แทน:

({
  do: function() {
    console.log("don't run this ...");
    this.do();
  }
}).do();

พื้นที่ทั่วโลกของคุณไม่มีมลภาวะ ค่อนข้างตรงไปตรงมา และคุณสามารถใช้ประโยชน์จากสถานะที่ไม่ใช่โลกของวัตถุได้อย่างง่ายดาย

คุณยังสามารถใช้เมธอดอ็อบเจ็กต์ ES6 เพื่อทำให้ไวยากรณ์รัดกุมยิ่งขึ้น

({
  do() {
    console.log("don't run this ...");
    this.do();
  }
}).do();

13

ฉันจะไม่ทำสิ่งนี้เป็นฟังก์ชันอินไลน์ มันดันข้ามขอบเขตของรสชาติที่ดีและไม่ทำให้คุณได้อะไรเลย

ถ้าคุณต้องการจริงๆก็มีarguments.calleeคำตอบของ Fabrizio อย่างไรก็ตามโดยทั่วไปถือว่าไม่สามารถมองเห็นได้และไม่ได้รับอนุญาตใน 'โหมดเข้มงวด' ของ ECMAScript Fifth Edition แม้ว่า ECMA 3 และโหมดไม่เข้มงวดจะไม่หายไป แต่การทำงานในโหมดเข้มงวดจะช่วยเพิ่มประสิทธิภาพภาษาที่เป็นไปได้มากขึ้น

คุณยังสามารถใช้ฟังก์ชันอินไลน์ที่มีชื่อ:

(function foo(data){
    data++;
    var nothing = function() {
        foo(data);
    }
    nothing();
})();

อย่างไรก็ตามนิพจน์ฟังก์ชันอินไลน์ที่ตั้งชื่อก็ควรหลีกเลี่ยงได้ดีที่สุดเช่นกันเนื่องจาก JScript ของ IE ทำสิ่งที่ไม่ดีกับพวกเขา ในตัวอย่างข้างต้นfooไม่ถูกต้องมลพิษขอบเขตผู้ปกครองใน IE และผู้ปกครองfooเป็นเช่นกันไปภายในเห็นfoofoo

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


ฉันเห็นด้วยฟังก์ชันที่มีชื่อเหมาะสมกว่าอาร์กิวเมนต์ Callee ไม่เพียง แต่สำหรับโหมดที่เข้มงวดของ ecmascript เท่านั้น แต่ยังรวมถึงเรื่องของการปรับให้เหมาะสมด้วยเนื่องจากในการเรียกซ้ำแต่ละครั้งเขาจำเป็นต้องได้รับการอ้างอิงถึง callee (และอาจลดความเร็วในการดำเนินการได้ )

+1 สำหรับบทกวี"pushing against the boundaries of good taste"- (ดีและข้อมูลที่ดี)
Peter Ajtai

สิ่งที่เกี่ยวกับ pre / postfix ง่ายๆถ้ามลพิษเป็นปัญหาที่นี่? เมื่อพิจารณาว่ามันไม่ได้อยู่ในขอบเขตส่วนกลาง (แม้ว่าฟังก์ชั่นจะอยู่ในระดับสูงสุด แต่เขาก็ควรจะมีฟังก์ชันที่ไม่ระบุตัวตนที่ห่อรหัสทั้งหมดของเขาไว้แล้ว) มันไม่น่าเป็นไปได้มากที่ชื่อที่ชอบrecur_fooจะชนกับฟังก์ชันในขอบเขตหลัก (หรือไม่ดี - ใช้แล้ว)
gblazex

น่าสนใจมาก - jsfiddle.net/hck2A - IE ก่อให้เกิดมลพิษต่อผู้ปกครองในกรณีนี้เช่นที่คุณกล่าว ไม่เคยตระหนักว่า
Peter Ajtai

1
@Peter : kangax.github.com/nfe (โดยเฉพาะ 'จุดบกพร่องของ JScript') มากกว่าที่คุณอยากรู้ในเรื่องนี้ ในที่สุดก็แก้ไขใน IE9 (แต่เฉพาะในโหมดมาตรฐาน IE9)
bobince

10
(function(data){
    var recursive = arguments.callee;
    data = data+1;
    var nothing = function() {
        recursive(data)
    }
    nothing();
})();

34
ฉันหวังว่าทุกคนที่ลงคะแนนสำหรับคำตอบนี้ (ถูกต้องทางเทคนิค) จะตระหนักถึงปัญหาarguments.callee: ไม่อนุญาตในโหมดเข้มงวดและใน ES5
Pointy

โหวตลง, อาร์กิวเมนต์. callee เลิกใช้งานใน ES5
Jaime Rodriguez

ทำงานใน NodeJS ฉันไม่สนใจ ES5 น้อยลงตราบเท่าที่มันทำงานในรูปแบบที่คาดเดาได้ในสภาพแวดล้อมที่คงที่
Angad

1
นี่คือระเบิดเวลา ไม่มีสิ่งที่เรียกว่าสภาพแวดล้อม "คงที่" ตามที่ความคิดเห็นด้านบนแนะนำ คุณมักจะอัปเกรดเนื่องจากเหตุผลหลายพันประการที่จะทำเช่นนั้น
sampathsris

6

คุณสามารถทำสิ่งต่างๆเช่น:

(foo = function() { foo(); })()

หรือในกรณีของคุณ:

(recur = function(data){
    data = data+1;
    var nothing = function() {
        if (data > 100) return; // put recursion limit
        recur(data);
    }
    nothing();
})(/* put data init value here */ 0);

คุณสามารถทำได้ด้วยการประกาศrecurก่อนด้วยvarคำสั่ง Dunno ว่าผิดกฎของคำถามหรือไม่ แต่อย่างที่คุณมีตอนนี้หากไม่มีvarคำสั่งคุณจะได้รับข้อผิดพลาดในโหมดเข้มงวด ECMAScript 5
Tim Down

ความคิดเห็นเริ่มต้นของฉันมีvarคีย์เวิร์ด แต่เมื่อฉันทดสอบโค้ดนี้พบว่ามีข้อผิดพลาดเนื่องจากคุณไม่สามารถประกาศตัวแปรภายในบล็อกที่เรียกใช้ด้วยตนเองได้และวิธีการของฉันอาศัยการประกาศตัวแปรที่ไม่ได้กำหนดโดยอัตโนมัติดังนั้น @ Pointy วิธีแก้ปัญหาถูกต้องมากขึ้น แต่ฉันยังคงโหวตให้ Fabrizio Calderan คำตอบแม้ว่า;)
ArtBIT

ใช่การทำ(var recur = function() {...})();จะไม่ได้ผลเนื่องจากตอนนี้เป็นคำสั่งแทนที่จะเป็นนิพจน์การกำหนด (ซึ่งส่งคืนค่าที่กำหนด) ฉันแนะนำvar recur; (recur = function() {...})();แทน
Tim Down

3

เมื่อคุณประกาศฟังก์ชันนิรนามเช่นนี้:

(function () {
    // Pass
}());

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

(function foo () {
    foo();
}());
foo //-> undefined

"ไม่เปิดเผยตัวตน" - ไม่ ฟังก์ชันนิรนามไม่มีชื่อ ฉันเข้าใจว่าfooไม่มีการประกาศในบริบทปัจจุบัน แต่ไม่เกี่ยวข้องมากหรือน้อย ฟังก์ชันที่มีชื่อยังคงเป็นฟังก์ชันที่มีชื่อ - ไม่ระบุชื่อ
ขอบคุณ

3

ทำไมไม่ส่งฟังก์ชั่นไปที่ functio เอง?

    var functionCaller = function(thisCaller, data) {
        data = data + 1;
        var nothing = function() {
            thisCaller(thisCaller, data);
        };
        nothing();
    };
    functionCaller(functionCaller, data);

3

ในบางสถานการณ์คุณต้องพึ่งพาฟังก์ชันที่ไม่ระบุตัวตน ให้เป็นmapฟังก์ชันเรียกซ้ำ:

const map = f => acc => ([head, ...tail]) => head === undefined 
 ? acc
 : map (f) ([...acc, f(head)]) (tail);

const sqr = x => x * x;
const xs = [1,2,3,4,5];

console.log(map(sqr) ([0]) (xs)); // [0] modifies the structure of the array

โปรดทราบว่าmapจะต้องไม่แก้ไขโครงสร้างของอาร์เรย์ ดังนั้นจึงaccไม่จำเป็นต้องสัมผัสกับตัวสะสม เราสามารถรวมmapเป็นฟังก์ชันอื่นได้เช่น:

const map = f => xs => {
  let next = acc => ([head, ...tail]) => head === undefined
   ? acc
   : map ([...acc, f(head)]) (tail);

  return next([])(xs);
}

แต่วิธีนี้ค่อนข้างละเอียด มาใช้UCombinator ที่ต่ำเกินไป:

const U = f => f(f);

const map = f => U(h => acc => ([head, ...tail]) => head === undefined 
 ? acc
 : h(h)([...acc, f(head)])(tail))([]);

const sqr = x => x * x;
const xs = [1,2,3,4,5];

console.log(map(sqr) (xs));

กระชับไม่ใช่เหรอ? Uตายง่าย แต่มีข้อเสียที่การเรียกซ้ำทำให้สับสนเล็กน้อย: sum(...)กลายเป็นh(h)(...)- นั่นคือทั้งหมด


2

ฉันไม่แน่ใจว่ายังต้องการคำตอบหรือไม่ แต่สามารถทำได้โดยใช้ผู้รับมอบสิทธิ์ที่สร้างโดยใช้ function.bind:

    var x = ((function () {
        return this.bind(this, arguments[0])();
    }).bind(function (n) {
        if (n != 1) {
            return n * this.bind(this, (n - 1))();
        }
        else {
            return 1;
        }
    }))(5);

    console.log(x);

สิ่งนี้ไม่เกี่ยวข้องกับฟังก์ชันที่ตั้งชื่อหรืออาร์กิวเมนต์ callee


1

เช่นเดียวกับที่ Bobince เขียนเพียงแค่ตั้งชื่อฟังก์ชันของคุณ

แต่ฉันเดาว่าคุณต้องการส่งผ่านค่าเริ่มต้นและหยุดฟังก์ชันของคุณในที่สุด!

var initialValue = ...

(function recurse(data){
    data++;
    var nothing = function() {
        recurse(data);
    }
    if ( ... stop condition ... )
        { ... display result, etc. ... }
    else
        nothing();
}(initialValue));

ตัวอย่างการทำงาน jsFiddle (ใช้ data + = data เพื่อความสนุกสนาน)



1
+1 นี่เป็นคำตอบที่มีประโยชน์มากและคุณควรได้รับคะแนนโหวตมากขึ้น แต่ก็ไม่ระบุชื่อ
ไม่ระบุตัวตน

คุณอย่างชัดเจนไม่ได้อ่านสิ่งที่ bobince However named inline function expressions are also best avoided.wrote: แต่ OP ก็พลาดจุดนี้เช่นกัน ... :)
gblazex

@Galamb - ฉันอ่านแล้ว ไม่อนุญาตในโหมดเข้มงวดและใน ES5 ไม่เหมือนกับการสร้างมลพิษในขอบเขตหลักและการสร้างอินสแตนซ์เพิ่มเติม
Peter Ajtai

1

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

var cmTitle = 'Root' + (function cmCatRecurse(cmCat){return (cmCat == root) ? '' : cmCatRecurse(cmCat.parent) + ' : ' + cmCat.getDisplayName();})(cmCurrentCat);

ซึ่งสร้างสตริงเช่น 'Root: foo: bar: baz: ... '


1

ด้วย ES2015 เราสามารถเล่นกับไวยากรณ์และการละเมิดพารามิเตอร์เริ่มต้นและ thunks ได้ ส่วนหลังเป็นเพียงฟังก์ชันที่ไม่มีข้อโต้แย้งใด ๆ :

const applyT = thunk => thunk();

const fib = n => applyT(
  (f = (x, y, n) => n === 0 ? x : f(y, x + y, n - 1)) => f(0, 1, n)
);

console.log(fib(10)); // 55

// Fibonacci sequence: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55...

โปรดทราบว่าfเป็นพารามิเตอร์ที่มีฟังก์ชันนิรนาม(x, y, n) => n === 0 ? x : f(y, x + y, n - 1)เป็นค่าเริ่มต้น เมื่อfใดที่ถูกเรียกโดยการเรียกใช้applyTนี้จะต้องเกิดขึ้นโดยไม่มีอาร์กิวเมนต์เพื่อให้ใช้ค่าเริ่มต้น ค่าดีฟอลต์คือฟังก์ชันและด้วยเหตุนี้จึงfเป็นฟังก์ชันที่มีชื่อซึ่งสามารถเรียกตัวเองซ้ำได้


0

คำตอบอื่นที่ไม่เกี่ยวข้องกับฟังก์ชันที่มีชื่อหรืออาร์กิวเมนต์ callee

var sum = (function(foo,n){
  return n + foo(foo,n-1);
})(function(foo,n){
     if(n>1){
         return n + foo(foo,n-1)
     }else{
         return n;
     }
},5); //function takes two argument one is function and another is 5

console.log(sum) //output : 15

nice: ผูกฟังก์ชันที่ไม่ระบุตัวตนเข้ากับพารามิเตอร์โลคัลจากนั้นเรียกใช้ฟังก์ชันผ่านพารามิเตอร์โลคัล แต่ยังส่งผ่านฟังก์ชันไปยังตัวเองสำหรับการเรียกซ้ำ
englebart

0

นี่คือการแก้ไขคำตอบของ jforjs ที่มีชื่อต่างกันและรายการที่ปรับเปลี่ยนเล็กน้อย

// function takes two argument: first is recursive function and second is input
var sum = (function(capturedRecurser,n){
  return capturedRecurser(capturedRecurser, n);
})(function(thisFunction,n){
     if(n>1){
         return n + thisFunction(thisFunction,n-1)
     }else{
         return n;
     }
},5); 

console.log(sum) //output : 15

ไม่จำเป็นต้องยกเลิกการเรียกซ้ำครั้งแรก ฟังก์ชั่นที่รับตัวเองเป็น harkens อ้างอิงกลับไปที่ ooze ดั้งเดิมของ OOP


0

นี่คือคำตอบของ @ zem พร้อมฟังก์ชันลูกศร

คุณสามารถใช้UหรือYCombinator Y Combinator เป็นวิธีที่ใช้ง่ายที่สุด

U combinator ด้วยสิ่งนี้คุณต้องผ่านฟังก์ชั่นต่อไป: const U = f => f(f) U(selfFn => arg => selfFn(selfFn)('to infinity and beyond'))

Y ด้วยวิธีนี้คุณไม่จำเป็นต้องส่งผ่านฟังก์ชันต่อไปนี้: const Y = gen => U(f => gen((...args) => f(f)(...args))) Y(selfFn => arg => selfFn('to infinity and beyond'))


0

ยังเป็นอีกหนึ่งโซลูชัน Y-combinator โดยใช้ลิงก์rosetta-code (ฉันคิดว่าก่อนหน้านี้มีคนพูดถึงลิงก์ที่ใดที่หนึ่งใน stackOverflow

ลูกศรมีไว้สำหรับฟังก์ชั่นที่ไม่ระบุชื่อที่ฉันอ่านได้มากขึ้น:

var Y = f => (x => x(x))(y => f(x => y(y)(x)));

-1

สิ่งนี้อาจใช้ไม่ได้ทุกที่ แต่คุณสามารถใช้arguments.calleeเพื่ออ้างถึงฟังก์ชันปัจจุบันได้

ดังนั้นแฟกทอเรียลสามารถทำได้ดังนี้:

var fac = function(x) { 
    if (x == 1) return x;
    else return x * arguments.callee(x-1);
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.