การผูกมัดวิธีการแบบ Null-safe ด้วยตัวเลือก

รูปแบบ Optional ของ Guava นั้นยอดเยี่ยม เนื่องจากช่วยขจัดความคลุมเครือที่เป็นโมฆะ เมธอด transform มีประโยชน์มากสำหรับการสร้างกลุ่มเมธอดแบบ null-safe เมื่อส่วนแรกของเชนอาจหายไป แต่ไม่มีประโยชน์เมื่อส่วนอื่นๆ ของเชนหายไป

คำถามนี้เกี่ยวข้องกับ Guava Optional type เมื่อการแปลงส่งกลับ Optional อื่น ซึ่งถามคำถามเดียวกันเป็นหลัก แต่สำหรับกรณีการใช้งานอื่นซึ่งฉันคิดว่าอาจไม่ใช่การใช้งานที่ตั้งใจไว้ของ Optional (การจัดการข้อผิดพลาด)

พิจารณาวิธีการ Optional<Book> findBook(String id) findBook(id).transform(Book.getName) ทำงานได้ตามที่คาดไว้ หากไม่พบหนังสือ เราจะได้ Absent<String> หากพบหนังสือ เราจะได้ Present<String>

ในกรณีทั่วไปที่วิธีการระดับกลางอาจส่งคืน null/absent() ดูเหมือนจะไม่มีวิธีที่ดีในการโยงสาย ตัวอย่างเช่น สมมติว่า Book มีเมธอด Optional<Publisher> getPublisher() และเราต้องการรับหนังสือทั้งหมดที่จัดพิมพ์โดยผู้จัดพิมพ์หนังสือ ไวยากรณ์ตามธรรมชาติดูเหมือนจะเป็น findBook(id).transform(Book.getPublisher).transform(Publisher.getPublishedBooks) อย่างไรก็ตาม สิ่งนี้จะล้มเหลวเนื่องจากการเรียก transform(Publisher.getPublishedBooks) จะส่งกลับ Optional<Optional<Publisher>> จริงๆ

ดูเหมือนสมเหตุสมผลพอสมควรที่จะมีเมธอด transform()-like บน Optional ที่จะยอมรับฟังก์ชันที่ส่งคืน Optional มันจะทำหน้าที่เหมือนการใช้งานปัจจุบันทุกประการ ยกเว้นว่าจะไม่รวมผลลัพธ์ของฟังก์ชันไว้ในตัวเลือก การใช้งาน (สำหรับ Present) อาจอ่านได้:

public abstract <V> Optional<V> optionalTransform(Function<? super T, Optional<V>> function) {
    return function.apply(reference);
}

การใช้งานสำหรับ Absent ไม่มีการเปลี่ยนแปลงจาก transform:

public abstract <V> Optional<V> optionalTransform(Function<? super T, Optional<V>> function) {
    checkNotNull(function);
    return Optional.absent();
}

คงจะดีไม่น้อยหากมีวิธีจัดการกับเมธอดที่ส่งคืน null ซึ่งต่างจาก Optional สำหรับการทำงานกับออบเจ็กต์ดั้งเดิม วิธีการดังกล่าวจะเป็นเช่น transform แต่เพียงแค่เรียก Optional.fromNullable ตามผลลัพธ์ของฟังก์ชัน

ฉันอยากรู้ว่ามีใครพบปัญหานี้หรือไม่และพบวิธีแก้ปัญหาที่ดี (ซึ่งไม่เกี่ยวข้องกับการเขียนคลาส Optional ของคุณเอง) ฉันยังอยากได้ยินจากทีม Guava หรือให้พูดถึงการสนทนาที่เกี่ยวข้องกับปัญหานี้ด้วย (ฉันไม่พบสิ่งใดในการค้นหาของฉัน)


person Yona Appletree    schedule 31.05.2013    source แหล่งที่มา
comment
คุณได้ลองใช้กลุ่มสนทนาแล้วหรือยัง ลองโพสต์ที่นั่น แม้ว่าจะเพียงเพื่อชี้ไปที่คำถามนี้ (เขียนดี) ก็ตาม   -  person Paul Bellora    schedule 03.07.2013


คำตอบ (2)


คุณกำลังมองหา Monad อยู่ แต่ตัวเลือกของ Guava (ตรงข้ามกับตัวอย่างตัวเลือกของ Scala) เป็นเพียง Functor

ฟันเตอร์นี่มันอะไรกันเนี่ย!

Functor และ Monad เป็นกล่องชนิดหนึ่ง ซึ่งเป็นบริบทที่ห่อหุ้มคุณค่าบางอย่าง Functor ที่มีค่าบางประเภท A รู้วิธีใช้ฟังก์ชัน A => B และนำผลลัพธ์กลับเข้าไปใน Functor ตัวอย่างเช่น: นำบางอย่างออกจาก Optional แปลงร่าง และรวมกลับเข้าไปใน Optional ในภาษาโปรแกรมเชิงฟังก์ชัน วิธีการดังกล่าวมักมีชื่อว่า 'map'

โมนา.. อะไร?

