在 yojson 列表中对 Yojson 元素进行子类型化

Subtyping for Yojson element in a yojson list

我遇到了关于子类型的错误。 对于此代码,List.map (fun ((String goal_feat):> Basic.t) -> goal_feat) (goal_feats_json:> Basic.t list)。 我在 vscode 中遇到以下错误:

This expression cannot be coerced to type
  Yojson.Basic.t =
    [ Assoc of (string * Yojson.Basic.t) list
    | Bool of bool
    | Float of float
    | Int of int
    | List of Yojson.Basic.t list
    | Null
    | String of string ];
it has type [< String of 'a ] -> 'b but is here used with type
  [< Yojson.Basic.t ]. 

编译时遇到如下错误。 Error: Syntax error: ')' expected.

如果我将代码更改为 List.map (fun ((String goal_feat): Basic.t) -> goal_feat) (goal_feats_json:> Basic.t list),使用显式类型转换而不是子类型,则错误消失。当我使用子类型化时,我不明白我的代码有什么问题。非常感谢任何能给我一些帮助的人。

首先,您正在寻找的答案很可能是

let to_strings xs =
  List.map (function `String x -> x | _ -> assert false) (xs :> t list)

编译器告诉您您的函数只处理一种情况,而您传递给它的列表可能包含许多其他内容,因此可能会出现运行时错误。因此,最好向编译器表明您知道只有标记为 String 的变体是预期的。这就是我们在上面的例子中所做的。现在我们的函数的类型是 [> Yojson.Basic.t].

现在回到你的直接问题。这里的syntax for coercion is (expr : typeexpr), however in the fun ((String goal_feat):> Basic.t) -> goal_feat snippet, String goal_feat is a pattern, and you cannot coerce a pattern, so we shall use parenthesized pattern是为了给它赋予正确的,更笼统的,类型1,例如,

let exp xs =
  List.map (fun (`String x : t) -> x ) (xs :> t list)

这会告诉编译器你的函数的参数应该属于更宽的类型,并立即将错误转为警告8,

Warning 8: this pattern-matching is not exhaustive.
Here is an example of a case that is not matched:
(`Bool _|`Null|`Assoc _|`List _|`Float _|`Int _)

这就是我在 post 的第一部分所说的内容。让警告 8 无人看管通常是一个坏主意,所以我建议您使用第一个解决方案,或者,找到一种方法向编译器证明您的列表没有任何其他变体,例如,您可以为此使用 List.filter_map

let collect_strings : t list -> [`String of string] list = fun xs ->
  List.filter_map (function
      | `String s -> Some (`String s)
      | _ -> None) xs

更自然的解决方案是 return 未标记的字符串(除非你真的需要标记,例如,当你需要将此列表传递给在 [=25= 上具有多态性的函数时](此外,我使用 t 代替 Yojson.Basic.t 来缩短 post,但您应该在代码中使用正确的名称)。所以这里是提取字符串和让每个人都开心(它会丢弃其他标签的值),

let collect_strings : t list -> string list = fun xs ->
  List.filter_map (function
      | `String s -> Some s
      | _ -> None) xs

注意,这里不需要类型注解,我们可以轻松去掉它们,得到最通用的多态类型:

let collect_strings xs =
  List.filter_map (function
      | `String s -> Some s
      | _ -> None) xs

它将获得类型

[> `String a] list -> 'a list

这意味着,带有任何标签的多态变体列表,returning 带有 String 标签的对象列表。


1)强制转换对模式不起作用并不是限制,而且强制转换模式没有任何意义。强制转换采用具有现有类型的表达式并将其向上转换(弱化)为超类型。函数参数不是表达式,所以这里没有什么可以强制的。你可以只用类型注释它,例如,fun (x : #t) -> x 会说我们的函数需要类型 [< t] 的值,这比未注释的类型 'a 更不通用。总而言之,当你有一个接受具有对象或多态变体类型的值的函数时,需要强制转换,并且你希望在某些表达式中将它与弱化(升级类型)一起使用,例如

type a = [`A]
type b = [`B]
type t = [a | b]

let f : t -> unit = fun _ -> ()
let example : a -> unit = fun x -> f (x :> t)

这里我们有类型 t 和两个子类型 ab。我们的函数 f 接受基本类型 t,但 example 特定于 a。为了能够在类型为 a 的对象上使用 f,我们需要一个显式类型强制将其类型弱化(我们在这里丢失了类型信息)为 t。请注意,我们并没有改变 x 本身的类型,所以下面的例子仍然是类型检查:

let rec example : a -> unit = fun x -> f (x :> t); example x

即,我们弱化了 f 的参数类型,但变量 x 仍然具有更强的类型 a,所以我们仍然可以将它用作输入 a