I'm trying to create a bunch of types to generate a MIDI file to be played in a separate audio player (yes, I know there are libraries available... I'm using this as a way to learn). I created a single module signature (while attempting a functor solution) that the various modules containing these types are implementing. The signature looks like this (for now... string will be changed to bytes once I get a working template):
module type MIDIInterface =
sig
type t
val encode : t -> string
end
The various types I'm creating in different modules/files would be for the various types of MidiEvents: NoteOn, NoteOff, etc. All of them will have an encode function. Because each type is different (differing # of variables, differing event code, etc.), they can't just use the same encode function.
What I want to ultimately do is have a way to fold through all of the events of a song and call each events' encode function to create the full string (eventually bytestring) that can then be saved to a file.
I know I can just create a GADT to put make all of the various types the same type so that I can turn them into a giant list:
type Event =
| NoteOn of NoteOnEvent
| NoteOff of NoteOffEvent
...
...but this seems kind of a bulky solution, primarily because I'd have to update that type every time I create a new type. I also know that I can just implement them all under one type and just have one giant encode function:
let encode = function
| (NoteOn time note velocity) -> ...
| (NoteOff time note) -> ...
This is the solution I first tried in Haskell, and it was a nightmare to try to read through it when I was looking to reference something (one of the big reasons I'd like to break them into different types in different modules).
Is there a better solution in OCaml to be able to unify these various types based off the same signature without maintaining a big combinatory type into some sort of list/array/collection so that I can "fold encode empty collection_of_events"? If so, can you please let me know what this is called and where I can read up on how it works?
It sounds like you may be looking for the object-oriented side of OCaml. The virtual class midi_interface
cannot itself be instantiated and thus is analogous to your module type. As your module type MIDIInterface
will create abstract types, the implementation details of the a
and b
subclasses are opaque.
# class virtual midi_interface =
object
method virtual encode : string
end
class a =
object
inherit midi_interface
method encode = "a"
end
class b =
object
inherit midi_interface
method encode = "b"
end
let lst : midi_interface list = [new a; new b]
let result = List.fold_left (fun i x -> i ^ x#encode) "" lst
;;
class virtual midi_interface :
object method virtual encode : string end
class a : object method encode : string end
class b : object method encode : string end
val lst : midi_interface list = [<obj>; <obj>]
val result : string = "ab"
And demonstrating the ability to have different variables:
# class c n =
object
inherit midi_interface
method encode =
let rec replicate s n =
if n = 0 then ""
else s ^ replicate s (n - 1)
in
replicate "c" n
end;;
class c : int -> object method encode : string end
# let lst : midi_interface list = [new a; new b; new c 5] in
List.fold_left (fun i x -> i ^ x#encode) "" lst;;
- : string = "abccccc"
As an added note, it is not strictly speaking necessary for these classes to inherit from midi_interface
.
# class d = object
method encode = "d"
end;;
class d : object method encode : string end
# let lst : midi_interface list = [new a; new b; new c 5; new d] in
List.fold_left (fun i x -> i ^ x#encode) "" lst;;
- : string = "abcccccd"
Now, what happens if I fail to define encode
for d
?
# class d = object
end;;
class d : object end
# let lst : midi_interface list = [new a; new b; new c 5; new d] in
List.fold_left (fun i x -> i ^ x#encode) "" lst;;
Error: This expression has type d
but an expression was expected of type midi_interface
The first object type has no method encode
Note that OCaml had no problem with the definition of d
without the encode
method until I tried to use it. Then it rightly protested. It would protest the lack of encode
even if I removed the type annotation.
# let lst = [new a; new b; new c 5; new d] in
List.fold_left (fun i x -> i ^ x#encode) "" lst;;
Error: This expression has type d
but an expression was expected of type a
The first object type has no method encode
But, if I explicitly say that d
inherits from midi_interface
the compiler will complain if I fail to implement encode
as soon as I try to define d
rather than when I try to use it.
# class d = object
inherit midi_interface
end;;
Error: This non-virtual class has virtual methods.
The following methods are virtual : encode