Monad เกือบจะเหมือนกับ Functor ยกเว้นว่าจะใช้ฟังก์ชันส่งคืนค่าที่ห่อด้วย Monad (A => Monad เช่น Int => Optional) วิธีการของโมนาดเวทย์มนตร์นี้มักเรียกว่า 'flatMap'

คุณจะพบคำอธิบายที่ยอดเยี่ยมจริงๆ สำหรับคำศัพท์พื้นฐานของ FP ได้ที่นี่: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html

Functors & Monads กำลังมา!

ตัวเลือกจาก Java 8 สามารถจัดประเภทเป็นทั้ง Functor (http://docs.oracle.com/javase/8/docs/api/java/util/Optional.html#map-java.util.functionฟังก์ชั่น-) และ Monad (http://docs.oracle.com/javase/8/docs/api/java/util/Optional.html#flatMap-java.util.function.Function-)

สวัสดีคุณ (โฆษณา) olog, Marcin แต่ฉันจะแก้ไขปัญหาของฉันได้อย่างไร

ขณะนี้ฉันกำลังทำงานในโปรเจ็กต์ที่ใช้ Java 6 และเมื่อวานนี้ฉันเขียนคลาสตัวช่วยชื่อ 'ตัวเลือก' ซึ่งช่วยฉันประหยัดเวลาได้มาก

มันมีวิธีการช่วยเหลือบางอย่างที่ทำให้ฉันเปลี่ยนทางเลือกเป็น Monads (flatMap)

นี่คือโค้ด: https://gist.github.com/mkubala/046ae20946411f80ac52

เนื่องจากโค้ดเบสของโปรเจ็กต์ของฉันยังคงใช้ค่าว่างเป็นค่าส่งคืน ฉันจึงแนะนำ Optionals.lift(Function) ซึ่งสามารถใช้ในการตัดผลลัพธ์ลงในตัวเลือกได้

เหตุใดจึงต้องยกผลลัพธ์เป็นตัวเลือก? เพื่อหลีกเลี่ยงสถานการณ์ที่ฟังก์ชันที่ส่งผ่านไปยังการแปลงอาจคืนค่า null และนิพจน์ทั้งหมดจะส่งกลับ "present of null" (ซึ่งเป็นไปไม่ได้ด้วยตัวเลือกของ Guava เนื่องจากเงื่อนไขภายหลังนี้ -> ดูบรรทัด #71 ของ https://code.google.com/p/guava-libraries/source/browse/guava/src/com/google/common/base/Present.java?r=0823847e96b1d082e94f06327cf218e418fe2228#71)

ตัวอย่างบางส่วน

สมมติว่า findEntity() ส่งคืน Optional และ Entity.getDecimalField(..) อาจส่งคืน BigDecimal หรือ null:

Optional<BigDecimal> maybeDecimalValue = Optionals.flatMap(
    findEntity(),
    new Function<Entity, Optional<BigDecimal>> () {
        @Override 
        public Optional<BigDecimal> apply(Entity input) {
            return Optional.fromNullable(input.getDecimalField(..));
        }
    }
);

อีกตัวอย่างหนึ่ง สมมติว่าฉันมีฟังก์ชันบางอย่างอยู่แล้ว ซึ่งแยกค่าทศนิยมออกจากเอนทิตี และอาจส่งคืนค่าว่าง:

Function<Entity, Decimal> extractDecimal = .. // extracts decimal value or null
Optional<BigDecimal> maybeDecimalValue = Optionals.flatMap(
    findEntity(),
    Optionals.lift(extractDecimal)
);

และสุดท้ายแต่ไม่ท้ายสุด - กรณีการใช้งานของคุณเป็นตัวอย่าง:

Optional<Publisher> maybePublisher = Optionals.flatMap(findBook(id), Optionals.lift(Book.getPublisher));

// Assuming that getPublishedBooks may return null..
Optional<List<Book>> maybePublishedBooks = Optionals.flatMap(maybePublisher, Optionals.lift(Publisher.getPublishedBooks));

// ..or simpler, in case when getPublishedBooks never returns null
Optional<List<Book>> maybePublishedBooks2 = maybePublisher.transform(Publisher.getPublishedBooks);

// as a one-liner:
Optionals.flatMap(maybePublisher, Optionals.lift(Publisher.getPublishedBooks)).transform(Publisher.getPublishedBooks);
person Community    schedule 25.06.2014

คุณอาจทราบเรื่องนี้แล้ว แต่คุณสามารถเพิ่ม .or(Optional.absent) หลังการแปลงทุกครั้งที่ส่งคืน Optional (ในกรณีของคุณหลัง .transform(Book.getPublisher) โดยลด Optional<Optional<T>> เป็น Optional<T>:

Optional<List<Book>> publishedBooks = findBook(id).transform(Book.getPublisher).
        or(Optional.absent()).transform(Publisher.getPublishedBooks);

ขออภัย ไม่สามารถอนุมานประเภทของ Optional.absent ที่นี่ได้ ดังนั้นโค้ดจึงกลายเป็น:

Optional<List<Book>> publishedBooks = book.transform(Book.getPublisher).
        or(Optional.<Publisher> absent()).transform(Publisher.getPublishedBoooks);

ไม่สะดวกเกินไป แต่ดูเหมือนจะไม่มีวิธีอื่น

person siledh    schedule 26.10.2013