munificent / magpie

The Magpie programming language
http://magpie-lang.org
Other
359 stars 34 forks source link

A standard message-passing syntax. #37

Open LeFreq opened 10 years ago

LeFreq commented 10 years ago

If I have understood magpie's philosophy correctly, I would like to talk to you about standardizing on a message-passing syntax that would take OOP languages to a new level. The OOP field has been in disarray for awhile, most have accomidated it because no one really knows how to take the programming art beyond application programming or functional calculus.

Specifically, if you use a standard operator syntax (like C++'s >> and << I/O operators) to pass data in and out of objects, you will avoid the common tendency for people to create little forests of their own personal domain languages -- a taxonomy of objects of which only they really relate. Passing data in and between objects is a common theme in OOP, and no procedural language, to my knowledge, has made a standard, specialized syntax for it (beyond parameter passing).

munificent commented 10 years ago

to pass data in and out of objects

Can you explain what you mean here? That sounds pretty generic, which isn't exactly clarifying.

LeFreq commented 10 years ago

It is "generic" (so to speak). That's the idea. Instead of hundreds of different method invocations passing data in and out, you have ONE standardized way to do it -- and it forces a mindset on the programmers to consider that their objects are for coupling with one another and to design them that way from the start.

Example: msg >> NetworkPort #send a data packet to the network myString >> ch #get a character from a string.

In general, a convention should be made to avoid the symmetry of the operator symbols making a redundant form. Despite the popular idea of "everything should be an object", it would be better to note the one-directional distinction between data and objects, ending an otherwise infinite regress at the bit.

So in the above examples, I am sending a piece of data to an object in the first, but getting a piece of data in the second, even though I'm using the same symbol. The operator would bind to the most privileged Object, in other words to the one most likely to do the "right thing".

roryokane commented 10 years ago

I don’t really understand what @theProphet means, but here is a guess. Maybe this guess can get the conversation moving the right direction.

Methods would be forbidden from initializing fields on an object or returning their values; they can only modify fields. Outside an object, code can initialize and read fields explicitly using two standard methods like << and >>. So instead of this (Ruby):

class Counter
  def initialize
    @count = 0
  end

  def bump
    @count += 1
  end

  # (the long form of `attr_reader :count`)
  def count
    @count
  end
end

counter = Counter.new
counter.bump
final_count = counter.count

You would have to do this, making the data passing explicit:

class Counter
  stores :count

  def bump
    @count += 1
  end
end

counter = Counter.new <<(count: 0)
counter.bump
final_count = counter >>(:count)

I don’t see this as an improvement in this case. It seems like it encourages immutable objects so that you only have to call << once, when you create the object, but there are probably better ways to encourage immutable objects that don’t force you to know so much about the internal state. But maybe that just means Counter is a bad example of people’s “little forests of their own personal domain languages – an ontology of objects of which only they really relate”. I would be interested to see a better example, that is obviously messy, and that could be improved if the objects were forced to use generic data access methods.

LeFreq commented 10 years ago

No. That's actually a different design idea that is somewhat interesting. I'd have to evaluate that further.

But, no, the idea of message-passing involves making semi-private classes that have to be interacted with pre-conceived input and output mechanisms. You're trying to insure both the safety of the class and encourage the class designers to think of their classes as members of a large, dynamic ecosystem.

The concept is akin to requiring you to speak to my ears in order to exert influence, rather than using a scapal (the C way), pills (functors), or having to capitulate to my family language (high-German) to communicate to me.

A standard-message passing syntax makes a universal language for all objects (something like Esperanto) and makes all language designers really think about why they're creating objects that don't communicate to the environment or why they expect hundreds of users to learn their special hyper-personal API.

In closing, your Counter class is a rather bad example, because having such a class implies exposing a meaningless internal artifact to the user in the data ecosystem. Such a thing should be within a hierarchy of class enclosures, manipulated by a master class. But, staying with your example, the usage would be more like this:

myCounter = Counter.new myCounter << ScreenButton && MouseDown

roryokane commented 10 years ago

Let me see if I understand you. In your example, you are connecting the input of myCounter to the output of the compound (decorated?) object ScreenButton && MouseDown. The counter will be bumped when a certain on-screen button is clicked.

So you want a system where manually naming or calling a bump method is unnecessary; input just goes to the object’s “standard input”. And maybe there would be a limited number of standard hooks for each object – like an object’s standard input, standard output, and standard error output. Maybe there would also be one pair of secondary input and output.

When you say you don’t want objects that “expect hundreds of users to learn their special hyper-personal API”, you mean that a Counter doesn’t have a uniquely-named bump method; it just has standard input, so that users of the Counter API don’t have to remember the name of the method that increments the counter.

When I try to imagine a system that follows these rules, I come up with one where people would convert most method names into class names, by splitting behavior into lots of different objects. For example, a String might normally have public internal iterator methods like each_char, each_codepoint, and each_line. That is too many variations to represent through some standard API. So instead of a_string.each_line { |line| print line }, you might do String::Iterators::Line(a_string) >> { |line| print line }. In that example, the iterator object outputs each item through “standard output” to an anonymous function.

Let me think of another example, a Date class. You should be able to read the various fields of a date (year, month, day), as well as get a new Date that is some number of days ahead or behind, and also get the number of days difference between two Dates. With these rules, we can no longer read year, month, and day values through accessor methods like year. Perhaps instead, standard output my_date >> a_record_object would result in the new variable a_record_object containing a hash-map like {year: 2014, month: 9, day: 12}. To read a_record_object, maybe you give the key as standard input with a_record_object << :year and read standard output with a_record_output >> the_year to get the value. To add days to a Date, I suppose you have to use a separate object again: Date::DayAdder(my_date) << 3 >> date_three_days_later (using convenient syntax to both input and output on the same line). And to get the difference, another separate object: Date::DifferenceTaker(my_date, another_date) >> num_days_difference.

I also thought of another interpretation. Maybe I am taking your idea too far, and rather than outlawing all non-standard method names, you are simply suggesting that there be standard method names for just the act of input to and output from an object, while other methods could still have custom names. In that case, Date might have the >> syntax to read a hash-map of values, but unusual behavior like adding days and taking differences would be normal my_date.plus_days(3) method calls. Or you might define those two methods to use the operators + and -, which could also be standard method names – but there is no such shortcut for the String example, where you would still have three different method names like each_line.

Do either of these reflect the system you are thinking of? If not, how might you represent these String iterator and Date examples?

min4builder commented 7 years ago

This is called "concatenative programming", and is a really good idea (think forth + infix)

class Counter
    def init
        @count = 0
    end
    attr :count
end

c = Counter.new
ButtonPressed and KeyPressed >> c.bump

>> would work like unix |.

In other words, >> and << would work as composition operators:

f1(f2(x)) == x >> f2 >> f1 == f1 << f2 << x == f1 << (x >> f2) # etc
min4builder commented 7 years ago

Is this what you mean? (ruby)

class Object
    def >> other
        other.call self
    end
    def << other
        call other
    end
end