About Monkey 2 › Forums › Monkey 2 Programming Help › Playing with first class functions
This topic contains 19 replies, has 5 voices, and was last updated by
nerobot 1 year, 4 months ago.
-
AuthorPosts
-
December 1, 2017 at 7:57 am #12051
I was interested in – what will we get from map[key] if map’s value type is a function and map doesn’t contain key?
The answer is: We’ll get new first-function instance (or what is name for it?).
Just for experimenting I created NamedEvents class which allow us to subscribe and to emit events by key.
Monkey123456789101112131415Class NamedEvents<K,T>Operator []:T( name:K )Return _events[name]EndOperator []=( name:K,listener:T )_events[name]=listenerEndPrivateField _events:=New Map<K,T>EndShortly:
Monkey1234567891011' type of key is String' type of function is Void()Local evt:=New NamedEvents<String,Void()>' subscribing to "walk" eventevt["walk"] += Lambda()Print "walker 1"End' emit an event somewhereevt["walk"]()And there is a buildable demo showing how you can use it – with String-ed key and with Enum-ed key:
Monkey123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778Namespace myapp#Import "<std>"Using std..Function Main()Print "Hello World"Test1()Test2()EndFunction Test1()Local evt:=New NamedEvents<String,Void()>evt["walk"] += Lambda()Print "walker 1"Endevt["walk"] += Lambda()Print "walker 2"Endevt["walk"]()EndFunction Test2()Local player:=New Playerplayer.MoveEvents[MoveType.Run] += Lambda( speed:Float )Print "player run with speed "+speedEndplayer.Run()EndEnum MoveTypeWalkRunEnd' we can to simplify complex type using aliasesAlias MoveEvent : NamedEvents<MoveType,Void(Float)>Class PlayerProperty MoveEvents:MoveEvent()Global _moveEvents:=New MoveEventReturn _moveEventsEndMethod Run()_speed=10MoveEvents[MoveType.Run]( _speed )EndPrivateField _speed:FloatEndClass NamedEvents<K,T>Operator []:T( name:K )Return _events[name]EndOperator []=( name:K,listener:T )_events[name]=listenerEndPrivateField _events:=New Map<K,T>EndWe can create any events by passing different keys.
I don’t know can it be useful in real life.
December 1, 2017 at 8:06 am #12052It’s like a magic – we don’t put value into Map, but it however is there!
December 1, 2017 at 7:12 pm #12056Very cool!
It’s like a magic – we don’t put value into Map
The magic here is thanks to this code…
Monkey12evt["walk"] += Lambda()...…being converted by monkey2 to…
Monkey1234Local tmp:=evt["walk"]tmp+=Lambda()...evt["walk"]=tmpFor the first line, since the map doesn’t actually contain “walk”, a null value is returned by evt[“walk”].
A null function value is a NOP function (ie: it doesn’t do anything and returns ‘null’) but is still a normal, valid function (just like a null int, ie: 0, is a normal int) and can therefore be used with ‘+’.
The last line is what actually puts the value into the map – magic!
December 1, 2017 at 7:33 pm #12058That’s pretty cool. Mark Sibly is the Gandalf of Sophisticated Basic Compiler Design.
December 1, 2017 at 7:39 pm #12059Hopefully it all makes sense though – that is my goal!
December 2, 2017 at 2:10 pm #12070A null function value is a NOP function (ie: it doesn’t do anything and returns ‘null’) but is still a normal, valid function (just like a null int, ie: 0, is a normal int)
Returning ‘null’-values of different types can be very insidious.
For example, С# have method bool TryGetValue( key, out value ) to avoid returning unexisting values, and indexing operator throw an exception if key not found (msdn).
IMO, for monkey2 will be good at least to improve docs for Map::Get() method and Map::[ ] operator, where would be explained that *you always get ‘default-null’ value here*. And very important to use Map::Contains() if you want to know has map a key of not.
Have Int(0) isn’t the same as have no value at all.
Just to pay attention on it.
December 3, 2017 at 12:22 am #12075That’s really neat, thanks!
I feel like I still don’t take full advantage of first class functions, even though I understand how they work now. It’s a constant exercise of getting used to new design patterns. Probably good for my brain, though!
December 3, 2017 at 3:41 pm #12076Here’s is some (more) discovery of undocumented behaviour..
Is it expected behaviour?
Monkey1234567891011121314151617181920212223242526Namespace myapp#Import "<std>"Using std..Function Main()Local f:Int(Int)' f=fun1+fun2 'returns error "Types must be primitive" at semantingf=fun1' f=f+fun2 'returns error "Types must be primitive" at semantingf+=fun2Print f(5) ' will run both funcs and get the return of the last oneEndFunction fun1:Int(i:Int)Print "fun1: "+iReturn i*2EndFunction fun2:Int(i:Int)Print "fun2: "+iReturn i*3EndDecember 3, 2017 at 3:46 pm #12077and passing by reference could be useful in some designs!
Monkey12345678910111213141516171819202122232425262728Namespace myapp#Import "<std>"Using std..Function Main()Local f:Int(Int Ptr)f=fun1f+=fun2Local j:Intj=5Print f(Varptr j)EndFunction fun1:Int(i:Int Ptr)Print "fun1: "+i[0]i[0]=i[0]*2Return i[0]EndFunction fun2:Int(i:Int Ptr)Print "fun2: "+i[0]i[0]=i[0]*3Return i[0]EndDecember 3, 2017 at 3:53 pm #12078Gandalf of Sophisticated Basic Compiler Design.
I found a new name that would fit fine with google search!
MOINOBA (MOnkey Is NOt BAsic) (erm just joking …)
December 3, 2017 at 7:27 pm #12079Returning ‘null’-values of different types can be very insidious.
You’re probably right, although it does allow the above ‘magic’ to work. Ditto other tricks like non-intrusive ref counting, eg: ‘_refs[obj]+=1’ will ‘just work’.
I’m not really opposed to changing it so that [“blah”] where “blah” is not in the Map causes a runtime error instead. I doubt it’ll cause many/any problems in existing code, but it will break the code at the top you were initially so happy about! Will think about it…
I feel like I still don’t take full advantage of first class functions, even though I understand how they work now
I’m a bit the same way, still learning a lot of this stuff. I do think ‘signals’ (ie: ‘Field Changed:Void()’ style members) are often a way better way to go than virtual methods in many situations.
Is it expected behaviour?
Almost, you’re definitely supposed to be allowed to chain functions together, although ‘f=f+F2’ should work too, will fix!
and passing by reference could be useful in some designs!
I do have a safer ‘Var’ feature on an internal language features todo list…
December 4, 2017 at 2:34 am #12084Almost, you’re definitely supposed to be allowed to chain functions together, although ‘f=f+F2’ should work too, will fix!
I’m not sure that f=f1+f2 is a good idea.
If f1 returns 5 and f2 returns 10 then f1+f2 should return 15. But in our case we get resulting value of the last call only.
But operator += is used as ‘subscription’ operator for a long time and it’s behavior is quite expected.
December 4, 2017 at 2:58 am #12086If f1 returns 5 and f2 returns 10 then f1+f2 should return 15.
Nothing is being invoked though and IMO, f+=f1 should ‘mean’ the same as f=f+f1, regardless of the types of f and f1.
operator += is used as ‘subscription’ operator for a long time
Bah! I hate this ‘reasoning’ for adopting something! To me, ‘+=’ only makes sense because it is possible to ‘add functions’ in the first place with ‘+’ (well, it’s supposed to be…). So there is no ‘subscription’ operator, there is just function addition and ‘+=’ is just an assignment shortcut like all the other forms of ‘+=’.
December 4, 2017 at 3:12 am #12089You’re probably right, although it does allow the above ‘magic’ to work. Ditto other tricks like non-intrusive ref counting, eg: ‘_refs[obj]+=1’ will ‘just work’.
There is an default (t) in c#, don’t know is it possible/ okay for monkey.
With that mechanism we can write
GetValue <T>:T ( key:String,value:T=default (T) )
And it can also create default first class function for us.
December 4, 2017 at 3:21 am #12090> There is an default (t) in c#
This is pretty much ‘Null’ in monkey2, eg:
Monkey12GetValue:T( key:String,value:T=Null )should be about equivalent (as long as type of ‘T’ can be determined of course).
This is largely why I made Null ‘context sensitive’ (again – it was in bmx, I goofed in monkey1) – so that generic methods etc. could ‘Return Null’ and so on without caring what type was being returned.
Indeed, in monkey1 containers there is some awful code like:
Monkey12345678Global _NULL_:TMethod Get:T()...blah...Return _NULL_EndJust to get the same behaviour you can now get in monkey2 from ‘Return Null’! You learn more from your mistakes than your successes etc.
-
AuthorPosts
You must be logged in to reply to this topic.