英文:
Why Does F# Pattern Matching for a String Type Match a ReadOnlySpan<char>?
问题
以下是代码中的翻译部分:
让我们假设我想要查找特定的 `TryParse` 方法,用于给定类型,即从字符串解析的方法。令我惊讶的是,以下代码返回 2 个方法:一个以 `string` 作为第一个参数,还有一个以 `ReadOnlySpan<char>` 作为第一个参数。
我包括这个不那么简单的示例,因为事实证明在 F# 中“仅仅”即时创建 `ReadOnlySpan` 有点困难。
结果列表中的第二个元素对我来说是意外的。如何更改 `[1]` 处的模式匹配以排除它?
(*
val it: (string * string array) list =
[("TryParse", [|"System.String"; "System.UInt64&"|]);
("TryParse",
[|"System.ReadOnlySpan`1[[System.Char, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]";
"System.UInt64&"|])]
*)
请注意,代码中的 HTML 实体字符(如 "
和 >
)已保留在翻译中,以保持原始代码的格式。
英文:
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<char>
as first parameter.
I include this not-so-minimal example because it turns out to be somewhat hard to 'just' create ReadOnlySpan
s 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<uint64>.GetMember("TryParse")
|> Seq.cast<MethodBase>
|> Seq.choose (fun m ->
let parameterTypes =
m.GetParameters()
|> Array.map (fun p -> (p.ParameterType, p.Attributes))
// [1]
match parameterTypes with
| [| (string, ParameterAttributes.None); (uint64, ParameterAttributes.Out) |] -> Some m
| _ -> None)
|> Seq.toList
|> List.map (fun m -> (m.Name, m.GetParameters() |> Array.map (fun p -> p.ParameterType.FullName)))
(*
val it: (string * string array) list =
[("TryParse", [|"System.String"; "System.UInt64&"|]);
("TryParse",
[|"System.ReadOnlySpan`1[[System.Char, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]";
"System.UInt64&"|])]
*)
答案1
得分: 1
你的代码发现了TryParse
的两个版本,因为你声明了变量string
和uint64
,它们与任何值匹配。要修复这个问题,你需要与实际的类型进行比较:
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<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)))
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<string> && arg2 = typeof<uint64>.MakeByRefType()
-> Some m
| _ -> 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<string> && arg2 = typeof<uint64>.MakeByRefType()
-> Some m
| _ -> None
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论