เป็นไปได้ไหมที่จะมีฟังก์ชั่นการแกงและการแปรผันในเวลาเดียวกัน?


13

ฉันกำลังคิดเกี่ยวกับการทำให้ฟังก์ชั่นการแกงและการเปลี่ยนแปลงทั้งสองมีอยู่ในภาษาการเขียนโปรแกรมฟังก์ชั่นแบบไดนามิก แต่ฉันสงสัยว่ามันเป็นไปได้หรือไม่

นี่คือรหัสเทียมบางส่วน:

sum = if @args.empty then 0 else @args.head + sum @args.tail

ซึ่งคาดว่าจะรวมข้อโต้แย้งของมันทั้งหมด แล้วถ้าตัวเองจะได้รับการรักษาเป็นจำนวนมากแล้วผลที่ได้คือsum 0ตัวอย่างเช่น,

sum + 1

เท่ากับ 1 โดยสมมติว่า+สามารถใช้กับตัวเลขได้ อย่างไรก็ตามแม้sum == 0จะเป็นจริงsumจะยังคงรักษาคุณค่าและคุณสมบัติการทำงานของมันไม่ว่าจะมีอาร์กิวเมนต์เท่าใด (เช่น "นำไปใช้บางส่วน" และ "variadic" ในเวลาเดียวกัน) ตัวอย่างเช่นถ้าฉันประกาศ

g = sum 1 2 3

จากนั้นgเท่ากับ6แต่เรายังคงสามารถใช้ต่อไปgได้ ตัวอย่างเช่นg 4 5 == 15เป็นจริง ในกรณีนี้เราไม่สามารถแทนที่วัตถุgด้วยตัวอักษร6เพราะแม้ว่าพวกเขาให้ค่าเดียวกันเมื่อถือว่าเป็นจำนวนเต็มพวกเขามีรหัสที่แตกต่างกันภายใน

หากการออกแบบนี้ถูกใช้ในภาษาการเขียนโปรแกรมจริงมันจะทำให้เกิดความสับสนหรือความกำกวมหรือไม่?


1
การพูดอย่างเคร่งครัดโดยใช้การแกงเป็นพื้นฐานของภาษาหมายความว่าทุกฟังก์ชั่นนั้นไม่แตกต่างกันไป - ไม่เพียง แต่จะไม่มีฟังก์ชั่นแปรผันเท่านั้น แต่ยังไม่มีแม้แต่ไบนารี่! อย่างไรก็ตามโปรแกรมในภาษานั้นจะยังคงดูราวกับว่าพวกเขาได้รับการโต้แย้งหลาย ๆ อย่างและนั่นก็คือฟังก์ชั่นแปรผันเช่นเดียวกับที่เกิดขึ้นตามปกติ
Kilian Foth

ถ้าอย่างนั้นคำถามของฉันก็คือ "วัตถุสามารถเป็นฟังก์ชันและค่าที่ไม่ใช่ฟังก์ชันได้พร้อมกันหรือไม่" ในตัวอย่างข้างต้นsumเป็น0โดยไม่โต้แย้งและเรียกตัวเองซ้ำกับข้อโต้แย้ง
Michael Tsang

นั่นไม่ใช่งานของreduceหรือ
วงล้อประหลาด

1
ลองดูที่ฟังก์ชั่นที่คุณใช้ในargs: empty, และhead tailนั่นคือฟังก์ชั่นลิสต์ทั้งหมดซึ่งบอกว่าบางทีสิ่งที่ง่ายกว่าและตรงไปตรงมากว่านั้นก็คือการใช้ลิสต์ที่มีสิ่งที่หลากหลาย (ดังนั้นsum [1, 2, 3]แทนที่จะเป็นsum 1 2 3)
Michael Shaw

คำตอบ:


6

สามารถนำ varargs ไปใช้ได้อย่างไร เราต้องการกลไกบางอย่างในการส่งสัญญาณการสิ้นสุดของรายการอาร์กิวเมนต์ สิ่งนี้สามารถเป็นได้

  • ค่าเทอร์มิเนเตอร์พิเศษหรือ
  • ความยาวของรายการ vararg ส่งผ่านเป็นพารามิเตอร์พิเศษ

