【问题标题】:Scala string interpolation with a pass-by-name string使用按名称传递的字符串进行 Scala 字符串插值
【发布时间】:2021-01-11 00:49:11
【问题描述】:

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

例如,

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)
}

这不会编译,因为当编译器想要进行插值时,ab 和肯定不是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 pass-by-name;不走运。

我想我想从 Python 世界中得到这样的东西:

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

有什么建议吗?

【问题讨论】:

  • 好吧,你完全可以这样做,格式化字符串而不是使用插值器。
  • 我考虑过格式字符串(应该说),但被占位符可能更改的顺序难住了。 @tomer-shetah 提供了一种我不熟悉的方法。 stackoverflow.com/questions/26824690/… 提供另一个。

标签: string scala string-interpolation


【解决方案1】:

正如@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 运行。如果您采用这种方法,并且您的场景实际比给定示例更复杂,则可以在此字符串中使用命名参数。更多信息可以在Named placeholders in string formattingHow to format message with argument names instead of numbers? 以及更多文章中阅读。

【讨论】:

  • 我将采用这种基本方法,但使用位置符号而不是 Tomer 建议的两遍技巧。即msg = "%3$d" is not between %1$d and %2$d",然后是msg.format(lo, hi, v)。这允许在必要时重用变量,并且似乎更容易记录。谢谢指点!见Scastie
【解决方案2】:

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

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)

【讨论】:

    【解决方案3】:

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

    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)
      }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多