Post by raouldthe term generic seems to be over-used, and i'm not sure i grok all
the uses and/or how they overlap.
Everyone uses the term "generic programming" to refer to making definitions
more widely applicable (i.e. general) which seems to be indistinguishable
from code reuse and/or factoring to me. This is, in turn, strongly related
to brevity.
Lisp's macros were one of the earliest forms of generic programming and,
yet, modern uses of the term "generic programming" often refer specifically
to the expressiveness of static type systems. This seems to be particularly
prevalent in the context of Haskell where generic programming is conflated
with type constraints and any relevance to the true objective of code reuse
is lost.
For example, Haskellers Garcia et al. published a paper "An extended
comparative study of language support for generic programming" that was
actually more about static type constraints than it was about generalizing
definitions or improving brevity and reuse. Although they claimed to have
implemented graph theoretic code in several languages, their OCaml code
seems to have remained secret:
http://www.osl.iu.edu/research/comparing/
Garcia had published research on Haskell and apparently wrote great Haskell
solutions in this paper but I think their OCaml code leaves a lot to be
desired (and probably all of the other non-Haskell solutions as well) and
their relevant conclusions are simply wrong. Apparently they managed to get
this published without obtaining peer review from anyone familiar with the
other languages.
Even their first example (complete with superfluous parentheses and
semicolons):
class type comparable = object ('a) method better : 'a -> bool end
class apple init = object (self: 'a)
val value_ : int = init
method better (y: 'a) = self#value > y#value
method value = value_;
end
let pick ((x: #comparable as 'a), (y: 'a)) : 'a =
if x#better y then x else y
let a1 = (new apple 3);;
let a2 = (new apple 1);;
let a3 = pick (a1, a2);;
would normally be written:
let apple i = object
method better y = i > y#value
method value = i
end
let pick x y = if x#better y then x else y
let a1 = apple 3 and a2 = apple 1
let a3 = pick a1 a2
The essential difference is that OCaml can infer the relevant types and,
consequently, there is no need to define these types and their
relationships by hand. Ironically, their OCaml code inherited superfluous
definitions from the Haskell and they then concluded that these were a
disadvantage for OCaml because non-trivial combinations must be maintained
by hand (which is simply wrong: they are all automatically inferred).
Now look at their keystone OCaml solution for the graph theoretic problem:
class type ['vertex_t] vertex_list_graph = object
method vertices : 'vertex_t list
method num_vertices : int
end
class type ['vertex_t] edge = object
method source : 'vertex_t
method target : 'vertex_t
end
class type ['vertex_x, 'edge_t] incidence_graph = object
constraint 'edge_t = 'vertex_t #edge
method out_edges : 'vertex_t -> 'edge_t list
end
In the context of generic programming, these type definitions are entirely
superfluous. Sadly, they use these superfluous definitions to justify the
conclusion that OCaml is needlessly verbose.
They do give the following concrete implementation of an adjacency-list
based graph type in their paper:
class algraph_edge (s: int) (t: int) = object
val src = s
val tgt = t
method source = src
method target = tgt
end
class adjacency_list (num_vertices_) = object
val g = Array.make num_vertices_ []
method add_edge (src, tgt: int * int) = g.(src) <- (tgt::g.(src))
method vertices =
let rec floop (i: int) =
if i = num_vertices_ then [] else i::floop(i+1) in
floop 0
method num_vertices = num_vertices_
method out_edges v = List.map (fun n -> new algraph_edge v n) g.(v)
method adjacent_vertices v = g.(v)
method edges =
let (_, result) = Array.fold_left
(fun (src, (sofar: (algraph_edge) list)) (tgts: int list) ->
(src+1, List.append
(List.map (fun n -> new algraph_edge src n)
tgts) sofar))
(0, []) g in
result
method create_property_map : 'a. 'a -> 'a array =
fun def -> Array.make num_vertices_ def
end
let graph_search
(graph: (('vertex_t, 'edge_t) #incidence_graph) as 'graph_t)
(v: 'vertex_t)
(q: 'value_t #buffer)
(vis: ('graph_t, 'vertex_t, 'edge_t) #visitor)
(map: ('color_t, 'key_t) #color_map) = ...
This is just abhorrent and could not be less representative of a real OCaml
solution.
Why the superfluous immutable private data in the "algraph_edge" class?
Why are they defining classes that only have the effect of making the code
less generic?
Why are they mixing mutable (array) and immutable (list) data structures
like this?
Why the unnecessary nested function in the "vertices" method to create the
list [0..n-1]?
Why did they not annotate the type of "g" where it might actually help but
did annotate the arguments to the inner anonymous function in the "edges"
method?
You can rewrite their 46-line OCaml solution with the following 14-line
OCaml solution:
let edge s t = object method source = s method target = t end
let adjacency_list n = object
val g = Array.init n (fun _ -> Stack.create())
method add_edge (src, tgt) = Stack.push tgt g.(src)
method vertices = Array.init n id
method num_vertices = n
method out_edges v = g.(v)
method adjacent_vertices v = g.(v)
method edges =
let es = Stack.create() in
Array.iteri (fun u -> Stack.iter (fun v -> Stack.push (edge u v) es));
es
end
This uses more idiomatic data structures but that is irrelevant in this
context.
Finally, they claim that the OCaml solution cannot support separate
compilation. Is that true? I doubt it...
Post by raouldbut anyway, i'm attempting to learn
more about the subject. some things i've read [1] make it sound to me
like c++ is the most complete language when it comes to generic
approaches.
The problem with C++ (and Haskell) is that the Turing complete type system
gives you complete generality in some uselessly-impractical sense, i.e. it
is a Turing argument.
Post by raouldof course that makes me sad. (maybe d does well, i dunno.)
supposedly scala fares better than most, but still falls at some
point. any thoughts? i'm looking to learn about such things in a
concrete real world language rather than e.g. an extension to ghc.
You must take into account the applicability of these "solutions" in real
programs. The .NET guys are acutely aware of many of the academic features
found in languages like Haskell but they choose not to adopt them because
they do not solve real problems.
--
Dr Jon D Harrop, Flying Frog Consultancy Ltd.
http://www.ffconsultancy.com/?u