กลไกทั้งสองนี้สามารถใช้ในบริบทของการแก้ปัญหาเพื่อนำ varargs มาใช้ แต่การพิมพ์ที่เหมาะสมกลายเป็นปัญหาหลัก สมมติว่าเรากำลังเผชิญกับฟังก์ชั่นsum: ...int -> intยกเว้นว่าฟังก์ชั่นนี้ใช้ currying (ดังนั้นเราจึงมีประเภทที่คล้ายกันมากขึ้นsum: int -> ... -> int -> intยกเว้นว่าเราไม่ทราบจำนวนของข้อโต้แย้ง)

ค่า Terminator: กรณีอนุญาตendเป็น Terminator พิเศษและเป็นชนิดของT sumตอนนี้เรารู้ว่านำไปใช้กับendผลตอบแทนที่ฟังก์ชั่นนี้sum: end -> intและที่นำไปใช้กับ int เราได้รับอีก sum: int -> Tsum-เช่นฟังก์ชั่น: ดังนั้นTการรวมกันของประเภทเหล่านี้: T = (end -> int) | (int -> T). โดยการแทนTเราได้รับเป็นไปได้ประเภทต่างๆเช่นend -> int, int -> end -> int, int -> int -> end -> intฯลฯ แต่ส่วนใหญ่ระบบการพิมพ์ไม่รองรับประเภทดังกล่าว

ตัวพิมพ์: ความยาวอย่างชัดเจน: อาร์กิวเมนต์แรกของฟังก์ชัน vararg คือจำนวนของ varargs ดังนั้นsum 0 : int, sum 1 : int -> int, sum 3 : int -> int -> int -> intฯลฯ ซึ่งได้รับการสนับสนุนในระบบการพิมพ์บางอย่างและเป็นตัวอย่างของการพิมพ์ขึ้น ที่จริงจำนวนของการขัดแย้งจะเป็นพารามิเตอร์ชนิดและไม่ได้เป็นพารามิเตอร์ปกติ - มันจะไม่ทำให้ความรู้สึกของ arity ของการทำงานขึ้นอยู่กับค่ารันไทม์ที่s = ((sum (floor (rand 3))) 1) 2จะเห็นได้ชัดไม่ดีพิมพ์: ประเมินนี้อย่างใดอย่างหนึ่งs = ((sum 0) 1) 2 = (0 1) 2, หรือs = ((sum 1) 1) 2 = 1 2s = ((sum 2) 1) 2 = 3

ในทางปฏิบัติไม่ควรใช้เทคนิคเหล่านี้เนื่องจากมีแนวโน้มที่จะเกิดข้อผิดพลาดและไม่มีประเภท (มีความหมาย) ในระบบประเภททั่วไป sum: [int] -> intแต่เพียงแค่ผ่านรายการของค่าพารามิเตอร์เป็นหนึ่งดังนี้

ใช่มันเป็นไปได้สำหรับวัตถุที่จะปรากฏเป็นทั้งฟังก์ชั่นและค่าเช่นในระบบประเภทที่มีการข่มขู่ อนุญาตsumเป็นSumObj, ซึ่งมีการบีบบังคับสองแบบ:

  • coerce: SumObj -> int -> SumObjอนุญาตให้sumใช้เป็นฟังก์ชันและ
  • coerce: SumObj -> int ช่วยให้เราสามารถดึงผลลัพธ์

ในทางเทคนิคนี่คือการเปลี่ยนแปลงของกรณีค่า terminator ข้างต้นด้วยT = SumObjและcoerceเป็น un-wrapper สำหรับประเภท ในภาษาเชิงวัตถุจำนวนมากสิ่งนี้สามารถนำไปใช้งานได้อย่างมากกับตัวดำเนินการโอเวอร์โหลดเช่น C ++:

#include <iostream>
using namespace std;

class sum {
  int value;
public:
  explicit sum() : sum(0) {}
  explicit sum(int x) : value(x) {}
  sum operator()(int x) const { return sum(value + x); }  // function call overload
  operator int() const { return value; } // integer cast overload
};

