使用传递名称字符串的 Scala 字符串插值

Scala string interpolation with a pass-by-name string

我想将默认字符串传递给函数,并在函数中而不是在调用站点对其进行“字符串插值”。

例如,

def isBetween(a:Int, b:Int, 
              msg: String = s"${v} is not between ${a} and ${b}."
             )(v:Int):Either[String, Boolean] = {
  if (a <= v && v <= b) Right(true) else Left(msg)
}

这不会编译,因为 a 中的 none、b 肯定不在编译器想要进行插值时的范围内 v

目标是提供默认错误字符串,但允许用户在必要时更改它。例如:

val normalBetween = isBetween(0, 100)
val customBetween = isBetween(0, 100, s"Doofus! it's gotta be ${a} <= v <= ${b} but v is ${v}!")

val result1 = normalBetween(101) // Left("101 is not between 0 and 100.")
val result2 = customBetween(101) // Left("Doofus! it's gotta be 0 <= v <= 100 but v is 101!")

我试过让 msg 传递名字;运气不好。

我想我想要来自 Python 世界的这样的东西:

name = 'world'
program ='python'
print('Hello {name}!This is{program}.'.format(name=name, program=program))

有什么建议吗?

不可能引用在相同(或未来)参数列表中声明的变量,但是您可以引用在先前参数列表中声明的变量,如下所示:

def isBetween(
  a:Int, b:Int
)(v: Int)(
  msg: String = s"${v} is not between ${a} and ${b}."
): Either[String, Boolean] = {
  if (a <= v && v <= b) Right(true) else Left(msg)
}

如果您希望能够为呼叫者提供自定义模板字符串的能力,您可以按如下方式进行:

def isBetween(
  a:Int, b:Int
)(v: Int)(
  msg: (Int, Int, Int) => String = 
    (pA, pB, pV) => s"${pV} is not between ${pA} and ${pB}."
): Either[String, Boolean] = {
  if (a <= v && v <= b) Right(true) else Left(msg(a, b, v)
}

用法示例:

val customMsg = (a: Int, b: Int, v: Int) => s"Sorry but $v is not between $a and $b!"
isBetween(5, 7)(6)(customMsg)

如果您想为呼叫者提供一个完全“自定义”的 isBetween,那么您可以通过将消息放在第一个参数组中来实现:

def isBetween(
  msg: (Int, Int, Int) => String = 
    (pA, pB, pV) => s"${pV} is not between ${pA} and ${pB}."
)(
  a:Int, b:Int
)(v: Int): Either[String, Boolean] = {
  if (a <= v && v <= b) Right(true) else Left(msg(a, b, v))
}

val customMsg = (a: Int, b: Int, v: Int) => s"Sorry but $v is not between $a and $b!"
val customMsgIsBetween = isBetween(customMsg) _

customMsgIsBetween(5, 7)(6)

值得记住的是,我们可以为此使用标记值。虽然 null 在 Scala 中不鼓励传递数据,但它仍然是允许的,并且对于临时本地使用,只要我们不让它超出范围,它就相当无害。

def isBetween(a: Int, b: Int, msgArg: String = null)(v: Int): Either[String, Boolean] = {
  val msg = if (msgArg == null) {
    s"${v} is not between ${a} and ${b}.";
  } else {
    msgArg
  }
  if (a <= v && v <= b) {
    Right(true)
  } else {
    Left(msg)
  }
}

正如@LuisMiguelMejíaSuárez 在评论中建议的那样,您可以只使用 java 的字符串格式:

def isBetween(a: Int, b: Int, msg: String = "%%d is not between %d and %d.")(v: Int): Either[String, Boolean] = {
  if (a <= v && v <= b) Right(true) else Left(msg.format(a, b).format(v))
}

def normalBetween: Int => Either[String, Boolean] = isBetween(0, 100)
def customBetween: Int => Either[String, Boolean] = isBetween(0, 100, "Doofus! it's gotta be %d <= v <= %d but v is %%d!")

val result1 = normalBetween(101) // Left("101 is not between 0 and 100.")
val result2 = customBetween(101) // Left("Doofus! it's gotta be 0 <= v <= 100 but v is 101!")
println(result1)
println(result2)

结果如预期。 Scastie. If you are taking this approach, and your scenario inn reality is more complex than the given example, you can use named parameters in this string. More can be read about it at Named placeholders in string formatting, How to format message with argument names instead of numbers? 中的代码 运行,以及更多文章。