복붙노트

[SCALA] Groovy의 안전 참조 연산자 최고의 스칼라 모방 (?).?

SCALA

Groovy의 안전 참조 연산자 최고의 스칼라 모방 (?).?

나는 Groovy의 안전 참조 연산자의 가장 스칼라 모방 (?.), 또는 적어도 일부 가까운 대안이 무엇인지 알고 싶습니다?

나는 다니엘 스피 웍 블로그에 그것을 간략하게 설명했지만, 유래에 개방하고 싶습니다 ...

모든 사람의 시간을 위해, 여기에 다니엘의 초기 대응, 내 카운터 및 그의 제 2 응답은 다음과 같습니다

내 두번째 질문 :

company?.getContactPerson?.getContactDetails?.getAddress?.getCity
var city = company.map(_.getContactPerson.map(_.getContactDetails.map(_.getAddress.map(_.getCity))))

그의 두번째 응답 :

 for {
   c < - company
   person <- c.getContactPerson   
   details <- person.getContactDetails
   address <- details.getAddress 
  } yield address.getCity

추신. 다니엘은 답변으로 자신의 블로그에 자신의 원래의 답변을 게시하는 경우, 나는 시스템을 위해 그들을 제거하기 위해 질문을 수정합니다.

해결법

  1. ==============================

    1.이것은 어떤가요?

    이것은 어떤가요?

    def ?[A](block: => A) =
      try { block } catch {
        case e: NullPointerException if e.getStackTrace()(2).getMethodName == "$qmark" => null
        case e => throw e
      }
    

    이 작은 조각을 사용하여, 안전하게 역 참조 할 수 있고 코드 자체는 매우 간결하다 :

    val a = ?(b.c.d.e)
    

    == 널 B 또는 b.c 또는 b.c.d 또는 b.c.d.e이면 NULL, 그렇지 않으면 == b.c.d.e

    난 당신이 전화 별 이름과 implicits 등의 시설을 갖추고 스칼라 같은 언어를 사용하는 경우, 안전 참조 연산자의 값이 감소 생각합니다.

    추신 : NullPointerException이이 때 사건을 처리하는 아래의 설명 중 하나의 관점에서 조금 위의 코드를 수정 실제로 호출 된 함수 내부에서 발생합니다.

    BTW, 나는 아래의 기능이 스칼라를 작성하는 더 관용적 방법 사용하여 생각 :

    def ??[A](block: => A): Option[A] = ?(block) match {
        case a: A => Some(a)
        case _ => None
      }
    

    그래서 같은 :

    ??(a.b.c.d) match {
        case Some(result) => // do more things with result
        case None => // handle "null" case
      }
    
  2. ==============================

    2.여기에서 고려해야 할 두 가지가있다.

    여기에서 고려해야 할 두 가지가있다.

    첫째, "아무것도"의 문제가있다. 체인의 일부가 아무것도 반환하지 않을 때 어떻게 일을 체인합니까? 대답은 옵션과 함축을 위해 사용하고있다. 예를 들면 :

    scala> case class Address(city: Option[String] = None, street: Option[String] = None, number: Option[Int] = None)
    defined class Address
    
    scala> case class Contact(name: String, phone: Option[String] = None, address: Option[Address] = None)
    defined class Contact
    
    scala> case class ContactDetails(phone: Option[String] = None, address: Option[Address] = None)
    defined class ContactDetails
    
    scala> case class Contact(phone: Option[String] = None, address: Option[Address] = None)
    defined class Contact
    
    scala> case class Person(name: String, contactDetails: Option[Contact] = None)
    defined class Person
    
    scala> case class Company(name: String, contactPerson: Option[Person] = None)
    defined class Company
    
    scala> val p1 = Company("ABC", Some(Person("Dean", Some(Contact(None, Some(Address(city = Some("New England"))))))))
    p1: Company = Company(ABC,Some(Person(Dean,Some(Contact(None,Some(Address(Some(New England),None,None)))))))
    
    scala> val p2 = Company("Finnicky", Some(Person("Gimli", None)))
    p2: Company = Company(Finnicky,Some(Person(Gimli,None)))
    
    scala> for(company <- List(p1, p2);
         | contactPerson <- company.contactPerson;
         | contactDetails <- contactPerson.contactDetails;
         | address <- contactDetails.address;
         | city <- address.city) yield city
    res28: List[String] = List(New England)
    

    이것은 당신이 스칼라 뭔가 여부를 반환 할 수 있습니다 코드를 작성 해야하는 방법이다.

    두 번째 문제는 물론, 때로는 적절한 convertion을 할 수있는 소스 코드에 액세스하지 못할 수 있다는 것입니다. 이 때, 내재이 이용 될 수 없다면, 헤드 될 추가적인 구문 오버 헤드가있다. 나는 약 아래에 얘기하자있는 스칼라 2.8에 그런 일이있다 - 나는 내가 "toOption"기능을 사용하는 아래의 예를 줄 것이다.

    scala> def toOption[T](t: T): Option[T] = if (t == null) None else Some(t)
    toOption: [T](t: T)Option[T]
    
    scala> case class Address(city: String = null, street: String = null, number: Int = 0)
    defined class Address
    
    scala> case class Contact(phone: String = null, address: Address = null)
    defined class Contact
    
    scala> case class Person(name: String, contactDetails: Contact = null)
    defined class Person
    
    scala> case class Company(name: String, contactPerson: Person = null)
    defined class Company
    
    scala> val p1 = Company("ABC", Person("Dean", Contact(null, Address(city = "New England"))))
    p1: Company = Company(ABC,Person(Dean,Contact(null,Address(New England,null,0))))
    
    scala> val p2 = Company("Finnicky", Person("Gimli"))
    p2: Company = Company(Finnicky,Person(Gimli,null))
    
    scala> for(company <- List(p1, p2);
         | contactPerson <- toOption(company.contactPerson);
         | contactDetails <- toOption(contactPerson.contactDetails);
         | address <- toOption(contactDetails.address);
         | city <- toOption(address.city)) yield city
    res30: List[String] = List(New England)
    

    당신은 함수의 이름을 지정 상당히 창조적 될 수 있음을 기억하십시오. 그래서, 대신 "toOption", 나는 이름을 수도 그것은? ""내가 좋아하는 일을 써서이 경우 "? (address.city)".

    난 그냥 옵션 (뭔가를) 쓸 수 있도록 저를 생각 나게하기위한 nuttycom 덕분에, 스칼라 2.8에 개체 옵션에 옵션 공장이있다. 실제로, 당신은 "옵션"위에서 "toOption"를 대체 할 수 있습니다. 당신은? 사용을 선호한다면, 당신은 단지 이름 바꾸기로 가져 오기를 사용할 수 있습니다.

  3. ==============================

    3.이 암시 적 변환을 만듭니다.

    이 암시 적 변환을 만듭니다.

    class SafeDereference[A](obj: A) {
      def ?[B >: Null](function: A => B): B = if (obj == null) null else function(obj)
    }
    
    implicit def safeDereference[A](obj: A) = new SafeDereference(obj)
    

    사용법은 아주 멋져요 같은 아니지만, 그것은 끔찍한 아니다.

    case class Address(state: String)
    case class Person(first: String, last: String, address: Address)
    val me = Person("Craig", "Motlin", null)
    
    scala> me ? (_.first)
    res1: String = Craig
    
    scala> me ? (_.address)
    res2: Address = null
    
    scala> me ? (_.address) ? (_.state)
    res3: String = null
    
  4. ==============================

    4.단항 바인드 scala.Option 유형 (flatMap /지도). 지원은를위한 함축에 의해 제공됩니다. 당신이 원하는 경우 Scalaz는 실용적 펑터 스타일을 제공합니다.

    단항 바인드 scala.Option 유형 (flatMap /지도). 지원은를위한 함축에 의해 제공됩니다. 당신이 원하는 경우 Scalaz는 실용적 펑터 스타일을 제공합니다.

    이는 해당하지만, 여러 가지 이유로 Groovy의 연산자보다 훨씬 더 나은 솔루션이 아닙니다.

  5. ==============================

    5.광산하지만 동료 년대되지 않음

    광산하지만 동료 년대되지 않음

    class NullCoalescer[T <: AnyRef](target: T) {
        def ?? (other: T) =
            if(target == null) other else target
    }
    object NullCoalescerConversions {
        implicit def toNullCoalescer[T <: AnyRef](target: T): NullCoalescer[T] = 
            new NullCoalescer(target)
    }
    
    println (System.getProperty("maybe") ?? "definitely")
    
  6. ==============================

    6.관용적 스칼라가 널 포인터를 사용하지 않기 때문에 다니엘 C. 소브랄의 대답을 수행하려면 선호하는 이유 옵션입니다. 당신은 널 (NULL) 참조 대신 옵션을 반환하는 코드를 재 작성 할 수 있습니다. 각 단계에 대한 새로운 변수 이름을 필요로하지 않기 때문에 체인 flatMaps는,를위한 지능형 청소기보다 있습니다. 모든 값이 (그루비의 예에서와 같이) 선택 사항 인 경우, 스칼라의 접근 방식은 다음과 같을 것이다 :

    관용적 스칼라가 널 포인터를 사용하지 않기 때문에 다니엘 C. 소브랄의 대답을 수행하려면 선호하는 이유 옵션입니다. 당신은 널 (NULL) 참조 대신 옵션을 반환하는 코드를 재 작성 할 수 있습니다. 각 단계에 대한 새로운 변수 이름을 필요로하지 않기 때문에 체인 flatMaps는,를위한 지능형 청소기보다 있습니다. 모든 값이 (그루비의 예에서와 같이) 선택 사항 인 경우, 스칼라의 접근 방식은 다음과 같을 것이다 :

    (company flatMap _.getContactPerson
             flatMap _.getContactDetails
             flatMap _.getAddress
             flatMap _.getCity) match {
      case Some(city) => ...
      case None       => ...
    }
    

    자바 상호 운용성에 대한 널 (NULL) 값을 사용해야하는 경우, 여기 NPE-논쟁 또는 너무 많은 혼란없이 당신에게 안전을 제공하는 방법입니다 :

    sealed trait Nullable[+A] {
      def apply[B](f:A=>B): Nullable[B]
    }
    
    def ?[A](a: A) = a match {
      case null => NullRef
      case _    => Ref(a)
    }
    
    case class Ref[A](value: A) extends Nullable[A] {
      def apply[B](f:A=>B) = ?(f(value))
    }
    
    object NullRef extends Nullable[Nothing] {
      def apply[B](f: Nothing=>B): Nullable[B] = NullRef
    }
    
    
    ?(company)(_.getContactPerson)(_.getContactDetails)(_.getAddress)(_.getCity) match {
      case Ref(city) => ...
      case _         => ...
    }
    

    이 원하는 경우 전체 옵션 스타일의 모나드으로 확장이 용이해야한다.

  7. ==============================

    7.이 주석으로 끔찍한 보일 것 때문에, 여기 월터의 코드의 주석 버전입니다 :

    이 주석으로 끔찍한 보일 것 때문에, 여기 월터의 코드의 주석 버전입니다 :

    /**
     * Safe dereference operator. E.g. ?(a.b.c.null.dd)
     */
    def ?[A](block: => A) = {
      try { block } catch {
        // checks to see if the 3rd to last method called in the stack, is the ?() function, 
        // which means the null pointer exception was actually due to a null object, 
        // otherwise the ?() function would be further down the stack.
        case e: NullPointerException if e.getStackTrace()(2).getMethodName == "$qmark" => {null}
        // for any other NullPointerException, or otherwise, re-throw the exception.
        case e => throw e
      }
    

    그리고 사양은 통과하는 :

    case class Company(employee:Employee)
    case class Employee(address:Address){
      def lookupAddressFromDb:Address = throw new NullPointerException("db error")
    }
    case class Address(city:String)
    
    "NullSafe operater" should {
      "return the leaf value when working with non-null tree" in {
        val company = Company(Employee(Address("Auckland")))
        val result = ?( company.employee.address.city )
        result mustEq "Auckland"
      }
      "return null when working with a null element at some point in the tree" in {
        val company = Company(null)
        val result = ?( company.employee.address.city )
        result must beNull
      }
      "re-throw the NPE when working with a method which actually throws a NullPointerException" in {
        val company = Company(Employee(Address("Auckland")))
        ?( company.employee.lookupAddressFromDb.city ) aka "the null-safe lookup method" must throwA[NullPointerException]
      }   
    }
    
  8. ==============================

    8.나는 내가하고 있었다 중첩 된 경기의 폭포보다 더 빠르게 지점에 도달 --- 함축에 대한 다니엘 C. 소브랄의 사용을 좋아했다. 중간 더미 변수 (너무 많이 입력)이 여전히 있기 때문에 그러나, 그것은 여전히 ​​매우 편리 아니다.

    나는 내가하고 있었다 중첩 된 경기의 폭포보다 더 빠르게 지점에 도달 --- 함축에 대한 다니엘 C. 소브랄의 사용을 좋아했다. 중간 더미 변수 (너무 많이 입력)이 여전히 있기 때문에 그러나, 그것은 여전히 ​​매우 편리 아니다.

    그냥 뭔가를 얻을 나에게 당신이 그것을 얻을 수없는 경우에 옵션을 부여하려고??? 우리 사이에 오는 것에 대해 생각하지 않아도 우리는 .B이 .c .D 같은 것을 원한다.

    컨텍스트를 들어, 내가 가진 가정

    case class Inner(z: Option[Int])
    case class Outer(y: Option[Inner])
    val x = Some(Outer(Some(Inner(Some(123)))))
    

    나는 풀고 싶어. 이해의는 다음과 같이 갈 것

    for (tmp1 <- x; tmp2 <- tmp1.y; tmp3 <- tmp2.z) yield tmp3
    

    일부 (123)에 어떤 결과. 문제는 너무 많은 임시 변수 (그리고 사실은 부분적으로 뒤로 읽기 있다는)입니다.

    나는 쉽게 같이, flatMap 함께 할 찾을

    x.flatMap(_.y.flatMap(_.z))
    

    또는

    x flatMap {_.y flatMap {_.z}}
    

    이는 또한 일부 (123)가 발생합니다.

    하나는 상세 줄이려고하고 원하는 사용할 수 있습니다? 효과적으로 옵션이 방법을 입력 제공함으로써 기호? 그 flatMap과 같은 일을한다. 옵션 서브 클래스에서 밀봉,하지만 우리는 암시 적 변환과 새로운 방법을 시뮬레이션 할 수 있습니다.

    case class OptionWrapper[A](opt: Option[A]) {
      def ?[B](f: (A) => Option[B]): Option[B] = opt.flatMap(f)
    }
    implicit def toOptionWrapper[T](opt: Option[T]) = OptionWrapper(opt)
    implicit def fromOptionWrapper[T](wrap: OptionWrapper[T]) = wrap.opt
    

    그리고

    x ? {_.y ? {_.z}}
    

    일부 (123 얻을 수 있습니다. 아직 중첩 된 괄호하고 제대로해야한다는 밑줄이 있기 때문에 완벽하지,하지만 더 나은 내가 본 어떤 대안보다 더합니다.

  9. from https://stackoverflow.com/questions/1163393/best-scala-imitation-of-groovys-safe-dereference-operator by cc-by-sa and MIT license