i found this little Hoon example a persuasive argument towards having some basic constructs have punctuation syntax (as an aside, it also persuades me that what makes Hoon hard to read is not just the use of 'line noise' instead of alphanumeric keywords for basic constructs, but rather, the use of non-descriptive words such as 'clam', 'vase', 'whip', 'kelp' for almost function names and arguments, variable name, and language concepts; and also the sheer NUMBER of punctuation digraphs):
++ add
|= [a=@ b=@]
^- @
?: =(0 a) b
$(a (dec a), b +(b))
attribute add {
function(left-operand: atom, right-operand: atom)
produce atom
if equals(0, left-operand) {
right-operand
} else {
recurse(left-operand (decrement left-operand)),
right-operand (increment right-operand))
}
}
-- https://news.ycombinator.com/item?id=8585178
so mb we should reconsider that in Oot and give eg. 'if' a short punctuation operator, and syntax (so that the grouping becomes implicit).
if we allow digraphs for these things, there are a lot of them.
now, in Hoon, i think you might have to memorize the number of arguments that various digraphs take, which i don't like. In Hoon, the first character of each digraph is a functional category. How about we arrange things so that the number of arguments of Oot digraphs can be deduced from their first character?
also, as i noted previously, we can allow custom infix operators using punctuation bound to previously-defined alphanumeric functions, and the precedence can be deduced from the first character of the operator.
---
samatman 871 days ago
| parent | on: Clojure for the Brave and True – Functional Progra... |
(serve (fry (add-pan kale (fry (add-pan (dice onions)))))
cynicalkane 871 days ago
Better is
(->> onions
dice add-pan fry (add-pan kale) fry serve))---
dont forget that we need a range constructor! if . and .. are taken then mb "..."? Mb that seems hard to remember.. mb if '#' is then infix # eg 1#10 = 1..10? nah, anyway i dont think # will be len, len is good enough
---
dont forget we need a % as in Python's '%' for string format (string interpolate). Since $ has to do with interpolation in Oot, mb use $$ for this? And we could use '$' for 'it'. No, we probably need to save $$ for some metaprogrammy 'substitute'. Not sure about '$' (mb 'it'? or mb the environtment, eg current variable bindings). Is $ available for custom binop and unary ops? Probably not b/c $alpha is still needed for "immediately interpolate variable with the name 'alpha'", ie the opposite of ?alpha.
---
if two modules try to export the same name, you can't just import them both (and let the second one implicitly shadow the first); instead you must use import..with..as (like in Python, but whatever syntax we have instead). But then what if a module tries to overwrite a builtin? options: (a) disallow overriding builtings; or (b) in this case you have to import..builtins..as explicitly, which removes the otherwise-implicit import of it in the beginning ; or (c) add a new construct, mb import..overwriting
---
i like Python's use of alphabetical 'and' and 'or', but our "Only punctuation affects the way things are parsed." rule forbids this, so we need to reserve some characters for it. I looked thru http://rosettacode.org/wiki/Logical_operations and it seems like most everyone uses either alpha 'and' and 'or' or they use && and
| or mb & and | . So how about we use & and | . This means we have to rethink using both | and | for different kinds of piping, but i think it's worth it. We can always use | and | instead for the piping. | |||||
---
is '%' is still in python 3 for 'format' (string interpolation)? Yes: http://stackoverflow.com/questions/14753844/python-3-using-s-and-format
---
OK, so operator precedence/order of operations:
We want to allow custom operators. But we don't want to allow custom precedence (unlike Haskell, which does allow this) because it means you cannot parse expressions without looking up the function definitions to find the custome precedences of any included custom operators.
One way around this is to have custom operators have a fixed precedence. Another way, the way we are choosing, is to let the punctuation for the operator determine the precedence.
Most languages have a bunch of precedence levels, but i don't want to:
There's no point to having operator precedence if people can't memorize it. The only point of operator precedence is to remove the need to clutter up code with parentheses, by making the parentheses implied. If people can't memorize the precedence table, then they have to look up the rules in order to understand code which relied on the unfamiliar parts of the precedence table; in which case it's better to force use of explicit parentheses for readability (to save readers the time of looking that up).
Are equations and <= comparisons tighter or looser than and/or? Python and golang disagree (and i couldn't remember which way Python did it anyways, which means this part of the precedence table is too much to remember). So we should leave these things at equal precedence.
Go has 5 levels of precedence, which is less than most languages, so start there. It goes: multiplication/shifts/bitwise-ANDs, then addition/bitwise-ORs, then comparisons, then &&, then
| . This puts && and | at different levels, which we don't want. And these are on different levels from <=, which we don't want. We have 'div' for division, not '/'; and we don't want to allow custom operators starting with '-' b/c for now we are saving that for 'mark'. It is not yet clear if we will give shifts and bitwise operators punctuation in core, so ignore them for now. And we also have a '%' operator, which is like Python's "in", and a '%%', which is like Python's "%" (format/string interpolation); Python puts 'in' with <=, and puts '%' with *; i guess we'll put % with <=. Making these changes, we get three levels: |
unary: ! eg ! or !-
binary:
eg arg1 * arg2 or arg1 *
| arg2 |
ternary: <..x..> where .. can be more punctuation (reflected left-to-right the second time) eg arg1 <arg2> arg3 or arg1 <-arg2-> arg3
So:
Custom operators must be all punctuation.
They must start with one of:
| (binary) |
In Oot the precedence is determined by the first (punctuation) character of the operator.
actually using
| and | for piping isnt as good b/c we wanted something easy to reverse the direction of a chain of functions, eg before we had: | |||
x
| g | f == f(g(x)) |
but x
| g | f is clumsy to type. |
could say "'
| ' means OR when freestanding, or reverse-compose when infix not freestanding" but that's confusing. |
so this is an open question.
it would be nice if '*' or '+' implied associativity but then we'd want non-associative prefix symbols for those precedence levels too, so that there is a way for non-associative operators to be in that precedence level, right? But i am running low on punctuation, so i guess we can't have that. So either the * and - precedence levels imply associativity, or associativity can only be specified via a type annotation.
---
if '-' is "not" we may have a problem bc '-1' is different from 0, but -T is F; so mb do use ! for not after all
---
is prefix - also a custom unary prefix?
so do need assoc and nonassoc custom operators?
---
does custom op prefix infixify alphanumeric name? I'm thinking yes (later: nah just '%'). in which case we don't need separate infixify. if not, what is precedence of infixified? Q: what is it in Haskell A: seems like it is whatever is custom defined for the alphanumeric function, or 9 (the highest) otherwise: [1] )?
---
what about modals? ideas for modals: !+ !* or !+ !-
---
oo, i have a great idea. Instead of having syntax for an 'argspec' in the base-level language, just have blocks, and have the 'argspec' syntax be a type annotation. With no annotation, the compiler infers that any unbound variables must be parameters (i guess by default it would make them positional arguments in the order that they appear; of course, they could be accessed by keyword too, just like positional arguments in python, and their keyword names would just be their variable names, as usual)
now tiny anonymous functions can just be blocks with no argspec: f = {a + b}; f 3 4 == 7
also it makes the syntax more simple/uniform imo.
One potential problem with this: this seems to be incompatible with unprefixed reference to any non-lexically scoped variables, such as semiglobals or C++ style unprefixed reference to member variables if not declared in a way that the compiler can understand. Upvariables seem to be OK because they are in an enclosing lexical scope. I guess the answer is just:
another problem: it clashes a little with my idea that like Octave, you want to be able to copy and paste from a function definition to its use. But this isn't too bad if the type annotation is just a single : or ^ and something that looks like function arguments are the annotation; just delete that one character
eg
f : a b = a + b
or if ^ is the annotation character:
f ^ a b = a + b
another problem:
this isnt as clean looking as Haskell's
f a b = a + b
though
---
problems with distinguishing between uppercase and capitalized (currently for symbol/annotation distinction):
i guess we could just say that for something to be read as 'capitalized but not uppercase' it must have more than one letter at the beginning.
---
another idea for 'in' is -<, but i like % better
---
some ideas for ^ things:
type annotation could be ^ or ^% or ^<- (i like ^)
assertion could be ^= or just any expression without any = (you can have ==) nor & in it (but then how would you assert something with a &? mb you have to unpack it separately, eg y = &callfn; y == 3)
^+ could be to attach a 'label' to a node in the AST
---
how does the precedence of type annotation, ::, work in Haskell?
https://mail.haskell.org/pipermail/beginners/2012-December/011017.html
summary: it DOES NOT annotate the current AST node, rather, it annotates the entire expression to its left, eg loosest possible binding, although this is accomplished at the level of the language syntax, not operator precedenc; '::' is not a 'real' infix operator; EXCEPT that there are a few other un-operators like lambda with even looser binding
i don't know how it could bind to the 'current AST node', maybe enclose it in parens?
---
so do we want our type annotation operator to bind to the 'current AST node', or everything to the left, like Haskell?
probably whatever Haskell did maximizes convenience for programmers in this case, so do that. So, our type annotation operator has loosest, not tightest, precedence.
note: for us, it is an ordinary 'operator' though; types are values etc
---
also we need runtime functions to test type, i guess this can be alphanumeric ordinary fns though, rather than having special syntax for it
if syntax though, mb could be ^==
---
yknow, i do like having postfix . for elementwise. It's not always just equivalent to 'map', eg +. is not just 'map +', because it's elementwise in both arguments. But it is very common.
some sort of fold metaoperator would be good too, eg to transform + into 'sum' like \Sigma in math
i'm looking forward to having '%' for 'in', as 'in' is very common
we also definitely need something like Python's concise list comprehensions
also we really should have a 'range' operator like 1..5. Dunno if it should be .. though, i do like the idea of .. being for reified edges
otoh why not use ^. for the reified edge? or even .^. ? would have to use that for path literals, too, but that's ok. or could use ...
it's really good to have units, too, but i guess xUmeters is good enough for that, as it's not a metaoperator and it's unary.
later: yknow, using a one letter shortcut is not so much worse than '.' for elementwise, map, and fold. Also, could use a one letter shortcut for range.
---
we should probably have a logical operator for 'EXPR evaluates-to Z (in context C)'
---
is it okay to have quantifiers look like functions, like in some CL (common logic) representations? if the variables they take are metavariables (prefixed with ?), is that enough? eg
forall ?x
or do we want a special sigil for the quantifiers, more generally, for anything changing the 'meaning' of parenthesized phrase
eg ^forall ?x (^forall ?y (x == y))
(to warn you that this is not similar to:
f ?x (f ?y (x == y))
)
also, inside a logical context we aren't substituting in variables, even ones without ? prefixes, right? we'd have to prefix with $ to substitute?
---
in general, does ^ prefixed to an identifier mean that the identifier is a macro (or other metaprogrammy thing?) that should be run upon the argument (or just on the last argument), after quoting the argument? if so then
^forall
isn't quite what we want for logic. Or, is there a 'logic context' just like the 'data context'?
mb 'logic context' is entered/left by ?( )?
but why would we even need a 'logic context'? isn't the content of this context just expressions, perhaps with ?-s instead of =s? i guess i don't yet know enough about logic programming to be sure
---
we need a 'substitute' operator, but also an 'eval' operator, which may be more than substitution (eg run side-effects?); but we also want to be able to say "expression E evaluates to expression x" or "expression E evaluates to value v" WITHOUT running side-effects, right? eg do all possible substitutions, continue until idempotency, or something like that? mb '
| -' (single turnstile). This operator is also logical, eg "E | - x" can be a logical assertion. |
---
i think i've decided that <function> should be the syntax for infixify. The reason is that i want x<PyP?