int main() {
  int zero = sum();
  cout << "zero sum as int: " << zero << '\n';
  int someSum = sum(1)(2)(4);
  cout << "some sum as int: " << someSum << '\n';
}

คำตอบที่ยอดเยี่ยม! ข้อเสียเปรียบที่มีการบรรจุ varargs ขึ้นในรายการคือการที่คุณสูญเสียแอปพลิเคชั่นการแกงบางส่วน ฉันกำลังเล่นกับวิธีการปิดท้ายเวอร์ชัน Python ของคุณโดยใช้อาร์กิวเมนต์คำหลัก..., force=False)เพื่อบังคับใช้แอปพลิเคชันของฟังก์ชันเริ่มต้น
ThomasH

curryList : ([a] -> b) -> [a] -> [a] -> b, curryList f xs ys = f (xs ++ ys)คุณสามารถทำให้ฟังก์ชั่นการสั่งซื้อที่สูงขึ้นของคุณเองที่บางส่วนนำไปใช้ฟังก์ชั่นที่ใช้รายการเช่น
แจ็ค

2

คุณอาจต้องการที่จะมองไปที่การดำเนินงานของ printf ใน Haskellพร้อมกับคำอธิบายของวิธีการทำงานนี้ มีการเชื่อมโยงในหน้าหลังไปยังกระดาษของ Oleg Klysov ในการทำสิ่งนี้ซึ่งเป็นมูลค่าการอ่าน ในความเป็นจริงหากคุณกำลังออกแบบภาษาที่ใช้งานได้เว็บไซต์ของ Olegน่าจะเป็นการอ่านภาคบังคับ

ในความคิดของฉันวิธีการเหล่านี้เป็นบิตของการแฮ็ก แต่พวกเขาแสดงให้เห็นว่ามันเป็นไปได้ อย่างไรก็ตามหากภาษาของคุณมีการพิมพ์ที่สมบูรณ์ขึ้นอยู่กับว่ามันง่ายกว่ามาก ฟังก์ชัน Variadic เพื่อรวมอาร์กิวเมนต์จำนวนเต็มของมันจะมีลักษณะดังนี้:

type SumType = (t : union{Int,Null}) -> {SumType, if t is Int|
                                         Int,     if t is Null}
sum :: SumType
sum (v : Int) = v + sum
sum (v : Null) = 0

นามธรรมสำหรับการกำหนดประเภทซ้ำโดยไม่จำเป็นต้องให้ชื่อที่ชัดเจนอาจทำให้การเขียนฟังก์ชั่นดังกล่าวง่ายขึ้น

แก้ไข: แน่นอนฉันเพิ่งอ่านคำถามอีกครั้งและคุณบอกว่าภาษาที่พิมพ์แบบไดนามิกณ จุดนี้เห็นได้ชัดว่ากลไกประเภทไม่เกี่ยวข้องจริงๆและดังนั้นคำตอบ @ amon อาจมีทุกสิ่งที่คุณต้องการ โอ้ดีฉันจะออกจากที่นี่ในกรณีที่ทุกคนเจอในขณะที่สงสัยเกี่ยวกับวิธีการในภาษาคงที่ ...


0

นี่คือรุ่นสำหรับการปิดฟังก์ชัน variadic ใน Python3 ที่ใช้วิธี "terminator" ของ @amon โดยใช้ประโยชน์จากอาร์กิวเมนต์ตัวเลือกของ Python:

def curry_vargs(g):
    actual_args = []
    def f(a, force=False):
        nonlocal actual_args
        actual_args.append(a)
        if force:
            res = g(*actual_args)
            actual_args = []
            return res
        else:
            return f
    return f

def g(*args): return sum(args)
f = curry_vargs(g)
f(1)(2)(3)(4,True) # => 10

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

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

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

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

มันอาจจะง่ายใน Lisp เนื่องจากสัญลักษณ์ Lisp สามารถมีค่าและค่าฟังก์ชันในเวลาเดียวกัน ค่าฟังก์ชั่นจะถูกเลือกเพียงเมื่อสัญลักษณ์ปรากฏในตำแหน่งฟังก์ชั่น (เป็นองค์ประกอบแรกในรายการ)

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