I think it is important to distinguish between a variable that can be read from the outside and one that can be both read and written from the outside. The lack of this distinction is one reason why C++ and Java programmers often use getter/setter methods instead of public instance variables. (The other reason is that a setter method can do more than just set the variable (usually to notify some other code about the changed variable))
Ruby has an interesting approach: one can never access an object's variables from the outside, but when you define a getter method and a setter method whose name ends in an "=", it can be used like a variable. That obviously looses the speed of directly writing to a variable, unless some optimization to detect trivial getters/setters is performed.
Here's an example of how it works for clarification:
classFoodefmyvar()
puts("Getter called!")
return@variableenddefmyvar=(new_value)
puts("Setter called!")
@variable = new_value
endend
x = Foo.new
x.myvar = 1# prints Setter called!
x.myvar += 2# prints Getter called!, then Setter called!
puts(x.myvar)# prints Getter called!, then 3
I think a firewall of methods is a good way to protect an object's internal variables.
What Ruby does there is maybe mainly to simplify source code, so that rather than have a method to set myvar, like 'set_myvar(value)', it lets the programmer write simple assignments which are easier to look at and understand at a glance: 'myvar = 0'. It has the same effect as a method, but it's a lot easier to look at.
What Ruby does there is maybe mainly to simplify source code, so that rather than have a method to set myvar, like 'set_myvar(value)', it lets the programmer write simple assignments which are easier to look at and understand at a glance: 'myvar = 0'. It has the same effect as a method, but it's a lot easier to look at.
Exactly. (It also avoids nonsense like "obj.set_myvar(obj.get_myvar()+2)")
We will end up with chips and no software. This is gating further software development, for me at least. Do we want to wait another ten years?
At the risk of sounding like a broken record, you can program in Spin for the P2 today, and have been able to for some time. Lots of people have had success with FlexGUI/fastspin. If you stick with the basic Spin1 subset (with P2 assembly code in DAT) then I suspect you'll be able to port it over very quickly to Chip's Spin2 when that's available. I think the main benefit of Chip's Spin2 will be the smaller code size and (when it's available) self-hosted compilation, but for now you can certainly get a lot done with fastspin.
[I think the main benefit of Chip's Spin2 will be the smaller code size and (when it's available) native compilation, but for now you can certainly get a lot done with fastspin.
Native compilation? Do you mean a self-hosted development environment? Surely, FastSpin already compiles to native code.
We will end up with chips and no software. This is gating further software development, for me at least. Do we want to wait another ten years?
Actually, it was DONE yesterday, but there has been one more thing I've wanted to add for the first release that I'm implementing today. Just a wafer thin mint. I still need to write the documentation, anyway.
I'm putting a stealth method pointer into the call-stack frame which can be used to vector output anywhere, without the need for passing discrete method pointers around. Methods can set it to whatever they want, as they go, and any methods called inherit the current pointer. It's going to be dreamy.
I'm putting a stealth method pointer into the call-stack frame which can be used to vector output anywhere, without the need for passing discrete method pointers around. Methods can set it to whatever they want, as they go, and any methods called inherit the current pointer. It's going to be dreamy.
Could you describe exactly how this works (from a programmer's perspective, not from the compiler's)? The general idea seems promising, but it also seems like there are a lot of ways that it could go wrong and cause a beginner to get hopelessly tangled up...
[I think the main benefit of Chip's Spin2 will be the smaller code size and (when it's available) native compilation, but for now you can certainly get a lot done with fastspin.
Native compilation? Do you mean a self-hosted development environment? Surely, FastSpin already compiles to native code.
Yes, I did mean self-hosted, thanks. I've corrected my original post.
PUBdoit
send := @serial.tx
send("This is a test... ", hex(x), 13)
PRIhex(num)repeat8
send(lookup((num ROL= 4) & $F: "0".."9", "A".."F"))
SEND is a method pointer located at long[dbase][-2]. It can be assigned and read. If assigned zero, which is the initial state on cog start, SEND() doesn't do anything.
SEND() is a special method that the compiler generates which has hidden sub-methods for handling strings of bytes, single values, and multiple return values from methods.
I haven't been closely following the Spin2 development, so excuse me for the following dumb question. Will Spin2 methods be able to handle a variable number of parameters? And how does it distinguish between long parameters and string pointers?
I haven't been closely following the Spin2 development, so excuse me for the following dumb question. Will Spin2 methods be able to handle a variable number of parameters? And how does it distinguish between long parameters and string pointers?
At this time, Spin2 methods have fixed numbers of parameters. That could change in the future. For your second question, I'm not sure what you mean.
Chip, in your example you show two different calls to send:
send("This is a test... ", hex(x), 13)
send(lookup((num ROL= 4) & $F: "0".."9", "A".."F"))
In the first case there appear to be 3 parameters, which are a string, the return value of hex() and 13. In the second case there is one parameter, which is the value returned by lookup. Maybe the first call is actually only one parameter with the values of hex(x) and 13 included in the string. However, in the first call you are passing a string pointer, and the second call passes a long value. How does send, or tx know the difference?
The compiler builds the SEND() instance with three different ingredients. It's much different than a normal method call. For every element, the method pointed to by SEND is called with the element as a single parameter, effectively like 'serial.tx(element)'.
1) "This is a test..." gets recognized as a byte string and some bytecodes in the interpreter execute to send all those bytes to the SEND method.
2) 'hex(x)' gets called and then, afterwards, any return values it generated get sent the SEND method. Of course, hex() may do its own SEND() operations.
3) '13' gets sent as a single value to the SEND method.
I think these three cases cover everything, although a z-string output could be added. It might need a keyword.
I think that it would be useful to have a shorter notation for a string pointer, something like:
ser.write(@"hello, world")
instead of
ser.write(string("hello, world"))
I realize that for this particular example you could use the new "send" method instead, but in general strings can be awkward to use in Spin and having an easier way to get pointers to them would be nice.
Chip, thanks for the clarification. To me it seems that if you can do send("abc", hex(5), 13) you should also be able to do serial.tx("abc", hex(5), 13). Of course, this would mean that you don't need the hidden send method pointer. You should just be able to use any method pointer or direct call.
I think that it would be useful to have a shorter notation for a string pointer, something like:
ser.write(@"hello, world")
instead of
ser.write(string("hello, world"))
I realize that for this particular example you could use the new "send" method instead, but in general strings can be awkward to use in Spin and having an easier way to get pointers to them would be nice.
Eric, I like that notation. It's easy to parse, too, because it's just @constant(s). That would be good to replace string() with.
Chip, thanks for the clarification. To me it seems that if you can do send("abc", hex(5), 13) you should also be able to do serial.tx("abc", hex(5), 13). Of course, this would mean that you don't need the hidden send method pointer. You should just be able to use any method pointer or direct call.
Wouldn't we still need the hidden pointer to give hex() an output method?
Public / private syntax gets annoying. If it's not explicitly declared public or shared, it's private.
KISS
I agree.
CON shared i = 2, j = 5, k = 11
m = 1, n = 2VAR shared x,y,x
a,b,c
We already have public / private syntax (DAT vs VAR) so if DAT variables were accessible by name we wouldn't need to worry about declaring "shared". CON sections are always accessible via obj#constant syntax but variables are not unless explicitly declaring setter/getters. If you wanted to trigger some OTHER more complex behavior, you could always do it the way we have BUT... The idea of being able to access "variables" (even if the limitation is they must be declared in DAT) is very promising. I wonder if @Rayman thinks this would be a reasonable compromise? I find myself creating a lot of setter/getters and I think the declaration of private vs public is inherent..
So, the CONs are all shared already with # to get them...
There would have to be some way to make var public or private...
We have PUB and PRI. Maybe we need VAR and something else for public variable section...
maybe EXT for "external"?
Things in VAR section would be private, things in EXT would be public...
Was just thinking that it would be nice if you could change a "VAR" in a sub-object from a higher level object...
For example, I'm playing around with a VGA driver and would be nice if could do:
vga.boxcolor := $10
To set the VAR section defined variable, "boxcolor", to the value $10
Instead, I currently need to create a function like "SetBoxColor" in the VGA driver as a work around....
+1 for this
... and I would also add two events, if possible. To maintain the same example with the VGA driver, it will be nice that if the compiler founds two subs (in the VGA driver object):
Comments
Yes, I can see that. We really only NEED methods.
Ruby has an interesting approach: one can never access an object's variables from the outside, but when you define a getter method and a setter method whose name ends in an "=", it can be used like a variable. That obviously looses the speed of directly writing to a variable, unless some optimization to detect trivial getters/setters is performed.
Here's an example of how it works for clarification:
class Foo def myvar() puts("Getter called!") return @variable end def myvar=(new_value) puts("Setter called!") @variable = new_value end end x = Foo.new x.myvar = 1 # prints Setter called! x.myvar += 2 # prints Getter called!, then Setter called! puts(x.myvar)# prints Getter called!, then 3
No need to change 30 years of semantics at this point...
What Ruby does there is maybe mainly to simplify source code, so that rather than have a method to set myvar, like 'set_myvar(value)', it lets the programmer write simple assignments which are easier to look at and understand at a glance: 'myvar = 0'. It has the same effect as a method, but it's a lot easier to look at.
We will end up with chips and no software. This is gating further software development, for me at least. Do we want to wait another ten years?
At the risk of sounding like a broken record, you can program in Spin for the P2 today, and have been able to for some time. Lots of people have had success with FlexGUI/fastspin. If you stick with the basic Spin1 subset (with P2 assembly code in DAT) then I suspect you'll be able to port it over very quickly to Chip's Spin2 when that's available. I think the main benefit of Chip's Spin2 will be the smaller code size and (when it's available) self-hosted compilation, but for now you can certainly get a lot done with fastspin.
Actually, it was DONE yesterday, but there has been one more thing I've wanted to add for the first release that I'm implementing today. Just a wafer thin mint. I still need to write the documentation, anyway.
I'm putting a stealth method pointer into the call-stack frame which can be used to vector output anywhere, without the need for passing discrete method pointers around. Methods can set it to whatever they want, as they go, and any methods called inherit the current pointer. It's going to be dreamy.
Could you describe exactly how this works (from a programmer's perspective, not from the compiler's)? The general idea seems promising, but it also seems like there are a lot of ways that it could go wrong and cause a beginner to get hopelessly tangled up...
Yes, I did mean self-hosted, thanks. I've corrected my original post.
PUB doit send := @serial.tx send("This is a test... ", hex(x), 13) PRI hex(num) repeat 8 send(lookup((num ROL= 4) & $F: "0".."9", "A".."F"))
SEND is a method pointer located at long[dbase][-2]. It can be assigned and read. If assigned zero, which is the initial state on cog start, SEND() doesn't do anything.
SEND() is a special method that the compiler generates which has hidden sub-methods for handling strings of bytes, single values, and multiple return values from methods.
PUB doit send := @serial.tx send("uppercase a is", upper1("a")) PRI upper1(x) : r r := x + "A" - "a"
How is that distinguished from:PUB doit send := @serial.tx send("uppercase a is", upper2("a")) PUB upper2(x) send(x + "A" - "a")
This mechanism can be used to vector data streams anywhere.
At this time, Spin2 methods have fixed numbers of parameters. That could change in the future. For your second question, I'm not sure what you mean.
send("This is a test... ", hex(x), 13) send(lookup((num ROL= 4) & $F: "0".."9", "A".."F"))
In the first case there appear to be 3 parameters, which are a string, the return value of hex() and 13. In the second case there is one parameter, which is the value returned by lookup. Maybe the first call is actually only one parameter with the values of hex(x) and 13 included in the string. However, in the first call you are passing a string pointer, and the second call passes a long value. How does send, or tx know the difference?1) "This is a test..." gets recognized as a byte string and some bytecodes in the interpreter execute to send all those bytes to the SEND method.
2) 'hex(x)' gets called and then, afterwards, any return values it generated get sent the SEND method. Of course, hex() may do its own SEND() operations.
3) '13' gets sent as a single value to the SEND method.
I think these three cases cover everything, although a z-string output could be added. It might need a keyword.
ser.write(@"hello, world")
instead ofser.write(string("hello, world"))
I realize that for this particular example you could use the new "send" method instead, but in general strings can be awkward to use in Spin and having an easier way to get pointers to them would be nice.
Eric, I like that notation. It's easy to parse, too, because it's just @constant(s). That would be good to replace string() with.
Wouldn't we still need the hidden pointer to give hex() an output method?
I'm not sure what's better though... Could it be $ or & or maybe > ?
*?
We already have public / private syntax (DAT vs VAR) so if DAT variables were accessible by name we wouldn't need to worry about declaring "shared". CON sections are always accessible via obj#constant syntax but variables are not unless explicitly declaring setter/getters. If you wanted to trigger some OTHER more complex behavior, you could always do it the way we have BUT... The idea of being able to access "variables" (even if the limitation is they must be declared in DAT) is very promising. I wonder if @Rayman thinks this would be a reasonable compromise? I find myself creating a lot of setter/getters and I think the declaration of private vs public is inherent..
Just my 2c.
I've just tested Pnut v33p for the LOC bug and found it is still present. Further reading - https://forums.parallax.com/discussion/comment/1457051/#Comment_1457051
Here's my latest testing source repacked to suit Pnut. And also the correct output of Fastspin along with the buggy output of Pnut.
Fastspin
LOC PA, #label testing ========================= CogExec PC Op-code PA-data Cog-dref Hub-dref LUT-dref cog008 00000015 fe9ffff2 00000008 11111111 00000000 fdb00078 hub110 00000019 fe9000f6 00000110 52525252 22222222 00000000 hub4a4 0000001d fe8004a4 000004a4 00000000 33333333 fd640200 lut3a0 00000021 fe8003a0 000003a0 facbeff6 00000000 44444444 HubExec PC Op-code PA-data Cog-dref Hub-dref LUT-dref cog008 00000468 fe800008 00000008 11111111 00000000 fdb00078 hub110 00000478 fe9ffc94 00000110 52525252 22222222 00000000 hub4a4 00000488 fe900018 000004a4 00000000 33333333 fd640200 lut3a0 00000498 fe8003a0 000003a0 facbeff6 00000000 44444444 LutExec PC Op-code PA-data Cog-dref Hub-dref LUT-dref cog008 000003a7 fe800008 00000008 11111111 00000000 fdb00078 hub110 000003ab fe800110 00000110 52525252 22222222 00000000 hub4a4 000003af fe8004a4 000004a4 00000000 33333333 fd640200 lut3a0 000003b3 fe9fffec 000003a0 facbeff6 00000000 44444444
Pnut
LOC PA, #label testing ========================= CogExec PC Op-code PA-data Cog-dref Hub-dref LUT-dref cog008 00000015 fe9fffc8 000fffde 8607ee30 00000000 fa97fe3e hub110 00000019 fe9003d8 000003f2 fd63ec18 00000000 fd6bec18 hub4a4 0000001d fe8004a4 000004a4 00000000 33333333 fd640200 lut3a0 00000021 fe900df8 00000e1a fda004a8 00000000 fd7c002d HubExec PC Op-code PA-data Cog-dref Hub-dref LUT-dref cog008 00000468 fe800008 00000008 11111111 00000000 fdb00078 hub110 00000478 fe800110 00000110 52525252 22222222 00000000 hub4a4 00000488 fe900018 000004a4 00000000 33333333 fd640200 lut3a0 00000498 fe8003a0 000003a0 facbeff6 00000000 44444444 LutExec PC Op-code PA-data Cog-dref Hub-dref LUT-dref cog008 000003a7 fe9ff180 000ff528 fd8003a1 9e3e3e39 00000000 hub110 000003ab fe9ff590 000ff93c 2aa3ec03 21030111 00000000 hub4a4 000003af fe8004a4 000004a4 00000000 33333333 fd640200 lut3a0 000003b3 fe9fffb0 00000364 6f432020 00000000 00000000
There would have to be some way to make var public or private...
We have PUB and PRI. Maybe we need VAR and something else for public variable section...
maybe EXT for "external"?
Things in VAR section would be private, things in EXT would be public...
+1 for this
... and I would also add two events, if possible. To maintain the same example with the VGA driver, it will be nice that if the compiler founds two subs (in the VGA driver object):
PRI boxcolor_evGet
it execute it before an external read accessand executes this
PRI boxcolor_evSet
after an external write accessvga.boxcolor := $10