我最近尝试使用类似流媒体的流媒体播放...有一些代码使用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
答案 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 list
和float list
是不同的类型,您无法分配这两种不同类型的相同变量值。这不是OCaml本身的属性,这是任何参数多态的属性,例如std::vector<int>
和std::vector<float>
是具有不同表示的不同类型,它们的值不能互换使用,虽然严格来说c ++中的模板不是真正的参数多态,但它们只是宏。
但回到OCaml。多态函数背后的想法是具有多态数据类型的参数具有相同的表示,并且函数可以应用于此类型的任何实例而无需任何运行时检查,因为删除了所有类型信息。例如,
let rec length = function
| [] -> 0
| _ :: xs -> 1 + length xs
是多态的,因为它可以应用于多态数据类型'a list
的任何实例,包括int list
,float list
,person 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
。在某些时候,您会发现自己使用新字段扩展此记录,并实现越来越多的函数,这些函数接受此环结构作为第一个参数。这将是使用更重的抽象的好时机,称为functor
。 functor
实际上是类固醇的记录,隐含地传递给仿函数实现的每个函数。除此之外的函数,函数记录和仿函数以及其他抽象技术:第一类模块,对象,类,以及很快,implicits(又称类型类)。学习如何在每种特定情况下选择更适合的抽象技术是编程专业知识的一部分。一般的建议是尽可能使用最重的一个。事实上,95%使用功能或功能记录就足够了。
现在让我们回到你的榜样。在这里,你打在同一个洞里, 你将多态性与抽象混淆:
let fmt =
if format_num = 1
then new Lamefmt.sout sp
else new Otherfmt.sout sp in
Lamefmt.sout
和Otherfmt.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