方法类型不兼容

时间:2015-12-11 13:59:57

标签: streaming ocaml

我最近尝试使用类似流媒体的流媒体播放...有一些代码使用OCaml类和C库进行编码,如跛脚(通过ocaml-lame)等。

(* Lame module *)
type encoder
(* ... *)
external encode_buffer_float_part : encoder -> float array -> float array -> int -> int -> string = "ocaml_lame_encode_buffer_float"

(* Otherencoder module *)
type encoder
(* ... *)
external encode_buffer_float_part : encoder -> float array -> float array -> int -> int -> string = "ocaml_otherencoder_encode_buffer_float"

(=同一界面)

某处有两个高级类,它们从两个独立的encoderbase虚拟类继承:

(* Mp3_output module *)
class virtual encoderbase =
    object (self)
    method encode ncoder channels buf offset size =
        if channels = 1 then
            Lame.encode_buffer_float_part ncoder buf.(0) buf.(0) offset size
        else
            Lame.encode_buffer_float_part ncoder buf.(0) buf.(1) offset size
end

(* somewhere in the code *)
class to_shout sprop =
    (* some let-s *)
    object (self)
    inherit
        [Lame.encoder] Icecast2.output ~format:Format_mp3 (* more params *) as super
    inherit base
    (* ... *)
end

(* Other_output module *)
class virtual encoderbase =
    object (self)
    method encode ncoder channels buf offset size =
        if channels = 1 then
            Otherencoder.encode_buffer_float_part ncoder buf.(0) buf.(0) offset size
        else
            Otherencoder.encode_buffer_float_part ncoder buf.(0) buf.(1) offset size
end

(* somewhere in the code *)
class to_shout sprop =
    (* some let-s *)
    object (self)
    inherit
        [Otherencoder.encoder] Icecast2.output ~format:Format_other (* more params *) as super
    inherit base
    (* ... *)
end

所有事情都可以正常使用:

let icecast_out source format =
    let sprop =
        new Mp3_output.shout_sprop
    in
        (* some code here *)
        new Mp3_output.to_shout sprop

但是当我尝试这样的事情时:

let icecast_out source format =
    let sprop =
        if format = Format_other then
            new Other_output.shout_sprop
        else
            new Mp3_output.shout_sprop
    in
        (* some code here *)
        if format = Format_mp3 then
            new Mp3_output.to_shout sprop
        else
            new Other_output.to_shout sprop

编译中断错误@ new Other_output.to_shout sprop

Error: This expression has type Other_output.to_shout
    but an expression was expected of type Mp3_output.to_shout
    Types for method encode are incompatible

有没有办法“说服”OCaml(共同的祖先?包装类?类型转换?)同时使用这两个不同的类/绑定进行编译?

更新(2015.12.15): 代码示例:https://gist.github.com/soutys/22b67a5df9ae0a6f1f72

2 个答案:

答案 0 :(得分:0)

  

有没有办法说服"说服" OCaml(常见的祖先?包装类?类型转换?)一次使用这两个不同的类/绑定进行编译?

相信OCaml是一种类型安全的语言,因此不可能说服OCaml编译会崩溃的程序,除非你使用了一些险恶的方法。

您的误解的根源由您的示例中的以下代码段说明:

type 'a term = Save of 'a
let enc_t =
  if format_num = 1
  then Save Lame.encoder
  else Save Other.encoder

表达式Save Lame.encoder的类型为Lame.encoder term,而表达式Save Other.encoder的类型为Other.encoder term。从类型系统的角度来看,这是两种完全不同的类型,尽管它们是由相同类型的构造函数term构建的。 C.f.,int listfloat list是不同的类型,您无法分配这两种不同类型的相同变量值。这不是OCaml本身的属性,这是任何参数多态的属性,例如std::vector<int>std::vector<float>是具有不同表示的不同类型,它们的值不能互换使用,虽然严格来说c ++中的模板不是真正的参数多态,但它们只是宏。

但回到OCaml。多态函数背后的想法是具有多态数据类型的参数具有相同的表示,并且函数可以应用于此类型的任何实例而无需任何运行时检查,因为删除了所有类型信息。例如,

let rec length = function
  | [] -> 0
  | _ :: xs -> 1 + length xs

是多态的,因为它可以应用于多态数据类型'a list的任何实例,包括int listfloat listperson list等。

另一方面,一个功能

let rec sum = function 
  | [] -> 0
  | x :: xs -> x + sum xs

不是多态的,只能应用于int list类型的值。原因是因为这个函数的实现依赖于一个事实,即每个元素都是一个整数。如果您能够说服类型系统,要将此功能应用于float列表,您将收到分段错误。

但是你可能会说,我们错过了一些东西,因为float列表的和函数看起来基本相同:

let rec fsum = function 
  | [] -> 0.
  | x :: xs -> x +. fsum xs

因此有机会抽象总结。当我们抽象出一些东西时,我们发现不同实现之间存在差异,而抽象它们。 OCaml中最简单的抽象原语是一个函数,所以我们这样做:

let rec gsum zero plus xs =
  let (+) = plus in
  let rec sum = function
    | [] -> zero
    | x :: xs -> x + sum xs in
  sum xs

我们抽象出zero元素和plus函数。因此,我们得到求和的抽象,适用于任何类型,您可以为此操作提供plus操作和元素中性(在抽象代数中称为环数据结构)。 gsum的类型是

