Operators vs Methods

Discuss the future of the AutoHotkey language
iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Operators vs Methods

25 Sep 2018, 11:04

See: Commands vs Functions
Continued from here: https://autohotkey.com/boards/viewtopic ... 37&t=43593
nnnik wrote:Also contains is an operator rather than a method call.
There is an equivalence between operators and methods.

Code: Select all

2 + 3
2.+(3) ; strange but should be valid
which seems strange. But consider this code:

Code: Select all

object.push("dog", "cat")
object push ("dog", "cat")
which should be command syntax for object methods.

To parse everything correctly:

Code: Select all

obj push ("dog", "cat")
obj(push("dog", "cat")) ; obj is not a function and does not take arguments. 
obj.push("dog", "cat") ; try invoking a method of obj. 
So this is the sort of radical change I'd like to see in v2. It would do 3 main things.
- Settle the Command vs. Function syntax argument once and for all by allowing both commands and functions to have the same expressiveness.
- Formalize operators like contains, such that operators are in some sense global methods, and object methods are by default limited to that object
- Allow operator overriding.

I took inspiration from here: https://docs.scala-lang.org/style/metho ... ation.html
also: https://stackoverflow.com/questions/102 ... t-notation
Last edited by iseahound on 28 Sep 2018, 15:50, edited 2 times in total.
coffee
Posts: 133
Joined: 01 Apr 2017, 07:55

Re: Operators vs Methods

25 Sep 2018, 23:09

Uffffffffffff, what in gods name happened here...
There is an equivalence between operators and methods.
Nope, there isn't. I suppose the next statement is, there is an equivalence between operators and functions, there is an equivalence between operators and labels. Nope, there is not.
That nope goes from the autohotkey level, down into c++, slapping c then heading down into assembly, pooling into binary then as electrons leaving the cpu.
It shouldn't be possible to confuse the idea of operators, with the idea of methods/functions or with the idea of an object with its own/inherited members.

2.+3
So is that a method called +3, or a method called 3 after +3 is evaluated, or is it a method called + that is called with no parenthesis and all the arguments are defined one after the other? 2.+3"hey""another one", or is a combo operator called .+ and behaves like += with a variable called 2? or is it there a default method to which +3 is passed to?
Oh ok, it's an integer, exposed as an object, and the . is parsed as do onto self the + of the value of the this other fake integer emulation called 3 right next to it. Makes perfect sense.
So the . is not accessor? it's the do self operator? Or is 2 a reference? 2.+3 add three to self, 2.=3 or 2.3 assign three to self. Checks out.

