Guest User

Untitled

a guest
Sep 17th, 2011
379
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.62 KB | None | 0 0
  1. Russ Cox
  2.  
  3. Sep 16 (20 hours ago)
  4. Re: [go-nuts] How can Go solve a problem that is well served by multiple inheritance?
  5. > In the Eiffel implementation a key aspect to its efficiency is multiple
  6. > inheritance: this allows objects to access things in a way that Go's
  7. > embedding doesn't seem to support.
  8. I don't think this is a key aspect to its efficiency, nor to
  9. its brevity. It is, however, a key aspect to its multiple
  10. inheritance.
  11.  
  12. > * Of course, I am not claiming that you can't implement Dijkstra's
  13. > * algorithm in Go. The claim is that you can't take two types, one for
  14. > * directed graphs and one for priority queues, and use them, without
  15. > * rewriting their methods, to produce a version of Dijkstra's algorithm
  16. This question betrays an inheritance-oriented bias: it
  17. assumes that to reuse an algorithm, you have to do it by
  18. inheriting from a type that implements that algorithm. Go
  19. doesn't buy into that. Algorithms are code, not types.
  20. They obviously operate on values that have types, and each
  21. algorithm has requirements it places on the types of values
  22. it can operate on, but in the end, an algorithm is not a
  23. type, nor is it embodied by a type. Algorithms are code, so
  24. it is idiomatic to express them in Go as functions.
  25.  
  26. Sort is a good example. There are many kinds of sorting
  27. algorithms, but a very common kind is comparison-based,
  28. meaning that the operations it expects to be able to apply
  29. to the data are to compare two elements at arbitrary indices
  30. and perhaps also swap them. The Go sort package defines an
  31. interface, sort.Interface, that must be implemented by any
  32. data that wants to submit itself to this kind of sorting
  33. algorithm:
  34.  
  35. package sort
  36.  
  37. type Interface interface {
  38. // Len is the number of elements in the collection.
  39. Len() int
  40. // Less returns whether the element with index i should sort
  41. // before the element with index j.
  42. Less(i, j int) bool
  43. // Swap swaps the elements with indexes i and j.
  44. Swap(i, j int)
  45. }
  46.  
  47. If your type implements sort.Interface, you can sort what it
  48. represents, by passing it to the sort.Sort function - the
  49. actual algorithm. We changed the algorithm recently,
  50. actually, and no one noticed, because the details are hidden
  51. behind this simple signature:
  52.  
  53. func Sort(data Interface)
  54.  
  55. So far this doesn't look too different than many languages,
  56. because there is no actual state that Sort must maintain
  57. other than the values themselves. Maybe another language
  58. would choose different methods, but it's about the same.
  59.  
  60. Heaps are another good example, and where things start to
  61. diverge from inheritance-oriented languages. Instead of
  62. requiring each element that will be stored in a heap to
  63. inherit from some heap-element type, Go's heap package
  64. defines the interface required of a container in order to
  65. apply the heap algorithms to it:
  66.  
  67. package heap
  68.  
  69. type Interface interface {
  70. sort.Interface
  71. Push(x interface{})
  72. Pop() interface{}
  73. }
  74.  
  75. The container needs to be able to do the comparison
  76. operations that sort requires, and it also needs to be able
  77. to push an element onto the end of the underlying array and
  78. to pop an element from the end.
  79.  
  80. If your container implements those operations, you can call
  81. the heap algorithm operations:
  82.  
  83. func Pop(h Interface) interface{}
  84. func Push(h Interface, x interface{})
  85. func Remove(h Interface, i int) interface{}
  86.  
  87. Remove is the interesting operation here: it requires that
  88. you give it the heap index of the element you want to
  89. remove, not the element itself.
  90.  
  91. In an inheritance-oriented language, you can pass the
  92. element itself because the element type inherits from a
  93. master heap element type that the heap algorithm updates
  94. with its current index every time it moves within the heap.
  95. That has an implication: you can only put your element into
  96. one heap at a time. Since you only need this info during
  97. Remove, we decided in Go to let the caller do the tracking
  98. when necessary (and skip it if not). The most obvious
  99. implementation is to stick an index into the struct being
  100. moved around, like in this code from package time:
  101.  
  102. func (timerHeap) Swap(i, j int) {
  103. timers[i], timers[j] = timers[j], timers[i]
  104. timers[i].i = i
  105. timers[j].i = j
  106. }
  107.  
  108. but it's also possible to leave it out (if not calling
  109. Remove) or to do something more general (if you want one
  110. object to be storable in multiple heaps). In Go there is no
  111. PRIQUEUE_EXTENDED_FIBHEAP_ENTRY to inherit. This is
  112. especially important if you are building a heap of objects
  113. of a type you don't control, which comes up when writing
  114. Dijkstra's algorithm. (You could even implement an on-disk
  115. heap using Go's heap package; trying doing that when a disk
  116. block has to inherit from PRIQUEUE_EXTENDED_FIBHEAP_ENTRY!)
  117.  
  118. Speaking of Dijkstra's algorithm, there's no implementation
  119. in the standard library, so I wrote one. It expects an
  120. input that satisfies the Graph interface:
  121.  
  122. package graph
  123.  
  124. type Graph interface {
  125. // NumVertex returns the number of vertices in the graph.
  126. NumVertex() int
  127.  
  128. // VertexID returns a vertex ID, 0 <= ID < NumVertex(), for v.
  129. VertexID(v Vertex) int
  130.  
  131. // Neighbors returns a slice of vertices that are adjacent
  132. // to v in the graph.
  133. Neighbors(v Vertex) []Vertex
  134. }
  135.  
  136. type Vertex interface {
  137. String() string
  138. }
  139.  
  140. func ShortestPath(g Graph, start, end Vertex) []Vertex
  141.  
  142. Here I've decided that a Graph should be able to give each
  143. vertex a dense numeric ID, which the various graph
  144. algorithms can use to identify the vertex. Note that I am
  145. not requiring a Vertex to be a
  146. PRIQUEUE_EXTENDED_FIBHEAP_ENTRY. Why should internal
  147. details of the implementation of ShortestPath leak into the
  148. interface required of its callers?
  149.  
  150. ShortestPath is straightforward:
  151.  
  152. // ShortestPath uses Dijkstra's algorithm to find the shortest
  153. // path from start to end in g. It returns the path as a slice of
  154. // vertices, with start first and end last. If there is no path,
  155. // ShortestPath returns nil.
  156. func ShortestPath(g Graph, start, end Vertex) []Vertex {
  157. d := newDijkstra(g)
  158.  
  159. // Run Dijkstra's traversal.
  160. d.visit(start, 1, nil)
  161. for !d.empty() {
  162. p := d.next()
  163. if g.VertexID(p.v) == g.VertexID(end) {
  164. break
  165. }
  166. for _, v := range g.Neighbors(p.v) {
  167. d.visit(v, p.depth+1, p)
  168. }
  169. }
  170.  
  171. // Reconstruct path.
  172. p := d.pos(end)
  173. if p.depth == 0 {
  174. // unvisited - no path
  175. return nil
  176. }
  177. path := make([]Vertex, p.depth)
  178. for ; p != nil; p=p.parent {
  179. path[p.depth-1] = p.v
  180. }
  181. return path
  182. }
  183.  
  184. The visit and next methods contain calls to container/heap's
  185. heap algorithm functions, but that fact does not leak into
  186. the Graph interface. The ShortestPath code manages separate
  187. state on the side (the value d of type dijkstra) instead of
  188. requiring that state to be mixed into the Graph that is
  189. passed in.
  190.  
  191. A maze, of course, is a very specialized kind of graph, in
  192. which each vertex has at most four neighbors drawn from a
  193. fixed set. It is easy to implement a custom Maze type and
  194. then add the methods to implement graph.Graph.
  195.  
  196. The maze "solver" is then just a call to ShortestPath:
  197.  
  198. m := NewMaze(w, h)
  199. path := graph.ShortestPath(m, m.Vertex(0, 0), m.Vertex(w-1, h-1))
  200. fmt.Println(m.PathString(path))
  201.  
  202. The full code is at:
  203. http://code.google.com/p/rsc/source/browse/rosetta/graph/graph.go
  204. http://code.google.com/p/rsc/source/browse/rosetta/maze/maze.go
  205.  
  206. You can play with the code by running
  207.  
  208. goinstall rsc.googlecode.com/hg/rosetta/maze
  209. maze
  210. maze
  211. maze
  212.  
  213. If you look through the code, the only time there are
  214. conversions is to turn a Vertex into a maze.pos or an
  215. interface{} into a *graph.dpos. Those are fundamental to
  216. the way you write type-agnostic algorithms in Go: they have
  217. to operate on interface values, and then the implementations
  218. that accept those interfaces need to write a converson
  219. occasionally. There are two in package graph and three in
  220. maze. I don't believe that rises to the level of
  221. "extraneous rubbish".
  222.  
  223. It is easy to get caught in the trap of seeing a piece of
  224. nice code in another language, like this
  225. multiple-inheritance Eiffel shortest path, and then trying
  226. to translate the concepts into Go instead of stepping back
  227. to think about how you'd approach the problem and the
  228. solution from a Go point of view. Taking an Eiffel point of
  229. view when trying to write a Go program will not produce
  230. nice, idiomatic Go code. Quite the opposite. But if you
  231. can avoid the trap, and you come at it thinking about Go
  232. concepts instead of concepts from some other language, I
  233. think you can arrive at an elegant solution.
  234.  
  235. This makes Go different from many languages: because so many
  236. of today's languages are inheritance-oriented languages, it
  237. is easy to move between them and transliterate code, because
  238. the underlying model is the same. Go is built on a
  239. different model, so trying to move code from Eiffel or
  240. Python or Java or C# or C++ to Go usually works poorly. You
  241. have to start over, thinking in Go.
  242.  
  243. Go's interfaces force you to separate code from data. You
  244. don't get to mix them like you do in inheritance-oriented
  245. languages. This is certainly limiting - at the least, it
  246. makes code in inheritance-oriented languages harder to
  247. convert to Go - but it has benefits too. For example,
  248. because the ShortestPath state is not mixed into the Maze's
  249. Vertex implementation, it is possible for multiple
  250. goroutines to run graph.ShortestPath on the same maze
  251. simultaneously (presumably with different start, end pairs).
  252.  
  253. It's unfortunate that every time someone asks for
  254. inheritance the answer is "well, there's embedding."
  255. Embedding is useful and a kind of inheritance, but when
  256. people come looking for inheritance, embedding is not the
  257. answer. The answer is: you're thinking in C++, or in Python
  258. or Java or Eiffel or whatever. Don't do that. Think in Go.
  259.  
  260. Russ
Advertisement
Add Comment
Please, Sign In to add comment