'a -> ('b -> 'a -> 'a) -> 'b list -> 'a

它甚至太通用了,我们可以将它专门化一点,就像这种类型

'a -> ('a -> 'a -> 'a) -> 'a list -> 'a

更适合。我们可以将它聚合成记录类型,而不是逐个传递环结构的元素:

type 'a ring = {
    zero : 'a;
    plus : 'a -> 'a -> 'a
}

并按如下方式实施我们的通用sum

let rec gsum ring xs =
  let (+) = ring.plus in
  let rec sum = function
    | [] -> ring.zero
    | x :: xs -> x + sum xs in
  sum xs

在这种情况下,我们会有一个很好的类型sum : 'a ring -> 'a list -> 'a。在某些时候,您会发现自己使用新字段扩展此记录,并实现越来越多的函数,这些函数接受此环结构作为第一个参数。这将是使用更重的抽象的好时机,称为functorfunctor实际上是类固醇的记录,隐含地传递给仿函数实现的每个函数。除此之外的函数,函数记录和仿函数以及其他抽象技术:第一类模块,对象,类,以及很快,implicits(又称类型类)。学习如何在每种特定情况下选择更适合的抽象技术是编程专业知识的一部分。一般的建议是尽可能使用最重的一个。事实上,95%使用功能或功能记录就足够了。

现在让我们回到你的榜样。在这里,你打在同一个洞里, 你将多态性与抽象混淆:

let fmt =
  if format_num = 1
  then new Lamefmt.sout sp
  else new Otherfmt.sout sp in

Lamefmt.soutOtherfmt.sout是不同的类型,第一个类型为:

type sout = <
  doenc  : Lame.encoder -> float array array -> int -> int -> string;
  encode : Lame.encoder -> float array array -> int -> int -> string 
>

和第二个:

type sout = < 
  doenc  : Other.encoder -> float array array -> int -> int -> string;
  encode : Other.encoder -> float array array -> int -> int -> string 
>

这是两个不同的对象,虽然它们有类似的方案,这意味着我们在这里有一些抽象机会。

在您的情况下,我们可以从一个简单的观察开始,两个编码器函数具有相同的类型编码器对象本身。使用Occam的剃刀原则,我们试图用尽可能简单的抽象来捕捉这个 - 一个函数:

type encoder = buffer -> buffer -> int -> int -> string 

,其中

type buffer = float array array

然后我们可以构建不同的编码器:

let lame : encoder = 
   let encoder = Lame.create_encoder () in
   Lame.encode_buffer_float_part encoder

let other : encoder = 
   let encoder = Other.create_encoder () in 
   Other.encode_buffer_float_part encoder

然后你可以互换地使用这两个值。有时,不同的编码器需要不同的参数,在这种情况下,我们的任务是尽快发送它们,例如,

 let very_customizable_encoder x y z : encoder = 
   let encoder = VCE.create_encoder x y z in 
   Other.encode_buffer_float_part encoder

在这种情况下,您应该尽可能靠近用户解决自定义问题,然后再使用抽象。

经常使用哈希表或其他关联数据结构来存储编码器。这种方法甚至可以让你代表一个插件架构,其中插件是动态加载的,并在一些表中注册自己(类型编码器的值)。

结论。使用函数来表示您的问题就足够了。也许在某些时候你需要使用函数记录。到目前为止,我还没有看到使用课程的必要性。通常,当您对开放递归感兴趣时,即当您的问题由一组相互递归函数表示时,并且您希望保留一些未指定的函数,即参数化实现时,它们是必需的。

答案 1 :(得分:0)

首先要简化你的例子。例如删除不相关的位并添加虚拟存根,因此我们不需要真正的编码器:

module Lame = struct
  type encoder
  let encode_buffer_float_part : encoder -> float -> unit = fun _ -> failwith "ocaml_lame_encode_buffer_float"
end

module Otherencoder = struct
  type encoder
  let encode_buffer_float_part : encoder -> float -> unit = fun _ -> failwith "ocaml_otherencoder_encode_buffer_float"
end

module Mp3_output = struct
  class to_shout = object
    method encode ncoder x =
      Lame.encode_buffer_float_part ncoder x
  end
end

module Other_output = struct
  class to_shout = object
    method encode ncoder x =
      Otherencoder.encode_buffer_float_part ncoder x
  end
end

type format = Format_other | Format_mp3

let icecast_out source format =
  if format = Format_mp3 then new Mp3_output.to_shout
  else new Other_output.to_shout

给出

Error: This expression has type Other_output.to_shout
       but an expression was expected of type Mp3_output.to_shout
       Types for method encode are incompatible

哪个是对的。如果我给你“icecast_out返回的值”,那么你就不会知道如何调用它,因为你不知道第一个参数应该是什么。

由于每个类的编码器不同,因此它应该是构造函数参数。 e.g。

module Mp3_output = struct
  class to_shout ncoder = object
    method encode x =
      Lame.encode_buffer_float_part ncoder x
  end
end

module Other_output = struct
  class to_shout ncoder = object
    method encode x =
      Otherencoder.encode_buffer_float_part ncoder x
  end
end

type format = Format_other | Format_mp3

let icecast_out source format =
  if format = Format_mp3 then new Mp3_output.to_shout Lame.ncoder
  else new Other_output.to_shout Otherencoder.ncoder