What is the best way to return more than 2 differe

2019-09-12 17:22发布

问题:

It's been while Since I've started working on scala and I am wondering what kind of variable type is the best when I create a method which requires to return multiple data.

let's say If I have to make a method to get user info and it'll be called from many places.

  def getUserParam(userId: String):Map[String,Any] = {
    //do something
    Map(
      "isExist" -> true,
      "userDataA" -> "String",
      "userDataB" -> 1 // int
    )
  }

in this case, the result type is Map[String,Any] and since each param would be recognized as Any, You cannot pass the value to some other method requiring something spesifically.

def doSomething(foo: String){}
val foo = getUserParam("bar")
doSomething(foo("userDataA")) // type mismatch error

If I use Tuple, I can avoid that error, but I don't think it is easy to guess what each indexed number contains.

and of course there is a choice to use Case Class but once I use case class as a return type, I need to import the case class where ever I call the method.

What I want to ask is what is the best way to make a method returning more than 2 different variable type values.

回答1:

Here are three options. Even though you might like the third option (using anonymous class) it's actually my least favorite. As you can see, it requires you to enable reflective calls (otherwise it throws a compilation warning). Scala will use reflection to achieve this which is not that great.

Personally, if there are only 2 values I use tuple. If there are more than two I will use a case class since it greatly improves code readability. The anonymous class option I knew it existed for a while, but I never used that it my code.

import java.util.Date

def returnTwoUsingTuple: (Date, String) = {
    val date = new Date()
    val str = "Hello world"
    (date,str)
}

val tupleVer = returnTwoUsingTuple
println(tupleVer._1)
println(tupleVer._2)


case class Reply(date: Date, str: String)
def returnTwoUsingCaseClass: Reply = {
    val date = new Date()
    val str = "Hello world"
    Reply(date,str)
}

val caseClassVer = returnTwoUsingCaseClass
println(caseClassVer.date)
println(caseClassVer.str)


import scala.language.reflectiveCalls
def returnTwoUsingAnonymousClass = {
    val date = new Date()
    val str = "Hello world"
    new {
        val getDate = date
        val getStr = str
    }
}

val anonClassVer = returnTwoUsingAnonymousClass
println(anonClassVer.getDate)
println(anonClassVer.getStr)


回答2:

Sinse your logic with Map[String,Any] is more like for each key I have one of .. not for each key I have both ... more effective use in this case would be Either or even more effectively - scalaz.\/

scalaz.\/

import scalaz._
import scalaz.syntax.either._

def getUserParam(userId: String): Map[String, String \/ Int \/ Boolean] = {
  //do something
  Map(
    "isExist" -> true.right,
    "userDataA" -> "String".left.left,
    "userDataB" -> 1.right.left
  )
}

String \/ Int \/ Boolean is left-associatited to (String \/ Int) \/ Boolean

now you have

def doSomething(foo: String){}

unluckily it's the most complex case, if for example you had

def doSomethingB(foo: Boolean){}

you could've just

foo("userDataA").foreach(doSomethingB)

since the right value considered as correct so for String which is left to the left you could write

foo("userdata").swap.foreach(_.swap.foreach(doSomething))

Closed Family

Or you could craft you own simple type for large number of alternatives like

sealed trait Either3[+A, +B, +C] {
  def ifFirst[T](action: A => T): Option[T] = None
  def ifSecond[T](action: B => T): Option[T] = None
  def ifThird[T](action: C => T): Option[T] = None
}

case class First[A](x: A) extends Either3[A, Nothing, Nothing] {
  override def ifFirst[T](action: A => T): Option[T] = Some(action(x))
}

case class Second[A](x: A) extends Either3[Nothing, A, Nothing] {
  override def ifSecond[T](action: A => T): Option[T] = Some(action(x))
}

case class Third[A](x: A) extends Either3[Nothing, Nothing, A] {
  override def ifThird[T](action: A => T): Option[T] = Some(action(x))
}

now having

def getUserParam3(userId: String): Map[String, Either3[Boolean, String, Int]] = {
  //do something
  Map(
    "isExist" -> First(true),
    "userDataA" -> Second("String"),
    "userDataB" -> Third(1)
  )
}

val foo3 = getUserParam3("bar")

you can use your values as

foo3("userdata").ifSecond(doSomething)


标签: scala