Do you know what the dot does? like the concept of the dot? What its role is in obj.method() or obj.field? Not in obj . method(), but obj.method().
Do you know what obj is? what obj could possibly contain? the concept of an reference?
Do you know the difference between the concepts of an integer, float, string, object?
You are not adding a method to an object when you do obj.method, you are not really operating method onto object, or object onto method.
Do you know the difference between a . and ( ? Do you know what () is? why there is a ( and a )?
Do you think the ( is an operator? Do you think ( has the same purpose of a . in an expression or a dereference? That the underlying concept they are doing is interchangeable? That the action they are performing is the same?
Do you think obj.method() is the same as obj(method() ?
Do you think obj.method() is in reality obj.method(). ? Hence the thought process obj(method()) ?
Do you think in obj.params, that params is a parameter of the object?

The dot is convention, but let's do obj%method<param>
Or even better obj(method.param.
Roll the same questions.

Boils down to something as simple as is 2+3 the same as 2*3? can you represent 2*3+1 without using the symbols or using the same symbols in every combo and complex mathematical expression?

Do you think this sentence. "Hello, my name is John." Is the same as "Hello. my name is John," ?
Do you think getting a book from the shelf and going through its table of contents in whatever manner you decide, is the same as going to the page, reading it, then coming back to the table of contents? Are they the same action?
obj(push("dog", "cat")) ; obj is not a function and does not take arguments.
Orly, why wouldn't it be a function? because it's an object?

class xyz {
// bla bla
abc() {
return "bleble"
}
}

xyz(param) {
return "blabla"
}

abc() {
return "bloblo"
}

xyz(abc())

??????????????????????????????????
which should be command syntax for object methods.
No, no, no it shouldn't. Never should. The concept of a dot is not the same as a space, or a parenthesis. The concept of an object is not the same to that of a function.

Code: Select all

object push "dog", "cat" ;// defined as both, I want to concatenate the variable push with "dog", what do you do? push is a method, the obvious conclusion; push is a variable, the obvious conclusion.
Scrolling through one included file, and its definition is dumped into some other include, what is this? garbage(callmeOrNot(syntax(maybe())), arg2(arg3)) ? define it, without looking at the file it is defined in, which is not going to be 20 lines. Tell me the functions, tell me the objects. Which one is the method.
Function syntax argument once and for all by allowing both commands and functions to have the same expressiveness
Do you think obj.method is the problem with function syntax? Since when? it has never been part of the argument. Do you think the . represents a function call?
Do you think the idea when you do obj.method() is to get method as a value from object and then the value returned from method?
A space, a comma and a parenthesis enter into a bar, the dot gets shot in the head. The end.
It would only take like 5 minutes to enforce command(args) on users as opposed to do whatever 2.+3 is.
The function vs command issue is calling functions and commands. Nothing to do with the dot or objects, not sure why they are now being brought here.

The reason you can do str.contains() in other languages is because at the scripted layer, str is exposed as an object and contains is a method of of that object, call it a "characteristic" that it has. contains in these cases in not an operator. Autohotkey does not expose regular variables or strings for that matter as objects. There are plenty of examples where you can emulate this using the base object, you will have the added overhead however. Define your own function and do str.contains()

Variables in python are exposed as objects (and are), Variables in javascript are exposed as objects (and are). Variables in autohotkey are not exposed as objects (and are). Only variables containing object references are exposed as objects. Not even sure how much time it would take to rework variables to dereference to objects, and if they would retain the same performance in the current implementation of objects, which would also lead to all string functions to be methods for string type.
such that operators are in some sense global methods
Image
iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: Operators vs Methods

26 Sep 2018, 16:28

Before anyone reads the rest of this post, I'd recommend reading this rather influential article about "The Blub Paradox".

Excerpt:
As long as our hypothetical Blub programmer is looking down the power continuum, he knows he's looking down. Languages less powerful than Blub are obviously less powerful, because they're missing some feature he's used to. But when our hypothetical Blub programmer looks in the other direction, up the power continuum, he doesn't realize he's looking up. What he sees are merely weird languages. He probably considers them about equivalent in power to Blub, but with all this other hairy stuff thrown in as well. Blub is good enough for him, because he thinks in Blub.
Now to refute coffee's stance denying a equivalence between operators and methods, I'll link to this C++ operator overloading tutorial. After pressing execute, anyone can see the volume of Box3 after adding Box1 and Box2 together. Box3 = Box1 + Box2;

Code: Select all

Volume of Box1 : 210
Volume of Box2 : 1560
Volume of Box3 : 5400
diving deep into the code I see:

Code: Select all

class Box {
      // ...
      // Overload + operator to add two Box objects.
      Box operator+(const Box& b) {
         Box box;
         box.length = this->length + b.length;
         box.breadth = this->breadth + b.breadth;
         box.height = this->height + b.height;
         return box;
      }
      // ...
};
In Python:

Code: Select all

import math
 
class Box:
 
    def __init__(self, length, breadth, height):
        self.__length = length
        self.__breadth = breadth
        self.__height = height
 
    def volume(self):
        return self.__length * self.__breadth * self.__height
 
    def __add__(self, _box):
        return Box(self.__length + _box.__length, self.__breadth + _box.__breadth, self.__height + _box.__height)

Box1 = Box(6, 7, 5)
print(Box1.volume()) # 210
 
Box2 = Box(12, 13, 10)
print(Box2.volume()) # 1560
 
Box3 = Box1 + Box2
print(Box3.volume()) # 5400
So what is going on? Let's examine what's happening in Python where the code Box3 = Box1 + Box2 is returning a new object called Box3. It seems that the __add__ method is defining what the + operator does. I wonder if there is a duality between operators and methods in Python...

Code: Select all

Box3 = Box1.__add__(Box2)
print(Box3.volume()) # 5400
It looks like Box3 = Box1 + Box2 is equivalent to Box3 = Box1.__add__(Box2)!
User avatar
nnnik
Posts: 4500
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: Operators vs Methods

27 Sep 2018, 00:45

This might be possible in v2. However multiple parameter calls as you made them before are not. It's a nice thing but tbh it's rarely useful.

The amount of times you can use a standard maths operator to express an action that does a mathematical addition you are pretty much limited to yeah maths and only maths.
And you could cover all kinds of maths completely with a build in library - at least any kind of maths that will ever be needed by AHK.

People will use it for all kinds of strange syntax that essentially only makes their code unreadeable.
Also they might write their code poorly and not do type checks.
Aditionally we will get more special keys that block the standard array behavior in AHK - making a use of these require ObjRawGet and ObjRawSet at almost all times.

Also you have 2 boxes with a position, a size a velocity, an acceleration, a rotation, an angular velocity, an angular acceleration and with specific weights from their materials.
What does adding this type of box to another do?

Operator overloading is not that useful to begin with. It's limited to a specific domain. The amount of times it will hinder people by blocking keys is higher than the amounts it will be used correctly over the entire language.
But even worse - the amount of times it will be misused and will be used in a harmful way is far greater than the amount of correct uses.

It sets out to do 2 things: Allow polymorphic code between normal number types and specialized classes - and it allows things to look nicer than using .add on those objects.
While the first argument is a valid goal, operator overloading mostly fails to do that. The classes still need special handling and changes in most of the cases making polymorphic code extremely difficult and impossible if you add classes that are only slightly incompatible.
The second goal is nice but not necessary. The argument is often that a domain expert is supposed to check it and he should immediately understand whats going on.
2 problems - operator overloading might have problems under the hood that are unseen by the domain expert and might require you to use a conventional method - At which point you have to convert it for the domain expert anyways.
Even operators do not look natural to the domain expert.

The downside of adding operator overloading is a massive amount of new bugs that will be introduced. They will be misused - just like you misused them in your example.
Meanwhile the upside is barely existant.
Recommends AHK Studio
iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: Operators vs Methods

28 Sep 2018, 16:08

Sure. It might be better to just enclose multiple parameters in parenthesis.

Code: Select all

Boxes := Box1 + (Box2, Box3, Box4)
but realistically parameters should be curried

Code: Select all

Boxes := Box1 + Box2 + Box3 + Box4
so the code in the other thread would become if (var contains ("abc", "def", "ghi")) { to maintain the current AutoHotkey comma behavior. I've edited the examples on the top post.

The point is to generate functions using partial evaluation, allowing for cleaner code. You're not really supposed to use multiple parameters with operator syntax, the idea is to take things one step at a time. The motivation is to write higher/lower abstractions, such as map or monads, and to provide the first step in allowing for functional programming techniques such as pattern matching. It's a different style of programming, neither inferior nor superior to object oriented; it uses a different approach. Finally bugs will be heavily reduced since functional programming depends on a type system, which I assume is the point of v2?

Finally programming is math. Some people might call it propositions as types or the curry howard correspondence, maybe even define functional programming as category theory for programmers. It's not my role to help you see where the mathematics is in your code, but if would like to begin, try looking at the chart below taken from here.
Image
Make your functions purer, separate effects, and stop hiding variables inside objects!
User avatar
nnnik
Posts: 4500
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: Operators vs Methods

29 Sep 2018, 02:47

Of course one should avoid implicit hardcoded relations.
I completely fail to see how that relates to the current topic.
Programming is 25% maths at most - even in calculation heavy applications.

Your suggestion about the () and the , to solve the problem of AutoHotkeys current menting of them does not actually solve the problem:
Both if (function(), xyz contains a := function2()) and if xyz contains (function(), a := function()) already make sense in AHK.

Also we wont add a new programming paradigm to AHK v2 when it is about to go from alpha to beta.
At most this could happen when it goes from v2 to v3. However the logic involved would require a complete rewrite.
Recommends AHK Studio
iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: Operators vs Methods

29 Sep 2018, 08:09

The fact that it already makes sense is the reason why I'm advocating it. Why is "contains" an operator but object methods are not? You're advocating for special treatment of words like "is" and "contains". I'm saying get rid of that. AutoHotkey has types right? And each type has a fundamental base class. Clearly, symbolic methods such as "+" work on terms of a number type. Wasn't there an entire thread devoted to strong comparisons, by making ">" work on terms of a string type?

You've missed the entire point of this discussion by the way. Clearly if you define "<" and ">" for in a string base class, you can now reuse your quicksort algorithm to alphabetically order strings. And if you can order strings in arbitrary abc-order, what's to say that you can't order box objects from biggest to smallest? Just define a symbolic method for ">" and "<" and you can reuse the quicksort algorithm for numbers. It's so easy.

And really, 25%? That's rich. I thought lambda calculus was still taught to programmers. I suppose by calculation heavy you mean "number-crunching"? If you studied lambda calculus, you'd realize that counting numbers can be inductively defined. In fact, our base-10 representation of natural numbers is isomorphic to the ones defined by lambda calculus, Peano Arithmetic, and binary representation. The point is that induction is a powerful tool that can be used to define monoids, semigroups, and even rings. Forget about numbers = math, programmers care about structure. And of course you might think that well this is all very good and abstract but I can just call MsgBox and a box appears on screen. Like that's what programming is, sir. And well that's like adding water to instant pancake mix and saying "pancakes are EZ" when clearly, someone had to do most of the work. Aren't pixels just points on a screen? So an array of pixels would be your screen. Isn't resource management and memory allocation mathematics as well? Just because it's easy for you, doesn't mean that some windows engineer back in 1995 had to figure out how to display a box on a screen. And frankly that's downright insulting to anyone who's had to engineer anything.
iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: Operators vs Methods

29 Sep 2018, 08:58

So while checking for operator precedence in the manual, I realized words like in, contains, aren't even operators. They just work in if statements, not even in ternary statements! Anyways, if this does get implemented, words like contains will become real operators, and can be used in expressions outside of if (expression).

There are some caveats with implementing this:
- All types now have a base class.
- Arity-0 Examples: getLine() and getLine will become equivalent.
- custom operator precedence (could be added later)
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Operators vs Methods

29 Sep 2018, 09:09

So while checking for operator precedence in the manual, I realized words like in, contains, aren't even operators.
Look in the relevant manual.

Cheers.
User avatar
nnnik
Posts: 4500
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: Operators vs Methods

29 Sep 2018, 09:35

@offtopic:
I don't really like the word number crunching - it makes me feel like it's missing the point. When I made that note I thought about Artificial Neural Networks and a Physics Engine.

And yes 25% seems to be correct to me. You always handle data structures and prepare buffers for those data structures and then you connect to the OS in some way to fill those data structures with data from a file...
If all of these actions are hidden and defined by the language you are using in some sort of Interface, then the language has 2 options.
It either defined an Interface that uses the domain specific sub-language to let you control the specifics of that domain using it's own mechanisms, styles and paradigms or it doesn't let you control those specifics at all.
For a language like AutoHotkey that aims to take control over other systems, the first is the better option - of course you can have both (like ControlSend and PostMessage/SenMessage).

Memory allocation and resource management are not maths. Lists of data are not maths. Strings are not maths. Encodings are not maths. Execution order is not maths. Data structures are not maths....
Communicating with the OS or any kind of communication with anything is not maths.
These things can be expressed in some way in maths - but at their core they are not maths themselves.
Having a domain specific language and extending that domain specific language to express something out of this domain is not uncommon.
However it being expressed in that language does not mean that it is part of that language.

Same goes for maths - it is a language that can be used to express the relation between variables using definite numbers.
However the specific relations that are described themselves are not part of maths - maths is just a method for description.
While the calculation(description of a relation) e=m*c² itself is maths, it's context and meaning are clearly physics.
Informatics often requires specific relations that are hard to describe in the domain of maths - yet it also uses maths for description.

@Topic:
Getting back on point - AHKs type system is barebones to non-existant. You can barely check if something inherits from a specific class.
There is no seperation between the base class of an integer or a string. If you would redefine the greater than operator for strings you would do the same for integers.

I also see the benefit in redefining < and > and so on. However barely anything has a clearly defined natural order.
Is the box with bigger volume or the box with more surface area greater?
These things depend on the situation. Being able to define how exactly the comparison on the spot is better than having a hard coded method in your base class.

If I was the one completely controlling the source I would say I'd add this ability sooner or later as I do see the benefit.
However there are more important things that need to be dealt with first - like the type system exceptions etc.
The parser would have to be rebuild...

As it stands now your suggestion requires AHK to change in a way that would involve rewrites in major parts - that in a situation when we are focusing on releasing v2 alpha.
Lastly you seem to misunderstand my intentions. I do not completely disagree with your points it's just that practically speaking this is not possible right now.
I'm not denying the ideal - I'm saying that the world is far from ideal and that reaching this ideal is currently not feasible.
Recommends AHK Studio
iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: Operators vs Methods

30 Sep 2018, 20:16

Thanks Helgef.

@nnnik
I've always thought that command syntax could be defined in an S-expression like in Lisp to capture the return value. Like if Abs(-3) is equal to Abs -3 then int := (Abs -3) would capture the output of the command syntax. I've an affinity for RegEx and string parsing, so I might underestimate how difficult that is (although implementation is very difficult for me).
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Operators vs Methods

02 Oct 2018, 10:36

int := (Abs -3) is already valid, it is concatenation. I think that is more useful than interpreting it as a function call.

Cheers.
User avatar
nnnik
Posts: 4500
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: Operators vs Methods

02 Oct 2018, 12:18

Its not concatenation it's the same as Abs - 3
Recommends AHK Studio
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Operators vs Methods

02 Oct 2018, 12:33

Yes ofc. :P
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Operators vs Methods

03 Oct 2018, 21:21

@iseahound: Why so ethereal about this?
- functions are like maths: y = f(2) where f(x) = x**2
- operators are like maths: y = 1 + 2
- ... this says nothing about any behind-the-scenes implementation details
- (commands/methods are functions)

- How concerned are you about adding new syntax features, and the requisite parsing required, and it being a source of bugs and maintainability/update difficulties. As a general principle for all programming languages.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: Operators vs Methods

04 Oct 2018, 12:06

Not really adding new syntax features, just generalizing from special cases present in the old syntax. It looks like I'm proposing new syntax, but it's just a behind the scenes rewiring of logic. Idea is to create proper type classes, such as an integer base class, a float base class, string base class, etc. Of course, type casting will still be implicit, so to the regular AHK user nothing has changed.

Finally for advanced users, special objects can be created and composed together. It's unlikely the plus operator will be redefined, except maybe to add complex numbers on occasion. It's not really about adding Boxes either, but more about monoids, magmas, semigroups, etc. And higher types as well, function types, functor types, endofunctors, etc. Whatever mathematicians can come up with.

Command Syntax feels like an evolutionary dead end, and that bothers me. Not everything in AHK is a function and most "functions" are the Identity function + some effect. For example, something like Click, %x%, %y% if converted to a function will either return nothing, or x and y. Clearly there is something else going on here. That something is an action that occurs when click is run, and action that affects the outside world, and the consequences of that action can not be represented within our code. Seriously, no one is going to write Click(X,Y) unless they understand functional programming.

Which is what AHK is missing out on. Simple functions/operators such as map. Why not allow every function reference have a map? So to flesh out our previous example, imagine we have a List of [X, Y] coordinates and we want to click them in sequence. Grab a reference to the click function and apply it to the entire list. ClickRef := Func("Click") then ClickRef.map(List) or maybe more intuitively, Func("Click") map List or List map Func("click")?

I'm not saying that this is the right answer, and typically I've found AHK intuitive for the most part. I'm just throwing some ideas out there, some of them only partially researched.
User avatar
nnnik
Posts: 4500
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: Operators vs Methods

04 Oct 2018, 12:23

You don't need to know functional programming to know functions - procedural programming is enough.
I dont quite know what you are refering to since there is no command syntax in v2.
Recommends AHK Studio
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Operators vs Methods

04 Oct 2018, 16:04

- @iseahound: Why would you want a class for each type?
- var.Method(), "string".Method() etc could be useful, or do you want something more?

- 'monoids, magmas, semigroups ... function types, functor types, endofunctors'. Hello. Any decent links?

- 'Click' is a very unusual command, so if you wanted to make a point, I'd choose a different command/function.
- (Click and MouseClick will probably be combined, I prefer the MouseClick syntax as a basis.)
conversion logic, v1 = -> v1 := -> v2, two-way compatibility - Page 7 - AutoHotkey Community
https://autohotkey.com/boards/viewtopic ... 98#p203698

- Although it can be good sometimes to think big. If forced to restrict yourself, what would be your most wanted features e.g. between 3 and 10 ideas. Same question to nnnik. Cheers.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: Operators vs Methods

04 Oct 2018, 22:19

Hmm... I'd want a type for each type. Maybe having a class for each type isn't the best way to limit the scope of a set of operations.
jeeswg wrote:'monoids, magmas, semigroups ... function types, functor types, endofunctors'. Hello. Any decent links?
Hang around Hacker News and r/programming.

monoids, magmas, semigroups are just simple mathematical objects. Our system of 2 binary operations (+, x) is a ring.

Perhaps this video series will interest you?

I don't think I'm qualified to recommend stuff, since even I'm still learning stuff everyday. It's very tedious, and at times meaningless. Sometimes the differences are so subtle its hard to figure out what is being talked about. They're always talking about syntax vs semantics, the inside of an object vs. the outside of an object... it's all very philosophical and mathematical. Haskell is a language that's learned by mathematicians and for that reason it's hard to do useful stuff with it. They even have a series called "Real World Haskell" because there's so much navel-gazing and lack of concrete use cases. On the other hand, AutoHotkey is very useful. Anyone can pick it up. The "hello world" in AHK is literally #n:: Run Notepad.exe which is way more useful than all the output text to console that every other language uses. For example, I was reading this paper today, and it claims to abstract try, catch control flow and async/await control flow, as well as iterators like for into some abstract construct. It's hard to get through, but its good stuff and interesting. Just keep reading, and consult wikipedia or the nLab for information.
guest3456
Posts: 3454
Joined: 09 Oct 2013, 10:31

Re: Operators vs Methods

04 Oct 2018, 23:23

iseahound wrote: Which is what AHK is missing out on. Simple functions/operators such as map. Why not allow every function reference have a map? So to flesh out our previous example, imagine we have a List of [X, Y] coordinates and we want to click them in sequence. Grab a reference to the click function and apply it to the entire list. ClickRef := Func("Click") then ClickRef.map(List) or maybe more intuitively, Func("Click") map List or List map Func("click")
working AHK v2 code:

Code: Select all

CoordMode("Mouse", "Screen")
SetMouseDelay(100)
list := [[ 30, A_ScreenHeight-25]   ; click start menu
        ,[600, 600]]                ; click away
clickfn := Func("Click")
map(list, clickfn)
return


map(array, func) {
   outarr := []
   for i,v in array
      outarr[i] := %func%(v*)
   return outarr
}


Return to “AutoHotkey Development”

Who is online

Users browsing this forum: No registered users and 38 guests