试图理解这个 ruby 语法

Trying to understand this ruby syntax

我是 ruby 的新手,很多事情让我感到困惑。我相信这个特定的来自 Sorbet,它是某种类型检查库?不确定。这段特定的代码就在方法声明之前

sig { params(order: T::Hash[
          String, T.any(
            T.nilable(String), T::Hash[
              String, T.any(
                Integer, T.nilable(String)
              )
            ]
          )
        ]).returns(Types::Order) }

def self.build_prescription(prescription) 
# method implementation

order 对象是来自 REST API 的 json 对象。谁能解释一下这个嵌套是怎么回事。

这确实是一个冰糕签名。您可以在 official documentation

中阅读更多关于 Sorbet 的信息

本例中的签名本身只是描述参数的形状和 build_prescription 方法的 return 类型。

稍微重新格式化以便更容易注释:

sig { # A ruby method added by Sorbet to indicate that the signature is starting.
  params( # Start to declare the parameters that the method takes
    order: T::Hash[ # First param. In this case, it's a Hash...
      String, # ... where they key is a String ...
      T.any( # ... and the value can be either: ...
        T.nilable(String), ... # a nilable String
        T::Hash[ # or a Hash ...
          String, # ... where the key is a String...
          T.any(Integer, T.nilable(String)) # ... and the value is either an Integer or nilable string String
        ]
      )
    ])
  .returns(Types::Order) # Declaration of what the method returns
}
def self.build_prescription(prescription) # the regular Ruby method declaration

不过请注意,这将无法通过 Sorbet 验证,因为签名将参数声明为 order,而方法将其声明为 prescription。这两个名字应该匹配。

有一种方法可以重写此签名,使其更适合 read/understand

sig do
  params(
    prescription: T::Hash[
      String,
      T.nilable(
        T.any(
          String,
          T::Hash[String, T.nilable(T.any(Integer, String)]
        )
      )
  ])
  .returns(Types::Order) # Declaration of what the method returns
}
def self.build_prescription(prescription)

请注意,我将 T.nilable 移出一级,因为 T.any(Integer, T.nilable(String)) 的意思与 T.nilable(T.any(Integer, String)) 相同,但对于 reader 来说更明显该值可以是 nil

既然你具体问的是语法,而不是语义,我会回答你关于语法的问题。

