อะไรคือแรงจูงใจสำหรับการมอบหมายงาน Scala ที่ประเมินเป็นหน่วยแทนที่จะเป็นค่าที่กำหนด


84

อะไรคือแรงจูงใจสำหรับการมอบหมายงาน Scala ที่ประเมินเป็นหน่วยแทนที่จะเป็นค่าที่กำหนด

รูปแบบทั่วไปในการเขียนโปรแกรม I / O คือการทำสิ่งนี้:

while ((bytesRead = in.read(buffer)) != -1) { ...

แต่สิ่งนี้ทำไม่ได้ในสกาล่าเพราะ ...

bytesRead = in.read(buffer)

.. ส่งคืนหน่วยไม่ใช่ค่าใหม่ของ bytesRead

ดูเหมือนเป็นสิ่งที่น่าสนใจที่จะละเว้นจากภาษาที่ใช้งานได้ ฉันสงสัยว่าทำไมถึงทำเช่นนั้น?


David Pollack ได้โพสต์ข้อมูลโดยตรงซึ่งได้รับการรับรองจากความคิดเห็นที่ Martin Odersky เขียนไว้ในคำตอบของเขา ฉันคิดว่ามีใครสามารถตอบรับคำตอบของพอลแล็คได้อย่างปลอดภัย
Daniel C. Sobral

คำตอบ:


89

ฉันสนับสนุนให้มีการมอบหมายคืนค่าที่กำหนดแทนที่จะเป็นหน่วย มาร์ตินกับฉันกลับไปกลับมา แต่ข้อโต้แย้งของเขาคือการใส่ค่าลงในสแต็กเพียงเพื่อให้มันหลุด 95% ของเวลานั้นเป็นการเสียไบต์โค้ดและส่งผลเสียต่อประสิทธิภาพ


7
มีสาเหตุหรือไม่ที่คอมไพลเลอร์ Scala ไม่สามารถตรวจสอบได้ว่ามีการใช้ค่าของการกำหนดจริงหรือไม่และสร้างรหัสไบต์ที่มีประสิทธิภาพตามนั้นหรือไม่
Matt R

43
ไม่ใช่เรื่องง่ายเมื่อมีเซ็ตเตอร์: เซ็ตเตอร์ทุกคนต้องส่งคืนผลลัพธ์ซึ่งเป็นความเจ็บปวดในการเขียน จากนั้นคอมไพเลอร์จะต้องปรับให้เหมาะสมที่สุดซึ่งยากที่จะทำข้ามสาย
Martin Odersky

1
ข้อโต้แย้งของคุณมีเหตุผล แต่ java & C # ก็ต่อต้านสิ่งนั้น ฉันเดาว่าคุณกำลังทำอะไรแปลก ๆ กับโค้ดไบต์ที่สร้างขึ้นจากนั้นการมอบหมายงานใน Scala จะถูกรวบรวมเป็นไฟล์คลาสและการถอดรหัสกลับเป็น Java จะเป็นอย่างไร?
PhươngNguyễn

3
@ PhươngNguyễnความแตกต่างคือ Uniform Access Principle ใน C # / setters Java (ปกติ) voidผลตอบแทน ใน Scala foo_=(v: Foo)ควรส่งคืนFooหากได้รับมอบหมาย
Alexey Romanov

5
@Martin Odersky: วิธีการต่อไปนี้: setters ยังคงอยู่void( Unit), การมอบหมายx = valueได้รับการแปลเทียบเท่าx.set(value);x.get(value); คอมไพลเลอร์จะกำจัดในการเพิ่มประสิทธิภาพเฟสget-calls หากไม่ได้ใช้ค่า อาจเป็นการเปลี่ยนแปลงที่น่ายินดีในรุ่นใหญ่ใหม่ (เนื่องจากความเข้ากันไม่ได้ย้อนหลัง) รุ่น Scala และการระคายเคืองน้อยลงสำหรับผู้ใช้ คุณคิดอย่างไร?
Eugen Labun

20

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

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

วิธีแก้ปัญหา:

while ({bytesRead = in.read(buffer); bytesRead != -1}) { ... 

เพื่อสิ่งที่คุ้มค่า

ในฐานะที่เป็นคำอธิบายอื่น ๆ บางที Martin Odersky อาจต้องเผชิญกับข้อบกพร่องที่น่าเกลียดมากที่เกิดจากการใช้งานดังกล่าวและตัดสินใจที่จะผิดกฎหมายจากภาษาของเขา

แก้ไข

David Pollackได้ตอบด้วยข้อเท็จจริงที่เป็นจริงบางประการซึ่งได้รับการรับรองอย่างชัดเจนจากข้อเท็จจริงที่ว่าMartin Oderskyแสดงความคิดเห็นคำตอบของเขาโดยให้ความเชื่อถือในประเด็นที่เกี่ยวข้องกับการปฏิบัติงานโดย Pollack


3
น่าจะเป็นforเวอร์ชันลูป: for (bytesRead <- in.read(buffer) if (bytesRead) != -1ซึ่งดีมากยกเว้นว่าจะใช้ไม่ได้เพราะไม่มีforeachและwithFilterพร้อมใช้งาน!
oxbow_lakes

12

เหตุการณ์นี้เกิดขึ้นเนื่องจาก Scala มีระบบประเภทที่ "ถูกต้องอย่างเป็นทางการ" มากกว่า การพูดอย่างเป็นทางการการมอบหมายงานเป็นคำสั่งที่มีผลข้างเคียงอย่างUnitแท้จริงดังนั้นจึงควรกลับมา สิ่งนี้มีผลดีบางอย่าง ตัวอย่างเช่น:

class MyBean {
  private var internalState: String = _

  def state = internalState

  def state_=(state: String) = internalState = state
}

state_=วิธีการส่งกลับUnit(ตามที่คาดว่าจะสำหรับหมา) Unitที่แม่นยำเพราะผลตอบแทนที่ได้รับมอบหมาย

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


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

2
@Graham: แต่จากนั้นคุณจะต้องปฏิบัติตามความสอดคล้องและตรวจสอบให้แน่ใจว่าตัวตั้งค่าทั้งหมดของคุณมีความซับซ้อนไม่ว่าพวกเขาจะส่งคืนค่าที่ตั้งไว้ สิ่งนี้จะซับซ้อนในบางกรณีและในกรณีอื่น ๆ ฉันคิดว่าผิด (คุณจะส่งคืนอะไรในกรณีที่เกิดข้อผิดพลาด null? - ไม่ใช่ไม่มี? - ประเภทของคุณจะเป็น Option [T]) ฉันคิดว่ามันยากที่จะสอดคล้องกับสิ่งนั้น
Debilski

7

อาจเป็นเพราะหลักการแยกคำสั่งคิวรี ?

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

ภาพประกอบสั้น ๆ ว่าทำไม CQS เป็นประโยชน์: พิจารณาสมมุติไฮบริดภาษา F / OO กับListชั้นเรียนที่มีวิธีการSort, Append, และFirst Lengthในรูปแบบ OO ที่จำเป็นเราอาจต้องการเขียนฟังก์ชันเช่นนี้:

func foo(x):
    var list = new List(4, -2, 3, 1)
    list.Append(x)
    list.Sort()
    # list now holds a sorted, five-element list
    var smallest = list.First()
    return smallest + list.Length()

ในขณะที่รูปแบบการใช้งานมีแนวโน้มที่จะเขียนสิ่งนี้มากกว่า:

func bar(x):
    var list = new List(4, -2, 3, 1)
    var smallest = list.Append(x).Sort().First()
    # list still holds an unsorted, four-element list
    return smallest + list.Length()

สิ่งเหล่านี้ดูเหมือนจะพยายามทำในสิ่งเดียวกัน แต่เห็นได้ชัดว่าหนึ่งในสองอย่างนั้นไม่ถูกต้องและโดยไม่ทราบข้อมูลเพิ่มเติมเกี่ยวกับพฤติกรรมของวิธีการเราไม่สามารถบอกได้ว่าวิธีใด

อย่างไรก็ตามการใช้ CQS เราจะยืนยันว่าหากAppendและSortเปลี่ยนแปลงรายการพวกเขาจะต้องส่งคืนประเภทหน่วยดังนั้นจึงป้องกันไม่ให้เราสร้างจุดบกพร่องโดยใช้รูปแบบที่สองเมื่อเราไม่ควร ดังนั้นการปรากฏตัวของผลข้างเคียงจึงกลายเป็นนัยในลายเซ็นของวิธีการด้วย


4

ฉันเดาว่านี่คือเพื่อให้โปรแกรม / ภาษาปราศจากผลข้างเคียง

สิ่งที่คุณอธิบายคือการใช้ผลข้างเคียงโดยเจตนาซึ่งในกรณีทั่วไปถือเป็นสิ่งที่ไม่ดี


เฮ้. Scala ปราศจากผลข้างเคียง? :) นอกจากนี้จินตนาการกรณีเช่นval a = b = 1(จินตนาการ "ขลัง" valในด้านหน้าของb) val a = 1; val b = 1;กับ

สิ่งนี้ไม่เกี่ยวข้องกับผลข้างเคียงอย่างน้อยก็ไม่ได้อยู่ในความหมายที่อธิบายไว้ที่นี่: ผลข้างเคียง (วิทยาศาสตร์คอมพิวเตอร์)
Feuermurmel

4

ไม่ใช่รูปแบบที่ดีที่สุดในการใช้การกำหนดเป็นนิพจน์บูลีน คุณทำสองสิ่งในเวลาเดียวกันซึ่งมักนำไปสู่ข้อผิดพลาด และหลีกเลี่ยงการใช้ "=" แทน "==" โดยไม่ได้ตั้งใจด้วยข้อ จำกัด ของ Scalas


2
ฉันคิดว่านี่เป็นเหตุผลที่น่าขยะแขยง! ตามที่ OP โพสต์โค้ดจะยังคงคอมไพล์และรัน: มันไม่ได้ทำในสิ่งที่คุณคาดหวังอย่างสมเหตุสมผล อีกหนึ่ง gotcha ไม่ใช่น้อย!
oxbow_lakes

1
ถ้าคุณเขียนบางอย่างเช่น if (a = b) มันจะไม่คอมไพล์ ดังนั้นอย่างน้อยข้อผิดพลาดนี้ก็สามารถหลีกเลี่ยงได้
deamon

1
OP ไม่ได้ใช้ '=' แทน '==' เขาใช้ทั้งสองอย่าง เขาคาดหวังว่างานจะส่งคืนค่าที่สามารถนำไปใช้ได้เช่นเปรียบเทียบกับค่าอื่น (-1 ในตัวอย่าง)
IttayD

@deamon: มันจะคอมไพล์ (ใน Java เป็นอย่างน้อย) ถ้า a และ b เป็นบูลีน ฉันเคยเห็นมือใหม่ตกหลุมพรางนี้โดยใช้ if (a = true) อีกเหตุผลหนึ่งที่ชอบคำที่ง่ายกว่า if (a) (และชัดเจนกว่าถ้าใช้ชื่อที่มีความหมายมากกว่านี้!)
PhiLho

2

โดยวิธีการ: ฉันพบว่า while-trick เริ่มต้นโง่แม้ใน Java ทำไมไม่คิดแบบนี้ล่ะ?

for(int bytesRead = in.read(buffer); bytesRead != -1; bytesRead = in.read(buffer)) {
   //do something 
}

จริงอยู่งานจะปรากฏขึ้นสองครั้ง แต่อย่างน้อย bytesRead อยู่ในขอบเขตที่เป็นของและฉันไม่ได้เล่นกับเทคนิคการมอบหมายที่ตลก ...


1
แม้ว่าเคล็ดลับจะเป็นเรื่องธรรมดา แต่ก็มักจะปรากฏในทุกแอปที่อ่านผ่านบัฟเฟอร์ และดูเหมือนเวอร์ชันของ OP เสมอ
TWiStErRob

0

คุณสามารถมีวิธีแก้ปัญหานี้ได้ตราบเท่าที่คุณมีประเภทการอ้างอิงสำหรับทิศทาง ในการใช้งานที่ไร้เดียงสาคุณสามารถใช้สิ่งต่อไปนี้สำหรับประเภทที่กำหนดเอง

case class Ref[T](var value: T) {
  def := (newval: => T)(pred: T => Boolean): Boolean = {
    this.value = newval
    pred(this.value)
  }
}

จากนั้นภายใต้ข้อ จำกัด ที่คุณจะต้องใช้ref.valueเพื่อเข้าถึงข้อมูลอ้างอิงในภายหลังคุณสามารถเขียนเพรดิเคตของคุณwhileเป็น

val bytesRead = Ref(0) // maybe there is a way to get rid of this line

while ((bytesRead := in.read(buffer)) (_ != -1)) { // ...
  println(bytesRead.value)
}

และคุณสามารถทำการตรวจสอบเทียบกับ bytesReadในลักษณะที่เป็นนัยมากขึ้นโดยไม่ต้องพิมพ์

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