deathbeam / spoon

:ramen: Spoon is a programming language that runs blazingly fast, compiles to native code and works everywhere.
https://spoonlang.org
MIT License
56 stars 11 forks source link

Dynamic functions #14

Closed deathbeam closed 8 years ago

deathbeam commented 8 years ago

So currently, functions in Raxe are dynamic by default. This means you can do this:

self class

def new()
  myAnotherFunction()
  myFunction() -- This will print "Hello World"
end

def myFunction()
  trace("Hello")
end

def myAnotherFunction()
  myFunction = () =>
    trace("Hello World")
  end
end

But dynamic functions have lower performance. Right now, you can make them fixed by using fixed keyword before class or def.

But some people wants to have them fixed by default, and then we will simply use dynamic keyword to make them dynamic. I am leaving this issue open for discussion.

francescoagati commented 8 years ago

for me dynamics function shouldn't be enabled by default. But instead define a flag class for enable that in class level.

For example

self class

dynamics
def new()
  myAnotherFunction()
  myFunction() -- This will print "Hello World"
end

def myFunction()
  trace("Hello")
end

def myAnotherFunction()
  myFunction = () =>
    trace("Hello World")
  end
end
back2dos commented 8 years ago

You can have the best of both worlds, if you have a non-dynamic function, that checks a variable for an alternative implementation and if available uses that instead. A null-check is pretty fast, so on most targets (I think JS is the only exception here) invoking functions overwritten in this way will be marginally slower than it would be with dynamic, but invoking functions not overwritten will be way faster than it would be with dynamic and that's what you're going to do 95% of the time.

More-importantly: I think it is pretty much non-sense to make it so dynamic in the first place. In Haxe we do meta-programming at compile time and then let the compiler do it's magic. If you use dynamic, you loose inlining, which can work towards more effective DCE and ultimately you get output that is slower and bigger.

Also, by it's very nature, the possibility to change all implementations just like that violates the open/closed principle. It is powerful, but should be used with moderation - or so I think.

deathbeam commented 8 years ago

I have enabled check that if inline keyword is used, function isnt marked as dynamic. But yea, you are right about open/closed principle. But f.e. Ruby is also OO, and it violates it too.

back2dos commented 8 years ago

Sure, that's not a bad start. But it is conceivable that future versions of the compiler may perform inlining automatically, for all methods not overridden in subclasses. With your choice you are already opting out of benefiting from such optimization.

Talking about performance, to me this doesn't matter much, because I use haxe largely for JS development. But the fact is that most haxe developers are on static targets, where it makes a lot of difference. Your project is quite cool, but with this minute choice you're sacrificing a lot of its usefulness. To get the same performance, they will have to prefix every function with fixed, which kind of defeats the purpose of having a Haxe "dialect" with a shorter syntax.

Conceptually, I stand by my objections to just have everything be modifiable by default, but I'd rather not drag it out in a long discussion. Suffice it to say, that like every language, Ruby has it's own flaws. This is not one I would replicate, not only because it violates basic design principles, but because it is simply not worth the trouble. And more importantly, it is something you can easily add later without breaking anyone's code, but if you add it now, removing it later will be hard.

francescoagati commented 8 years ago

@back2dos you can mark all the class with fixed for disable dynamic feature for example

fixed class self

def self.main()
  trace("Hello World")

  def module  = Angular.module("myModule", [])
    .factory(Config.new())
    .controller("AppController", appController)

   def map(x)
     return x+2
  end

   def filter(x)
     return x+2
  end

  def sum = [1,2,3,4,5,6,7,8,9,10]
  .map(map)
  .filter(filter)

end

became

package ;using Lambda;using StringTools; class Main{

static public function main(){
  trace("Hello World");

  var module  = Angular.module("myModule", [])
    .factory(new Config())
    .controller("AppController", appController);

   function map(x){
     return x+2;
  };

   function filter(x){
     return x+2;
  };

  var sum = [1,2,3,4,5,6,7,8,9,10]
  .map(map)
  .filter(filter);

};

}
back2dos commented 8 years ago

@francescoagati Yes. Let me rephrase that: while I think it's a good idea to have a language like Raxe, that uses Haxe's type safety and is more geared towards Ruby's philosophy of conciseness through sensible defaults, I think it is a bad idea to then have users write extra stuff to achieve just normal performance.

In Ruby, where meta-programming can only be done at runtime, having modifiable objects at runtime is arguably a decent default (from which you can opt out by freezing classes). In Raxe, where meta-programming is available at compile time, I think it's not a sensible default, because it is not worth the performance penalty. It will also alienate people who don't know they should prefix things with fixed, because semantically identical code will result in bigger, slower and more cryptic output. And even if they figure that out, if they ever use 3rd party Raxe code, they will suffer the performance penalties from it being dynamic by default. So the sensible thing would be for "dynamicity" this to be an opt-in - as it is in Haxe anyway, with the already existing dynamic keyword.

It will also decrease the barrier between Raxe code, Haxe code and native code. If the Raxe is dynamic by default, then the latter two will just always feel alien. Let's assume that arbitrarily substituting implementations at runtime is a good idea. Any programmer used to this style will hit a wall as soon as they want to apply it to 3rd party code written in Haxe.

I would also point out that in Ruby you cannot just reassign implementations like that. You reopen the class and then work your magic. I think allowing this in Raxe (in restricted places, e.g. those where class declarations are valid) would be a decent compromise. Different Raxe files could all modify a Raxe class, which would result in one final Haxe class, without anything having to be dynamic whatsoever.

deathbeam commented 8 years ago

Your arguments are perfectly valid @back2dos, so going to remove fixed keyword and add optional dynamic keyword then.