Re: How to instantiate `ptr object`
> You can use closures or pass the data parameter as an input to the function. In some sense closures and objects are isomorphic, as are functional and OOP. There’s a famous old _koan_ about this: > The venerable master Qc Na was walking with his student, Anton. Hoping to > prompt the master into a discussion, Anton said “Master, I have heard that > objects are a very good thing — is this true?” Qc Na looked pityingly at his > student and replied, “Foolish pupil — objects are merely a poor man’s > closures.” > [(continues...)](https://citizen428.net/blog/of-closures-and-objects/) —Jens
Re: How to instantiate `ptr object`
y: 1.2E3 Run Thanks for that hint. Of course the book is in an very early state still, I wrote most of the two first chapters in April. But I hope and think that it can be already useful for beginners with nearly no CS knowledge. I have just yesterday applied a lot fixes provided by Jim Wilcoxson, mostly concerning English grammar. Current book content is the result of a fast flow directly from my head to the keyboard, so examples are untested and book content is not compared to the Nim manual in detail still. For reporting small issues you may use github at [https://github.com/StefanSalewski/NimProgrammingBook](https://github.com/StefanSalewski/NimProgrammingBook), or for a general discussion [https://forum.nim-lang.org/t/6170#38482](https://forum.nim-lang.org/t/6170#38482).
Re: How to instantiate `ptr object`
> For your example ARC makes a gigantic difference. Yeah, known problem. Method dispatch got super slow for ARC. Not sure yet how to fix it. I can do it the old way without problems, but where is the fun in that. ;-)
Re: How to instantiate `ptr object`
2 Stefan_Salewski - is your book published or it is "in process" state? I saw some referred (in the text) mistypings like: var mean = 3.0 / 7.9 x: float = 12 y: 1.2E3 Run
Re: How to instantiate `ptr object`
For machine learning in particular deep learning this is how I do it: 1\. I use inheritance, but only to be able to store the objects in the same container, **never for dispatch**. Example on neural network layer, called `Gate`, re-using the terminology from Andrej Karpathy's [Hacker's Guide to Neural Network](http://karpathy.github.io/neuralnets/) [https://github.com/mratsim/Arraymancer/blob/5b24877b/src/autograd/autograd_common.nim#L72-L85](https://github.com/mratsim/Arraymancer/blob/5b24877b/src/autograd/autograd_common.nim#L72-L85) type Gate*[TT] = ref object of RootObj # {.acyclic.} ## Base operator or layer. You can describe your custom operations or layers ## by inheriting from Gate and add a forward and optionally a backward method. ## Each operations should set the number of gradients produced during backpropagation. ## Additional fields specific to the operations like weights or inputs cache should be added too. PayloadKind* = enum pkVar, pkSeq Payload*[TT] = object case kind*: PayloadKind of pkVar: variable*: Variable[TT] of pkSeq: sequence*: seq[Variable[TT]] Backward*[TT] = proc(self: Gate[TT], payload: Payload[TT]): SmallDiffs[TT] {.nimcall.} Run 2\. For dispatching I associate a compile-time proc for each kind of `Gate` I have. The `Gate` carry the state I need to pass. The compile-time proc just unwrap it and pass it to the _[real](https://forum.nim-lang.org/postActivity.xml#real) which has the proper signature. For example for a MaxPool layer, the backward proc should have for signature. [https://github.com/mratsim/Arraymancer/blob/5b24877b/src/nn_primitives/nnp_maxpooling.nim#L70-L74](https://github.com/mratsim/Arraymancer/blob/5b24877b/src/nn_primitives/nnp_maxpooling.nim#L70-L74) So how to map it to `proc(self: Gate[TT], payload: Payload[TT]): SmallDiffs[TT] {.nimcall.}`? The MaxPoolGate and backward shim does the trick [https://github.com/mratsim/Arraymancer/blob/5b24877b/src/nn/layers/maxpool2D.nim#L20-L46](https://github.com/mratsim/Arraymancer/blob/5b24877b/src/nn/layers/maxpool2D.nim#L20-L46), `maxpool2D_backward_ag` stands for autograd version type MaxPool2DGate*[TT] {.final.} = ref object of Gate[TT] cached_input_shape: MetadataArray cached_max_indices: Tensor[int] kernel, padding, stride: Size2D proc maxpool2D_backward_ag[TT](self: MaxPool2DGate[TT], payload: Payload[TT]): SmallDiffs[TT] = let gradient = payload.variable.grad result = newDiffs[TT](1) result[0] = maxpool2d_backward( self.cached_input_shape, self.cached_max_indices, gradient ) Run So I get compile-time dispatch on arbitrary state with a fixed interface. And even though the wrapper proc does not much, 2 static function calls are always faster than a closure or a method. Other example, convolution, this may seem more complex as the backward operation has a lot of inputs: [https://github.com/mratsim/Arraymancer/blob/5b24877b/src/nn_primitives/nnp_convolution.nim#L65-L70](https://github.com/mratsim/Arraymancer/blob/5b24877b/src/nn_primitives/nnp_convolution.nim#L65-L70) proc conv2d_backward*[T](input, weight, bias: Tensor[T], padding: Size2D, stride: Size2D, grad_output: Tensor[T], grad_input, grad_weight, grad_bias: var Tensor[T], algorithm = Conv2DAlgorithm.Im2ColGEMM) Run But it's actually straightforward as well type Conv2DGate*[TT]{.final.} = ref object of Gate[TT] cached_input: Variable[TT] weight, bias: Variable[TT] padding, stride: Size2D # TODO: store the algorithm (NNPACK / im2col) proc conv2d_backward_ag[TT](self: Conv2DGate[TT], payload: Payload[TT]): SmallDiffs[TT] = let gradient = payload.variable.grad if self.bias.isNil: result = newDiffs[TT](2) else: result = newDiffs[TT](3) conv2d_backward( self.cached_input.value, self.weight.value, self.bias.value, self.padding, self.stride, gradient, result[0], result[1], result[2] ) Run
Re: How to instantiate `ptr object`
@mratsim Thank you for your detailed and informative response, also I didn't know about the do statement in Nim that's something else new for me. My main takeaway is that I don't always have to use runtime resolution, especially because the kind of code I write is mostly statistics/machine learning - type algorithms where performance is important and virtual method calls (dynamic dispatch) has an adverse impact. To be clear the example you gave using AbstractKernel[T] is resolved at compile time rather than at run time, AbstractKernel[T] is essentially a function pointer whose type (along with the matrix) is known at compile time. I guess OOP-based polymorphism is one way to provide a common interface to function dispatch and parametric polymorphism is another - which has no performance penalties but can not be used in instances where type resolution at compile time is not available, but in instances where this is not the case it pays to use parametric polymorphism.
Re: How to instantiate `ptr object`
For your example ARC makes a gigantic difference. (I put your global code in a main proc) import times type FooBase = ref object {.inheritable.} dummy: int type Foo{.final.} = ref object of FooBase value : float32 proc inplace_add_proc(x: var Foo, a: float32) = x.value += a proc inplace_add_closure(x: var float32, a: float32) = proc add_closure(v: var float32) = v += a add_closure(x) method inplace_add_method(x: FooBase, a: float32) {.base.} = discard method inplace_add_method(x: Foo, a: float32) = x.value += a proc main = var bar : Foo new bar var start = cpuTime() for i in 0..<1: inplace_add_proc(bar, 1.0f) echo " Proc with ref object ", cpuTime() - start var x : float32 start = cpuTime() for i in 0..<1: inplace_add_closure(x, 1.0f) echo " Closures ", cpuTime() - start var baz : Foo new baz start = cpuTime() for i in 0..<1: inplace_add_method(baz, 1.0f) echo " Methods ", cpuTime() - start main() Run $ nim c -r -d:danger t3.nim Proc with ref object 0.117909274 Closures 1.719076293 Methods 0.264790297999 Run $ nim c -r -d:danger --gc:arc t3.nim Proc with ref object 0.118106775 Closures 0.941439717001 Methods 3.822302076 Run
Re: How to instantiate `ptr object`
You can use closures or pass the data parameter as an input to the function. Here is an example with closures type AbstractKernel[T] = proc(dst: var T, src: Matrix[T], i, j: int) {.closure.} Matrix[T] = object dim: array[2, int] data: seq[T] func newMatrix(rows, cols: int, T: typedesc): Matrix[T] = result.dim[0] = rows result.dim[1] = cols result.data.newSeq(rows * cols) func ncols(m: Matrix): int {.inline.} = m.dim[1] func nrows(m: Matrix): int {.inline.} = m.dim[0] template `[]`(m: Matrix, i, j: int): auto = m.data[i * m.dim[1] + j] template `[]=`[T](m: var Matrix[T], i, j: int, val: T) = m.data[i * m.dim[1] + j] = val proc calculateKernelMatrix*[T](m: Matrix[T], kernel: AbstractKernel[T]): Matrix[T] = result = newMatrix(m.nrows, m.ncols, T); for i in 0 ..< m.nrows: for j in 0 ..< m.ncols: kernel(result[i, j], m, i, j); let M = newMatrix(3, 3, int) echo M # (dim: [3, 3], data: @[0, 0, 0, 0, 0, 0, 0, 0, 0]) let N = M.calculateKernelMatrix do (dst: var int, src: Matrix[int], i, j: int): let state = 100 dst = state * i + j echo N # (dim: [3, 3], data: @[0, 1, 2, 100, 101, 102, 200, 201, 202]) let K = N.calculateKernelMatrix do (dst: var int, src: Matrix[int], i, j: int): let state = -1 dst = src[i, j] * state echo K # (dim: [3, 3], data: @[0, -1, -2, -100, -101, -102, -200, -201, -202]) Run `state` can be anything beyond a simple int.
Re: How to instantiate `ptr object`
> No you don't, you can have AbstractKernel be a procNo you don't, you can have > AbstractKernel be a proc. > > > type AbstractKernel[F] = proc(loc: var F) {.nimcall.} > > > Run I don't understand and have a couple of questions. Firstly, what would be the syntax for other kernel functions that inherit? Secondly, the DotKernel function is the only function whose type doesn't carry data, the vast majority of the other kernel functions do, for example: Gaussian*[T] = ref object of AbstractKernel gamma: T Run Other kernels have different number (and names) of data members so how would you represent that with your function types? Thanks
Re: How to instantiate `ptr object`
> At compile time yes you could just use procs but you need polymorphism for > runtime dispatch. No you don't, you can have AbstractKernel be a proc type AbstractKernel[F] = proc(loc: var F) {.nimcall.} Run
Re: How to instantiate `ptr object`
One of the things that intrigued me about Nim was it's object system, you could create value, reference and pointer types with/without inheritance. As I understand it, an important distinction between ptr and ref classes is that ref classes are managed by the garbage collector while ptr classes have to be manually allocated and deallocated. In this case for me that _is_ the distinction between both of them, I see inheritance from a ptr class as the same as inheritance from a ref class and polymorphism in the same way: type: Animal = ptr object of RootObj Cat = ptr object of Animal var x: Animal = create(Cat) Run But I also want to be able to dispatch against Cat (x) at runtime, so send it as a parameter in a function marked with Animal but within that function call the cat method and allow the right method to be dispatched. In exactly the same way as in my kernel function: proc calculateKernelMatrix*(K: AbstractKernel, data: Matrix[F]): Matrix[F] = let n = int64(ncol(data)); var mat = Matrix[F](data: newSeq[F](n*n), dim: @[n, n]); for j in 0..
Re: How to instantiate `ptr object`
The allure of undocumented behaviors ;) @dataPulverizer, if you already use pointers you can use generic procs, why do you need inheritance for dispatch?
Re: How to instantiate `ptr object`
> you can't mix inheritance with un-managed raw pointers. I can do it. In fact, I did in Nim's allocator.
Re: How to instantiate `ptr object`
> you can't mix inheritance with un-managed raw pointers. I can do it. In fact, I did in Nim's allocator.
Re: How to instantiate `ptr object`
Isn't the point of inheritance method dispatch? Why isn't that a separate issue from objects that are dissociated from the garbage collector?
Re: How to instantiate `ptr object`
> you can't mix inheritance with un-managed raw pointers That is an interesting point, I was wondering about it this morning already when I wrote my first reply. I am still not sure. I could generally imagine that inheritance can work with with un-managed raw pointers. But maybe not in Nim? @dataPulverizer note that create() may be easier to use, as with create() no cast is necessary. @snej The book is in a very early stage still, I wrote the two chapters in April. And it is mostly for people with very less programming experience.
Re: How to instantiate `ptr object`
`ptr object of RootObj` seems very strange, you can't mix inheritance with un-managed raw pointers.
Re: How to instantiate `ptr object`
I wasn’t aware of that book, though I’ve been guzzling from the Nim-documentation firehose for a week now. You should get it linked from the Nim-lang “learning” page!
Re: How to instantiate `ptr object`
I just wasn't sure whether when allocating for objects you have to account for any "gubbings", just cautious I guess: type MyTypeA = ptr object of RootObj x: int64 MyTypeB = ptr object of RootObj x: array[0..4, int64] var x0 = alloc(sizeof(int64)); var x1 = cast[MyTypeA](x0); x1.x = 42; echo "x: ", x1.x; dealloc(x1); # or dealloc(x0) var y0 = alloc(sizeof(int64)*5); var y1 = cast[MyTypeB](y0); y1.x = [1'i64, 2, 3, 4, 5]; echo "y1: ", y1.x; dealloc(y1); # or dealloc(y0) Run I've now added your book to my Nim reading list. Thanks
Re: How to instantiate `ptr object`
That is also covered in my book in detail. Have you starting reading? [http://ssalewski.de/nimprogramming.html#_allocating_objects](http://ssalewski.de/nimprogramming.html#_allocating_objects) alloc(), create(), dealloc().