On Monday, 7 March 2016 at 05:56:54 UTC, Patience wrote:
Just curious if anyone can see the use for them?

e.g., for[32], switch[alpha] //alpha is a user type, if[x](x < 32)
etc..

The idea is two-fold, one is to allow polymorphic keyword behavior(it's behavior depends on the argument) and to allow the code itself to manipulate the behavior.

Of course, the behavior isn't easily define for the above examples. So far all I can think of is various simple modifications:

foreach[?] <- executes only if there are one or more iterations and checks for nullity. This allow one to avoid writing the if check.

int[size] <- creates an integer of size bits.

etc...

I can't see much benefit but maybe someone has some good uses?

These could be thought of as "attributes for keywords". The programmer would have to define precisely what if[x] means.

Obviously the biggest objection is "It will obfuscate keywords"... Please don't obfuscate the question with such responses.

This... looks like you have a solution and now you are looking for a problem. I fail to see the need to parameterize the *keywords*, when what you really want to modify is the *constructs* created from these keywords. That is, you don't want to modify `if` keyword - you want to modify the whole `if` statement(`if (a) { b(); } else { c(); }`). Modifying the keyword is just a means to that end.

This distinction is important because the term "keywords", while distinctive and important at the lexical phase, is too broad at the grammar phase and is no longer meaningful once we reach the semantics phase. Parameterizing `int` is different from parameterizing `foreach` is different from parameterizing `pure`.

Of course, this statement doesn't hold for homoiconic languages, where keywords are actual values and parameterizing them simply means returning a different value. Also, I'm assuming you mean to allow defining parameterizations at library level - otherwise they won't be very useful, since you could simply create new syntactic constructs.

So, assuming you the language is not homoiconic and that users and library authors should be able to define("overload") their own keyword parameterizations, the keywords will need to be partitioned into several categories: data-types, annotations, statements etc. Each category should have it's own parameterization overloading rules - so `int[...]` and `float[...]` will have similar rules, which will be very different from `if[...]`'s rules.

Now, let's focus, for a moment, on types - because "parameterizing" types is a solved problem - it's called "templating". You usually want parameterized types to also be types - your `int[12]` should be a type, usable wherever types are usable - which is exactly what templated types do - it's easy to implement `CustomSizedInt!12` which does exactly what your `int[12]` does. In fact, templated types are better, because:

1) When you encounter `CustomSizedInt!12` and want to know what it does, you need to search for `CustomSizedInt`'s declaration - an extremely common problem, automated by many simple-to-use IDE features and command line tools. To divine `int[12]`'s meaning you'd have to look for the implementation of a the parameterization of `int` with another `int`(or with a `long`, or with a `uint`, or with a...), and you need a more complex query to search for it.

2) `int[12]` is a user defined type, but it's conceptually coupled to `int`. The mere concept of parametrized keyword types is coupled to primitive types. Templated types do not have this limitation - they can depend on whatever they want to - so you have much more freedom. Even if parameterized keywords could do everything templated types could, you'd have to abuse them(resulting in many code smells) whenever you want a type doesn't strictly resolve around a primitive type - something that comes naturally with templated types.

3) Code that invokes user-defined behavior should have an "anchor" - something the definition of that behavior resolves around. When you call a function it's the function. When you call a method it's the object's type. When you use an overloaded operator it's the type of one of the operands. When you use `int[12]`? Both `int` and `12` are built in - there is no anchor. If `int[12]` is to be library defined, it would have to be more like `int[SizeInBits(12)]`(so `SizeInBits` is the anchor), and suddenly it doesn't look that syntactically appealing compared to `CustomSizedInt!12`...


So, that was for keywords that represent types - what about other keywords? I picked types because it's something D(and many other languages) already have, but I claim that the same reasons apply to all other keywords. Let's look at another easy one - annotations. Let's say you want to paramererize `pure` - e.g. `pure[MyPureModifier]` - so it'll do something a bit different. It'll still be an annotation, so it'll have to annotate some declaration. If you are already going to implement custom modification of declarations, why not give that power to UDAs? `@MyPure` looks better, has a clear anchor, it's definition is easier to search, and it doesn't have to be related to an existing modifier with and existing purpose.


What about more complex stuff, like your `foreach[?]`? Many would suggest macros, but even without going to hyperblown macroland, identifiers are better than parameterized keywords. Ruby has a nice syntax for it with blocks. Instead of parameterizing the existing `for` into `for[?]`, it lets me define my own "keyword" - `foreach?`:



[1] pry(main)> for x in [1, 2, 3]
[1] pry(main)*   puts "Loop1: #{x}"
[1] pry(main)* end
Loop1: 1
Loop1: 2
Loop1: 3
=> [1, 2, 3]
[2] pry(main)> for x in nil:
SyntaxError: unexpected ':', expecting keyword_do_cond or ';' or '\n'
[2] pry(main)> for x in nil
[2] pry(main)*   puts "Loop2: #{x}"
[2] pry(main)* end
NoMethodError: undefined method `each' for nil:NilClass
from (pry):4:in `__pry__'
[3] pry(main)> def foreach?(collection)
[3] pry(main)*   if collection
[3] pry(main)*     for entry in collection
[3] pry(main)*       yield entry
[3] pry(main)*     end
[3] pry(main)*   end
[3] pry(main)* end
=> :foreach?
[4] pry(main)> foreach? [1, 2, 3] do|x|
[4] pry(main)*   puts "Loop 3: #{x}"
[4] pry(main)* end
Loop 3: 1
Loop 3: 2
Loop 3: 3
=> [1, 2, 3]
[5] pry(main)> foreach? nil do|x|
[5] pry(main)*   puts "Loop 4: #{x}"
[5] pry(main)* end
=> nil



The main idea in all these examples is the same - instead of parameterizing the existing keywords and syntax, it's better to have a generic syntax designed for overloading by library and user code.

Reply via email to