您在此处看到的内容称为消息发送。 (在 Java 或 C# 等其他编程语言中,它可能被称为 方法调用 。)更准确地说,它是使用 隐式接收器发送的消息.

消息总是发送给特定的接收者(就像现实世界中的消息一样)。消息发送的一般语法如下所示:

foo.bar(baz, quux: 23) {|garple| glorp(garple) }

这里,

  • foo接收者,即接收消息的对象。请注意 foo 当然可以是任意 Ruby 表达式,例如(2 + 3).to_s.
  • bar 消息选择器 ,或者简称为 消息 。它告诉接收者对象要做什么。
  • 消息选择器后的括号包含参数列表。在这里,我们有一个 位置参数 ,它是表达式 baz(它可以是局部变量或另一条消息发送,稍后会详细介绍),还有一个 关键字参数 是关键字 quux,值为 23。 (同样,该值可以是任意 Ruby 表达式。)注意:除了位置参数和关键字参数之外,还有一些其他类型的参数,即 splat arguments 和一个可选的 显式块参数 .
  • 参数列表之后是​​文字块参数。 Ruby 中发送的每条消息都可以有一个文字块参数……这取决于调用的方法来忽略它、使用它或做任何它想做的事情。
  • 块是一段轻量级的可执行代码,因此,就像方法一样,它有一个参数列表和一个主体 .参数列表由 | 管道符号分隔 - 在这种情况下,只有一个名为 garple 的位置参数,但它可以具有方法可以具有的所有相同类型的参数,加上 块局部变量。当然,正文可以包含任意 Ruby 表达式。

现在,重要的是其中很多元素都是可选的:

  • 你可以省略括号:foo.bar(baz, quux: 23)foo.bar baz, quux: 23相同,这也意味着foo.bar()foo.bar相同。
  • 您可以省略显式接收器,在这种情况下,隐式接收器self,即self.foo(bar, baz: 23)与[=29=相同],这当然与 foo bar, baz: 23.
  • 相同
  • 如果将两者放在一起,那就意味着例如self.foo() 与我之前提到的 foo 相同:如果你只是 foo 本身而没有上下文,你实际上不知道它是局部变量还是消息发送。只有当您看到接收者或参数(或两者)时,您才能确定它是消息发送,并且只有当您在同一范围内看到赋值时才能确定它是变量。如果你看不到这些东西,那也可能是。
  • 您可以省略不使用的块参数列表,也可以完全省略块。

那么让我们来分析一下您在这里看到的语法。第一层是

sig {
  # stuff
}

我们知道这是消息发送而不是局部变量,因为有一个文字块参数,变量不带参数,只有消息发送才带参数。

因此,这是将消息 sig 发送到隐式接收器 self(在模块定义主体中只是模块本身),并将文字块作为唯一参数传递。

该块没有参数列表,只有一个主体。正文内容为

params(
  # stuff
).returns(Types::Order)

同样,我们知道 params 是一条消息发送,因为它需要一个参数。所以,这是将消息 params 发送到隐式接收者 self(这里仍然是模块本身,因为块词法捕获 self,尽管这是语言语义的一部分,你严格询问语法)。它还将一个参数传递给消息发送,我们将在稍后查看。

然后我们发送另一条消息。我们怎么知道的?好吧,它需要一个参数并有一个接收者。我们将消息 returns 发送到 params 消息发送返回的对象,将表达式 Types::Order 作为唯一的位置参数传递。

Types::Order,又是一个常量引用Types),命名空间解析运算符 (::),后跟另一个常量引用 (Order).

接下来,让我们看一下params的参数:

params(order: T::Hash[
  # stuff
])

这里我们有一个关键字参数order,它的值是表达式T::Hash[ … ]T::Hash 当然又是常量引用、名称空间解析运算符和另一个常量引用。

那么,什么是[]?实际上,那只是另一条消息发送。 Ruby 有 语法糖 用于有限的、固定的特殊消息列表。一些例子:

  • foo.call(bar) 等同于 foo.(bar).
  • foo.+(bar) 等同于 foo + bar。 (与 ***/-<<>>|&======~!=!==!~,还有一些我可能忘记了。)
  • foo.+@ 等同于 +foo。 (与 -@ 类似。)
  • foo.! 等同于 !foo。 (和 ~ 类似。)
  • self.`("Hello")`Hello`一样,有点晦涩
  • foo.[](bar, baz) 等同于 foo[bar, baz].
  • foo.[]=(bar, baz, quux) 等同于 foo[bar, baz] = quux.

因此,这只是将消息[]发送到通过取消引用常量T获得的对象的名称空间中取消引用常量Hash的结果,并传递两个位置参数。

第一个位置参数是String,它又是一个常量引用。第二个位置参数是表达式 T.any( … ),它是常量 T 的常量引用,然后将消息 any 发送到该常量引用的对象,传递两个位置参数。

第一个参数是表达式T.nilable(String),它解引用常量T,发送消息nilable到解引用常量T的结果,传递单个位置参数,这是取消引用常量 String.

的结果

第二个参数是表达式T::Hash[ … ] ……我要到此为止了,因为这里真的没有什么要解释的了。有常量、消息发送和参数,我们之前已经多次看到所有这些。

总而言之,关于您关于语法的问题:我们在这里看到的语法元素是

  • 消息发送
  • 参数
  • 常量
  • 名称空间解析运算符(实际上并不是一个单独的语法元素,而只是许多运算符中的一个)
  • 和块文字