F# 模式匹配对于字符串类型为什么会匹配一个 ReadOnlySpan<char>?

huangapple go评论59阅读模式
英文:

Why Does F# Pattern Matching for a String Type Match a ReadOnlySpan<char>?

问题

以下是代码中的翻译部分:

让我们假设我想要查找特定的 `TryParse` 方法用于给定类型即从字符串解析的方法令我惊讶的是以下代码返回 2 个方法一个以 `string` 作为第一个参数还有一个以 `ReadOnlySpan&lt;char&gt;` 作为第一个参数

我包括这个不那么简单的示例因为事实证明在 F#仅仅即时创建 `ReadOnlySpan` 有点困难

结果列表中的第二个元素对我来说是意外的如何更改 `[1]` 处的模式匹配以排除它

(*
val it: (string * string array) list =
  [(&quot;TryParse&quot;, [|&quot;System.String&quot;; &quot;System.UInt64&amp;&quot;|]);
   (&quot;TryParse&quot;,
    [|&quot;System.ReadOnlySpan`1[[System.Char, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]&quot;;
      &quot;System.UInt64&amp;&quot;|])]
*)

请注意,代码中的 HTML 实体字符(如 &quot;&gt;)已保留在翻译中,以保持原始代码的格式。

英文:

Let's say I wanted to find a particular TryParse method for a given type, namely the one that parses from a string. To my surprise, the following code returns 2 methods: one with a string as first parameter, but also one with a ReadOnlySpan&lt;char&gt; as first parameter.

I include this not-so-minimal example because it turns out to be somewhat hard to 'just' create ReadOnlySpans in F# on the fly.

The second element in the result list is unexpected for me. How would I change the pattern matching at [1] to exclude it?

typeof&lt;uint64&gt;.GetMember(&quot;TryParse&quot;)
|&gt; Seq.cast&lt;MethodBase&gt;
|&gt; Seq.choose (fun m -&gt;
    let parameterTypes =
        m.GetParameters()
        |&gt; Array.map (fun p -&gt; (p.ParameterType, p.Attributes))
    // [1]
    match parameterTypes with
    | [| (string, ParameterAttributes.None); (uint64, ParameterAttributes.Out) |] -&gt; Some m
    | _ -&gt; None)
|&gt; Seq.toList
|&gt; List.map (fun m -&gt; (m.Name, m.GetParameters() |&gt; Array.map (fun p -&gt; p.ParameterType.FullName)))

(*
val it: (string * string array) list =
  [(&quot;TryParse&quot;, [|&quot;System.String&quot;; &quot;System.UInt64&amp;&quot;|]);
   (&quot;TryParse&quot;,
    [|&quot;System.ReadOnlySpan`1[[System.Char, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]&quot;;
      &quot;System.UInt64&amp;&quot;|])]
*)

答案1

得分: 1

你的代码发现了TryParse的两个版本,因为你声明了变量stringuint64,它们与任何值匹配。要修复这个问题,你需要与实际的类型进行比较:

typeof<uint64>.GetMember("TryParse")
|> Seq.cast<MethodBase>
|> Seq.choose (fun m ->
    let parameterTypes =
        m.GetParameters()
        |> Array.map (fun p -> (p.ParameterType, p.Attributes))
    if parameterTypes = [| (typeof<string>, ParameterAttributes.None); (typeof<uint64>.MakeByRefType(), ParameterAttributes.Out) |] then
        Some m
    else
        None)
|> Seq.toList
|> List.map (fun m -> (m.Name, m.GetParameters() |> Array.map (fun p -> p.ParameterType.FullName)))

请注意,你必须使用typeof来在运行时获取类型,然后调用MakeByRefType

英文:

Your code finds both versions of TryParse because you've declared variables named string and uint64, which match on any value. To fix this, you need to compare against the actual types:

typeof&lt;uint64&gt;.GetMember(&quot;TryParse&quot;)
|&gt; Seq.cast&lt;MethodBase&gt;
|&gt; Seq.choose (fun m -&gt;
    let parameterTypes =
        m.GetParameters()
        |&gt; Array.map (fun p -&gt; (p.ParameterType, p.Attributes))
    if parameterTypes = [| (typeof&lt;string&gt;, ParameterAttributes.None); (typeof&lt;uint64&gt;.MakeByRefType(), ParameterAttributes.Out) |] then
        Some m
    else
        None)
|&gt; Seq.toList
|&gt; List.map (fun m -&gt; (m.Name, m.GetParameters() |&gt; Array.map (fun p -&gt; p.ParameterType.FullName)))

Note that you have to use typeof to get a type at runtime, and then call MakeByRefType.

答案2

得分: 1

当你在模式中使用标识符时,你并不是在测试与现有值(或类型)的相等性。相反,你正在定义一个始终匹配的模式,并在模式后面的作用域中赋予匹配的值所给定的名称。

要进行相等性测试,你需要使用 when 卫述子代替:

    match parameterTypes with
    | [| (arg1, ParameterAttributes.None); (arg2, ParameterAttributes.Out) |]
        when arg1 = typeof&lt;string&gt; &amp;&amp; arg2 = typeof&lt;uint64&gt;.MakeByRefType()
        -&gt; Some m
    | _ -&gt; None
英文:

When you use an identifier in a pattern, you're not testing equality with an existing value (or type). Instead, you're defining a pattern that always matches, and giving the matched value the given name in the scope following the pattern.

To test for equality, you need to use a when guard instead:

    match parameterTypes with
    | [| (arg1, ParameterAttributes.None); (arg2, ParameterAttributes.Out) |]
        when arg1 = typeof&lt;string&gt; &amp;&amp; arg2 = typeof&lt;uint64&gt;.MakeByRefType()
        -&gt; Some m
    | _ -&gt; None

huangapple
  • 本文由 发表于 2023年2月16日 02:41:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/75464148.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定