Hello, Daniel, thanks for the quick reply. Sorry for not being able to
respond as quickly.
> […] many languages differentiate between an undefined variable (or member)
> […] and an existing variable (or member) that holds null. […] [I]n FM3 I do
> consider differentiating the two cases (undefined and defined but null) […].
I honestly hope you don’t actually decide to differentiate undefined
from null. Otherwise, you’ll end up with two very similar concepts
with slightly different behavior. That’s not good for anybody, it’s
just straightforwardly confusing. It’s good for a static‐typed
language, where an error can be placed at compile‐time, but for a
dynamic‐typed language where these kinds of errors go to the runtime,
it’s weird that sometimes an absent value behaves one way, and other
times it behaves another.
> Actually they do. […] it's only `(exp)!exp` and `(exp)??` that do the
> exception catching trick […].
Right, when I mentioned `!` and `??` I meant `()!` and `()??`.
> There were ?exists and ?default(exp), but because these are used so often,
> they have been deprecated in favor of the dedicated operators. […]
I generally follow the Ceylon philosophy that clarity and regularity
come before terseness. That’s why in Ceylon there is no `?:`, but
instead `else`, that’s why it’s `ceylon.language` and not
`ceylon.lang`, that’s why it’s `variable value` and not `var`, etc.
In Ceylon, it’s always preferred to have a meaningfully‐named
functions/methods instead of cryptic operators. I mean, granted `??`
and `!` are not particularly cryptic, but I still think I’d rather
have the more regular generic‐pupose solution than to have the
specific‐purpose operators.
> And if we have `exp?else(exp)` instead of `exp!exp`, then `![]` doesn't clash
> with it, I see that. But I wouldn't give up `exp!exp` for the sake of
> `map![key]`
Interesting that you brought this up. In an older version of this
proposal, I suggested `!!.` and `!![]` because of the syntactical
collision with sequence literals and special variables. That was one
of the reasons I decided to suggest `else`. I don’t think it’s the
biggest deal, but that’s still an inconvenience: to have to choose a
meaning for `![]`. Whenever someone wants to null‐safely access an
awkwardly‐named member of an expression, they might be surprised that
`expr!["awkwardly named"]` doesn’t work.
> Well, `exists` is kind of verbose considering checking if something exists is
> frequent in templates, but it's easier to understand when someone reads the
> template, so maybe it worths it. I'm not sure yet.
As I said before, I generally prefer clarity over terseness.
> But I don't think it has reason to be bound to `if`. […] Do I miss something
> with the comma operator, considering this is not a static language? (BTW, an
> interesting thing above is the scoping of the assignments. But that's a
> different topic.)
It being part of the `if` syntax solves a couple of problems:
1. Scope: the scope of the variables is the `if` block. I think it’s
awkward for something deep inside an expression to be able to declare
a variable accessible outside the expression. Generally, when
languages do allow people to declare variables in an expression
(generally through a `let` expression), the scope of the variable is
only the expression.
2. What happens if the expression doesn’t run? Consider `(exists foo =
bar) || (exists baz = qux)`. In this expression, only one of the two
variables is declared. Not only this is awkward, but it’s useless. If
this was in an `if` directive, you would still have to check if each
variable exists anyway. Having `exists` be part of the `if` directive
would ensure that all variables are always created before they can be
used.
It’s also interesting to note that there is no “comma operator”, the
comma is also part of the `if` syntax. It’s used to be able to have
multiple `exists` conditions. Consider:
```
<#if foo==bar, baz==qux> <#-- works -->
<#-- ... -->
</#if>
```
```
<#if (foo==bar, baz==qux)> <#-- doesn’t work -->
<#-- ... -->
</#if>
```
> […] my personal opinion is that FTL3 should look and feel like FTL2 where
> it's possible, so the *basic* meaning of `!` and `?` stays. […]
Agreed.
> […] if I had a time machine I would use `?` in place of `!` on the first
> place. […]
Agreed.
> But, if things go well, we may build yet another language on the top of the
> same engine, which looks and feel totally differently anyway […].
Honestly, I really like Freemarker’s syntax. I’d be sad to see it go away.
> […] I want an easy and universal way of telling FM that "I know, this one
> might evaluates to null... so deal with it, silently". So that would the
> postfix `!` operator, as in `exp!`. How it works is that actually most
> operators (and now I count in ${} there too) can deal with null arguments,
> only they call the operand expression by saying that they can't return null
> […]. But if there's a `exp!`, the `!` operator will disobey, and call its own
> LHO by saying that returning null is OK, and then despite what the caller has
> asked, return null if it has to. This means that there's no `!.` operator,
> yet `a!.b.c` is valid and it just means `((a!).b).c`. If the `.` operator
> gets a null LHO, it just returns null.
I will say that that is a really creative approach to the problem. I’m
not sure if it’d be easy to understand for most people, but it solves
the problem quite nicely, while, as you said, allowing people to
expose the reason the variable was null.
To make sure I understand what you mean: the idea is that in an
expression could be evaluated in two different ways: allowing null,
and with a request for non‐null. Every expression would behave
slightly differently depending on which type of evaluation they were
executed as, except for `!` which would always behave the same (i.e.
it’d execute its operand allowing null).
I think what I feel is most interesting about this approach is that
any expression can return null even if it’s requested not to. For
example, consider the expression `${foo!.bar}`. In this expression, if
`foo` exists, the dot will access the returned hash’s `bar` member. If
`foo` doesn’t exist, the dot will return null, regardless of what it
has been asked.
> […] a #function, an operator, etc., I intend to treat these as the same […].
> Note that x?foo(y) basically means `core:x(x, y) […].
I think you meant `core:foo(x, y)` there. That’s cool, you are
planning to make built‐ins into something more generic. I don’t know
what this `core:` is supposed to be, but that reminds me of the `..`
operator in StratifiedJS.
I just feel like that `x?foo` should mean `foo(x)`, while `x?foo()`
should mean `foo(x)()` and `x?foo(y)` should mean `foo(x)(y)`. (i.e.
functions that return functions). That’s because the expression
`expr1?expr2` would then always mean the same thing, that is
`expr2(expr1)`, and you wouldn’t need to make the `()` part of the `?`
syntax, it’d just come naturally.
A problem with this approach is precedence. in StratifiedJS, `..` is
generally written with spaces around it (i.e. `expr1 .. expr2`)
because it binds very loosely, so `expr1..expr2.member` would mean
`expr2.member(expr1)` and not `expr2(expr1).member`. I’m not sure I
like that, though; I think I’d rather have it bind closer than `.`
does.
> […] A null bypasser is a "function" […] which *declares* that if a certain
> parameter of it is null […], then it does nothing but returns null […]. I
> want to make most built-ins to be null-bypassers […].
That doesn’t feel like a good idea to me. In my opinion, it’d be
better to have all functions have that behavior for all parameters,
but have a function call expression execute all of its argument
expressions by asking them to not return null.
> […] Thus, if you have `${maybeMissing?upperCase?trim?upperCase}`, you don't
> have to worry about what will `?upperCase` and `?trim` do if maybeMissing is
> null. They will just bypass it. […]
So with my approach, this would fail because `maybeMissing` would be
called by requesting to return non‐null. To make it work, you’d simply
have to write `${maybeMissing!?upperCase?trim?upperCase}` instead.
> […] Your only concern here is that `${}` will not like the null. So you can
> write ${maybeMissing?upperCase?trim?upperCase!'-'} […].
I don’t understand. You said above that `${}` was one of the things
that would be able to handle null. Below you also say that
`${missing!}` should work (and would output nothing) as further
indication that `${}` should indeed be able to handle null.
> I would keep `exp!exp`. I understand the advantages of expressive names like
> `?else`, but consider that `exp!exp` is quite natural once you have learnt
> about `exp!` (or the other way around […]) […].
I can’t disagree with that. It’s also interesting to note that with my
suggestion to make all function calls execute their argument
expressions by requesting to not return null, `missing?else(expr)`
would fail if `missing` is null, you’d have to instead write
`missing!?else(expr)` (and also `missing!?exists`). So maybe it’s
indeed a good thing to have dedicated operators that execute their
operands allowing null. However, I do think that it’s not a bad idea
to have a different operator for the binary `!`. I suggest `!:` since
it’s easy enough to type, and it extends quite nicely the `!` syntax
and meaning. This would both differentiate them syntactically (which
is good since they have different semantics) and remove the ambiguity
with special variables and sequence literals.