notes-computer-jasper-jasperSyntaxThoughts

unlike Haskell, we don't want to allow arbitrary operator precedence, because then you have to look up (or have memorized) every function in code that you read just in order to parse it.

like Haskell and Lisp and unlike Python, we separate fn arguments by spaces, not commas, and we don't require them to be surrounded by parens

like Haskell and unlike lisp, we dont require outer fns invocation to be surrounded by parens;

like haskell unlike lisp, we have function composition syntax (e.g. haskell's $)

like python and unlike haskell and lisp, we have syntax for list/array/dict access and mutation

like haskell, we have optional whitespace

like lisp and octave and unlike python, we allow space-separated list literals

like javascript, we have a type of map literal that implicitly quotes the key names

like ruby and perl (supposedly; see http://news.ycombinator.com/item?id=3068819 ), good options for quoting within literals

like octave, we have a way to quickly print out the result of each step (but ours is opt-in, not opt-out; e.g. mb if there IS a semicolon, then print)

(not syntax)

like python and unlike haskell and lisp, our fundamental data structure is dict-like, not list-like





pattern matching partial functions? seems messier than a pattern-matching switch statment to me; that way it's all in one place. is there any benefit to doing it the partial way? also, look at simon's advanced guards thingee -- is there some corecursion thing going on there that makes partial functions look more appropriate?

--- i just wrote:

"

Read left-to-right (standard orientation)

That is to say, in Jasper you write "x = 1++(f y)", not "3++(y f) = x". "3++(y f) = x" (the nonstandard way that we didn't adopt) would be clearer, because when you read it from left to right, that follows the order that data actually flows thru the expression.

However, programming text is usually left-aligned, which makes it easy to scan down the screen with your eye and look at the leftmost words.. and hard to scan down looking at the rightmost words. A common use of scanning is to look for where some variable is defined. So, the lhs of the assignment operator should indeed be on the left hand side.

todo: mb mix and match? x = 3++(y f) ? "

hmm, mb should do:

anything with a side-effect, including assignment, goes on the leftmost side. So, there can only be one side-effect per line. The side-effectful thing is executed last. Everything else is 'backwards', e.g. x f instead of f x, i.e. the left-er stuff is executed earlier.

hmmm but now where/how do we put parens now that everything is backwards?

instead of:

defun (map f xs if (unit? xs unit cons f car xs,, map f, cdr xs

we have

defun ( unit xs cdr, f map,, xs car ..

no no no, the whole idea of doing it backwards is for the order of evaluation to be followed. so you can't put something after a conditional first. so i guess the order of arguments should not be reversed

defun unit? xs unit cons xs car f ,, xs cdr, f map ) map xs f

no, too irregular. since we want the side-effect to go on the left, and the side-effect is usually the last thing, we either have to put the first stuff on the right, as usual, or we do it backwards but then we have an irregularity because we switch directions between the first thing on the left and the second.

so let's stick with the usual direction.

---

i do like the rule that a side-effectful command/function may only be leftmost on each line (and hence there can be at most one per line). But this means that when you change something from pure to side-effectful, callers all the way up the call chain must rewrite the way they call you.


right associative, but left associative with , (which only changes parsing to the right of the comma), newline with parens open is left associative w/r/t the parens opening through the line boundary. In other words its like http://chrisdone.com/z/ except using opening parens instead of indentation, and with commas to indicate multiple arguments.

If there is a newline without unbalanced opening parens on that line, then the line is implicitly surrounded with parens.

empty lines are sugar for {}s which are like 'big parens' that define 'blocks'. Blocks are like big parens which auto-close parens as needed at the end of the block. They also provide scope for block-scoped macros.

since we are using {} and () now for grouping, and we are using [] for data literals, we need another bracket grouping symbol for annotations and/or type system directives. How about <>. Use 1 for comparison.

---

todo read http://en.wikipedia.org/wiki/Bracket#Uses_of_.22.28.22_and_.22.29.22 and subsequent 'uses of' sections.

---

for punctuation used as operators and also as normal symbols, you want the languagey things for single uses and the normal symbols as repetitions, because the languagey uses are much more common, e.g. - is inverse, -- is subtraction. But for punctuation used as grouping, you want the single uses as symbols and repetitions as grouping, because once you think you see a grouping symbol you dont want to have to lookahead to see if its not really one, and also you want weird grouping stuff to stand out when you are skimming code

--

mb start with right associative, and switch into left association mode when you enter parens, or start with left associative, and switch into right association mode when you enter parens

dont alternate/switch again with nested parens; thats too confusing

--

in the context of wondering if "a , b , c = 1 , 2 , 3" should work, and if it would that would seem to imply that a, b, c == [a b c], what is really gained from Haskell-ish currying, e.g. always having f(x)(y)(z) instead of f([x,y,z]) ?

http://www.haskell.org/haskellwiki/Currying says "The major advantage of considering all functions as curried is theoretical: formal proofs are easier when all functions are treated uniformly (one argument in, one result out). Having said that, there are Haskell idioms and techniques for which you need to understand currying."

http://www.haskell.org/haskellwiki/Composing_functions_with_multiple_values notes the imbalance between curried inputs and tuple outputs

oh i remember one thing it lets you do; it lets you not distinguish between:

can this be easily mimiced with tuples and partial application? partial application can take the second and turn it into the first, but is there a situation where you need to go the other way somehow?

i think that's it. the benifit of currying instead of having tuple arguments is that it lets an arbitrary multiargument function be implemented as a function that takes some of its arguments and returns a function that takes the rest. i guess that's important.

--

remember that big blocks are also scopes for things like macros and transactions (should we allow transactions with 'dynamic boundaries' too? probably)

-- ppl seem to dislike function scope, and prefer block scope but with closures (e.g. something defined in an ancestor block is available in all descendents)

http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html

"

Don’t tell me it’s got lexical scope, because JavaScript’s? scoping is an abomination in the face of God. Guy Steele isn’t even dead and JS scope makes him pre-emptively roll in his not-yet-occupied grave.

...

At the same time, we’re ignoring the things about JavaScript? that make it not Scheme. It’s got a much richer syntax including a great notation for data. I’m not a huge fan of prototypes (anymore), but it’s an interesting dispatch model that Scheme doesn’t have. "

-- http://journal.stuffwithstuff.com/2013/07/18/javascript-isnt-scheme/

    "JavaScript's C-like syntax, including curly braces and the clunky for statement, makes it appear to be an ordinary procedural language. This is misleading because JavaScript has more in common with functional languages like Lisp or Scheme than with C or Java. It has arrays instead of lists and objects instead of property lists. Functions are first class. It has closures. You get lambdas without having to balance all those parens." -- http://www.crockford.com/javascript/javascript.html

"

Avatar danielparks • 4 days ago

Could you expand on your contention that JavaScript? isn’t lexically scoped? 3 • Reply • Share ›

    Avatar
    Calvin Metcalf danielparks • 4 days ago
    It is functionally scoped instead of block scoped and while it is mostly lexically scoped 'this' is dynamically scoped.
    6
    •
    Reply
    •
    Share ›
        Avatar
        munificent Mod Calvin Metcalf • 4 days ago
            −
        Not just that, but thanks to with and the global object, you always have dynamically scoped variables.
        1
        •
        Reply
        •
        Share ›" "

---

let's try to think of a way to use capitalization more usefuly than (a) distinguishing a certain type (the Type type), or (b) scoping.

note: many of these may deserve some other special syntax, if not capitalization

so far i like the last one the best. e.g. capitalized names can optionally break through hygenicity of macros. But how to encourage ppl not to use metaprogramming tricks like ruby's method_missing to do the same thing for lowercase names? perhaps a version of method_missing that only works on capitalized names should be provided, and use of the full (original) method_missing that works on all names should be discouraged (at a higher level of the metaprogramming hierarchy).

--

--

coq's 'notations' syntax

" We can make numerical expressions a little easier to read and write by introducing "notations" for addition, multiplication, and subtraction.

Notation "x + y" := (plus x y) (at level 50, left associativity) : nat_scope. Notation "x - y" := (minus x y) (at level 50, left associativity) : nat_scope. Notation "x * y" := (mult x y) (at level 40, left associativity) : nat_scope. "

"at level x" is precedence, "nat_scope" is which namespace the notation is declared in

" Notation "( x , y )" := (pair x y). "

" Notation "x :: l" := (cons x l) (at level 60, right associativity). Notation "[ ]" := nil. Notation "[ x ; .. ; y ]" := (cons x .. (cons y nil) ..).

"

"For example, since we defined + as infix notation for the plus function at level 50, ... The + operator will bind tighter than ::, so 1 + 2 :: [3] will be parsed, as we'd expect, as (1 + 2) :: [3] rather than 1 + (2 :: [3]). "

"

The right associativity annotation tells Coq how to parenthesize expressions involving several uses of :: so that, for example, the next three declarations mean exactly the same thing:

Definition mylist1 := 1 :: (2 :: (3 :: nil)). Definition mylist2 := 1 :: 2 :: 3 :: nil. Definition mylist3 := [1;2;3]. "

" Notation "x ++ y" := (app x y) (right associativity, at level 60). "

--

homoiconicity: at some point need to take this more seriously. No "graph data constructor". Grouping constructs, etc, are the same in graphs as in code. Graph node labels are used in code. Etc.

--

list of syntactic/semantic universals:

x > 3}; the pipe in Haskell guards) (note: "implies" (arrow) is related; "b, given a" is the same as "a -> b", so maybe just use that? e.g. instead of Haskell guard syntax "f x x > 3 = 2" we'd use "x > 3 --> f x = 2"; this unification is also needed to unify Haskell pattern guards, and typeclass contexts)

--

--- http://www.haskell.org/haskellwiki/GADTs_for_dummies ( http://web.archive.org/web/20130702221947/http://www.haskell.org/haskellwiki/GADTs_for_dummies )brings up an excellent point: type classes are like arbitrary functions on types, with normal Haskell stuff like pattern matching, algebraic data types (multiple constructors), guards, etc, except with a confusing relational (rather than functional) syntax.

this brings up the obvious point: Jasper could be like this but use normal syntax

seems like there is really a lot of mileage to be had just by delivering a uniform notation for data, code, patterns, types, typeclasses. Seems like you could get one just by reading through that article and using the 'basic' (base level functions) one.

note: Haskell's data syntax is a little confusing if thought of as just pattern matching; from the above:

" data Either a b = Left a Either a b = Right b

we write just

data Either a b = Left a

"
Right b

but really we meant "the type of Left a is Either a b, AND the type of Right a is Either a b AND nothing else is Either a b"

note that this is like a Coq match statement.

---

it can be annoying if functions are variadic:

http://stackoverflow.com/questions/7823516/why-are-many-clojure-functions-variadic

---

if we have optional return arguments, then need a way to get all the return arguments sometimes

perhaps similar to the way that we have variadic *args, kw formal parameters at the end of fn declarations

---

so perhaps we have 2 syntactical argument conventions:

---

it's useful to have clojure-style variadic map:

"Returns a lazy sequence consisting of the result of applying f to the set of first items of each coll, followed by applying f to the set of second items in each coll, until any one of the colls is exhausted. Any remaining items in other colls are ignored. Function f should accept number-of-colls arguments."

(map + [1 2 3] [4 5 6]) (5 7 9)

user=> (apply map vector [[:a :b :c] [:d :e :f] [:g :h :i]])

([:a :d :g] [:b :e :h] [:c :f :i])

and

(defn numbered-lines [lines] (map vector (iterate inc 0) lines))

but you also want to do simple maps without enclosing them in []s:

(map inc [1 2 3 4 5]) (2 3 4 5 6)

if you don't have variadicity, and the last positional argument of 'map' is 'args', then you'd have to do:

(map inc [[1_2_3_4_5?]])

and if this could be transformed into a variadic form but it wasn't the default, you'd have to variadicize it explicitly:

(map* inc [1 2 3 4 5])

--

i like clojure's super lightweight lambda syntax: #(blah %)

--

could have precendence for custom operators, but defined by their ASCII or something (to match arith, i guess)

later: i think Scala did this; but i think ppl still get confused by precedence in scala, which has 10 levels: http://stackoverflow.com/questions/7618923/actual-precedence-for-infix-operators-in-scala

---

clojure has a weird notation with slashes i guess for accessing member of Java classes:

https://speakerd.s3.amazonaws.com/presentations/2471a370b3610130440476a0f7eede16/2013-05-17-ClojureOOP-Geecon.pdf

© 2011 innoQ Deutschland GmbH? ( defn make-id [prefix id] ( join "-" [prefix ( Long/toString id 16)]))

--

clojure has an interesting pattern matching syntax:

(defn id-generator ([prefix] (id-generator prefix 0)) ([prefix v] (let [cnt (atom v)] (fn [] (make-id prefix (swap! cnt inc))))))

--

i guess compared to clojure, we want to omit parens (and indentation levels) for things like enclosing a 'let' statement or a fn defn. Wouldn't the previous have been nicer if it were:

defn id-generator: [prefix] id-generator prefix 0 [prefix v] cnt = atom v fn [] (make-id prefix (swap! cnt inc))

notice how it looks nicer with that colon in the first line, too. mb i'm wrong and python is right and we should have that. the colon would mean a special form that introduces a block and goes until the end of the block.

without the colon its not too bad tho

defn id-generator [prefix] id-generator prefix 0 [prefix v] cnt = atom v fn [] (make-id prefix (swap! cnt inc))

--

another example: clojure:

(defn make-fsm "creates an fsm with initial state s0, a reset event, and a map of transitions. [state-transitions] must be a map of state->[[f1 f2 ...] {e0->s0, e1->s2, ...}]" [s0 reset-event state-transitions ] (let [s (atom s0)] (fn [evt] (if (= evt reset-event) (do (println "Reset event, returning to " s0) (swap! s (fn [_] s0))) (let [[actions transitions] (state-transitions @s)] (if-let [new-state (transitions evt)] (do (println "Event" evt "causes transition from" @s "to" new-state) (doseq [f actions] (f)) (swap! s (fn [_] new-state))) (println "Unexpected/unhandled event" evt "in state" @s)))))))

suggested:

defn make-fsm "creates an fsm with initial state s0, a reset event, and a map of transitions. [state-transitions] must be a map of state->[[f1 f2 ...] {e0->s0, e1->s2, ...}]" [s0 reset-event state-transitions ] s = atom s0 fn [evt] (if (= evt reset-event) (do (println "Reset event, returning to " s0) (swap! s (fn [_] s0))) [actions transitions] = state-transitions @s (if-let [new-state (transitions evt)] (do (println "Event" evt "causes transition from" @s "to" new-state) (doseq [f actions] (f)) (swap! s (fn [_] new-state))) (println "Unexpected/unhandled event" evt "in state" @s)))

better, yes?

but the implicit grouping of the assignment with the statement below it to form only one branch of the 'if' is confusing. so how about an explicit block:

defn make-fsm "creates an fsm with initial state s0, a reset event, and a map of transitions. [state-transitions] must be a map of state->[[f1 f2 ...] {e0->s0, e1->s2, ...}]" [s0 reset-event state-transitions ] s = atom s0 fn [evt] (if (= evt reset-event) (do (println "Reset event, returning to " s0) (swap! s (fn [_] s0))) { [actions transitions] = state-transitions @s (if-let [new-state (transitions evt)] (do (println "Event" evt "causes transition from" @s "to" new-state) (doseq [f actions] (f)) (swap! s (fn [_] new-state))) (println "Unexpected/unhandled event" evt "in state" @s)) })

but this change makes it a little HARDER to read in another way: now you have to think about the interleaving of } and ) at the end

but what about those if's and do's? this is tough because in algol you'd have if/then/fi, so the construct could end before the end of the block, unlike lets. And this is necessary because if you did assignment within the if, you'd want it to stick until later. (i guess in clojure if you had a bunch of other stuff to do after the if that depends on variable assignment done within the if, you'd have to make another function and call it at the end of each branch of the if?)

to make the language homeoiconic, rather than having the parser worry about the special case of how many parts 'if' has, it encloses all three parts of the 'if' in a list. now if you want to do more than one side-effecty thing inside one of the branches of the 'if', you need a 'do', which, to be uniform, is also inside a list.

this is already seriously annoying. one small change we can make is replace the do's by blocks:

defn make-fsm "creates an fsm with initial state s0, a reset event, and a map of transitions. [state-transitions] must be a map of state->[[f1 f2 ...] {e0->s0, e1->s2, ...}]" [s0 reset-event state-transitions ] s = atom s0 fn [evt] (if (= evt reset-event) { (println "Reset event, returning to " s0) (swap! s (fn [_] s0)) } { [actions transitions] = state-transitions @s (if-let [new-state (transitions evt)] { (println "Event" evt "causes transition from" @s "to" new-state) (doseq [f actions] (f)) (swap! s (fn [_] new-state)) } (println "Unexpected/unhandled event" evt "in state" @s)) })

is this really better than using an algol format for those ifs? at least then you'd get to say 'else':

defn make-fsm "creates an fsm with initial state s0, a reset event, and a map of transitions. [state-transitions] must be a map of state->[[f1 f2 ...] {e0->s0, e1->s2, ...}]" [s0 reset-event state-transitions ] s = atom s0 fn [evt] (if (= evt reset-event) THEN { (println "Reset event, returning to " s0) (swap! s (fn [_] s0)) } ELSE { [actions transitions] = state-transitions @s (if-let [new-state (transitions evt)] THEN { (println "Event" evt "causes transition from" @s "to" new-state) (doseq [f actions] (f)) (swap! s (fn [_] new-state)) } ELSE (println "Unexpected/unhandled event" evt "in state" @s)) })

you could make this non-syntax by making ELSE = 'else' i guess...

but now how would it look like if you accepted grammar and got rid of the parens around if:

defn make-fsm "creates an fsm with initial state s0, a reset event, and a map of transitions. [state-transitions] must be a map of state->[[f1 f2 ...] {e0->s0, e1->s2, ...}]" [s0 reset-event state-transitions ] s = atom s0 fn [evt] if (= evt reset-event) THEN { (println "Reset event, returning to " s0) (swap! s (fn [_] s0)) } ELSE { [actions transitions] = state-transitions @s if-let [new-state (transitions evt)] THEN { (println "Event" evt "causes transition from" @s "to" new-state) (doseq [f actions] (f)) (swap! s (fn [_] new-state)) } ELSE (println "Unexpected/unhandled event" evt "in state" @s) }

less pretty, certainly, but perhaps easier to follow? i'm not sure. and a little more to type, what with those THENs and ELSEs. without the THENs:

defn make-fsm "creates an fsm with initial state s0, a reset event, and a map of transitions. [state-transitions] must be a map of state->[[f1 f2 ...] {e0->s0, e1->s2, ...}]" [s0 reset-event state-transitions ] s = atom s0 fn [evt] if (= evt reset-event) { (println "Reset event, returning to " s0) (swap! s (fn [_] s0)) } ELSE { [actions transitions] = state-transitions @s if-let [new-state (transitions evt)] { (println "Event" evt "causes transition from" @s "to" new-state) (doseq [f actions] (f)) (swap! s (fn [_] new-state)) } ELSE (println "Unexpected/unhandled event" evt "in state" @s) }

and what does it look like without the ELSEs:

defn make-fsm "creates an fsm with initial state s0, a reset event, and a map of transitions. [state-transitions] must be a map of state->[[f1 f2 ...] {e0->s0, e1->s2, ...}]" [s0 reset-event state-transitions ] s = atom s0 fn [evt] if (= evt reset-event) { (println "Reset event, returning to " s0) (swap! s (fn [_] s0)) } { [actions transitions] = state-transitions @s if-let [new-state (transitions evt)] { (println "Event" evt "causes transition from" @s "to" new-state) (doseq [f actions] (f)) (swap! s (fn [_] new-state)) } (println "Unexpected/unhandled event" evt "in state" @s) }

hmm.. a little prettier and not terribly harder to follow.

leaving in the parens around 'if' is not terrible though:

defn make-fsm "creates an fsm with initial state s0, a reset event, and a map of transitions. [state-transitions] must be a map of state->[[f1 f2 ...] {e0->s0, e1->s2, ...}]" [s0 reset-event state-transitions ] s = atom s0 fn [evt] (if (= evt reset-event) { (println "Reset event, returning to " s0) (swap! s (fn [_] s0)) } { [actions transitions] = state-transitions @s (if-let [new-state (transitions evt)] { (println "Event" evt "causes transition from" @s "to" new-state) (doseq [f actions] (f)) (swap! s (fn [_] new-state)) } (println "Unexpected/unhandled event" evt "in state" @s)) })

alternately, the programmer could choose to use the implicit block syntax for the ifs, forcing them to use explicit blocks for the fn (which might be a good idea anyway; i get having syntax for 'let', but not so much for 'fn'):

defn make-fsm "creates an fsm with initial state s0, a reset event, and a map of transitions. [state-transitions] must be a map of state->[[f1 f2 ...] {e0->s0, e1->s2, ...}]" [s0 reset-event state-transitions ] s = atom s0 {fn [evt]

    if (= evt reset-event) { 
        (println "Reset event, returning to " s0) 
        (swap! s (fn [_] s0))
      }
      {
        [actions transitions] = state-transitions @s
        (if-let [new-state (transitions evt)] {
            (println "Event" evt "causes transition from" @s "to" new-state)
            (doseq [f actions] (f)) 
            (swap! s (fn [_] new-state))
          }
          (println "Unexpected/unhandled event" evt "in state" @s))
      }
   }

hmm.. alternately.. we could use ':' to open a new implicit block within the current one (which will be closed, without closing the enclosing one, upon an empty line:

defn make-fsm "creates an fsm with initial state s0, a reset event, and a map of transitions. [state-transitions] must be a map of state->[[f1 f2 ...] {e0->s0, e1->s2, ...}]" [s0 reset-event state-transitions ] s = atom s0 {fn [evt] if (= evt reset-event): (println "Reset event, returning to " s0) (swap! s (fn [_] s0))

      :
        [actions transitions] = state-transitions @s
        (if-let [new-state (transitions evt)] {
            (println "Event" evt "causes transition from" @s "to" new-state)
            (doseq [f actions] (f)) 
            (swap! s (fn [_] new-state))
          }
          (println "Unexpected/unhandled event" evt "in state" @s))
   }

no, because at then end, because you have two nested :s, you have to have two empty lines to close them, violating the "don't measure quantity of whitespace" principal.

how would we like it to look? maybe like this:

defn make-fsm "creates an fsm with initial state s0, a reset event, and a map of transitions. [state-transitions] must be a map of state->[[f1 f2 ...] {e0->s0, e1->s2, ...}]" [s0 reset-event state-transitions ] s = atom s0 {fn [evt] if (= evt reset-event): (println "Reset event, returning to " s0) (swap! s (fn [_] s0)) : [actions transitions] = state-transitions @s (if-let [new-state (transitions evt)] { (println "Event" evt "causes transition from" @s "to" new-state) (doseq [f actions] (f)) (swap! s (fn [_] new-state)) } (println "Unexpected/unhandled event" evt "in state" @s)) }

so, maybe a block started by a ':' must be 'held open' by another ':' at each implicit block closure; and a ':' on a blank line doubles as an implicit block closure, just like an empty line.

but now does nesting those get confusing?

defn make-fsm "creates an fsm with initial state s0, a reset event, and a map of transitions. [state-transitions] must be a map of state->[[f1 f2 ...] {e0->s0, e1->s2, ...}]" [s0 reset-event state-transitions ] s = atom s0 {fn [evt] if (= evt reset-event): (println "Reset event, returning to " s0) (swap! s (fn [_] s0)) : [actions transitions] = state-transitions @s if-let [new-state (transitions evt)]: (println "Event" evt "causes transition from" @s "to" new-state) (doseq [f actions] (f)) (swap! s (fn [_] new-state)) : println "Unexpected/unhandled event" evt "in state" @s }

yes; because indentation is ignored, so how do you know that the second lone ':' isn't for the first level of 'if'?

you could require '::' for a nested one, but this violates the principal that you should be able to cut and paste inner code.

in this particular case, however, we could just make the rule that a lone ':' also belongs to the innermost :-block.

but now if the first branch itself ended with an 'if', you wouldn't be able to use : on it. this seems like it would be prone to error.

how about if you use :s to specify how many parts the construct has? so instead of introducing grammar for 'if', you just say 'if::' if you want it to eat two blocks (in addition to whatever other arguments you give it outside of those blocks)?

defn make-fsm "creates an fsm with initial state s0, a reset event, and a map of transitions. [state-transitions] must be a map of state->[[f1 f2 ...] {e0->s0, e1->s2, ...}]" [s0 reset-event state-transitions ] s = atom s0 {fn [evt] if (= evt reset-event):: (println "Reset event, returning to " s0) (swap! s (fn [_] s0)) : [actions transitions] = state-transitions @s if-let [new-state (transitions evt)]:: (println "Event" evt "causes transition from" @s "to" new-state) (doseq [f actions] (f)) (swap! s (fn [_] new-state)) : println "Unexpected/unhandled event" evt "in state" @s }

ahh.. that seems right!

--

and i forgot to take out the needless parens around individual lines (since this is implicit); and let's put the == back an an infix operator:

defn make-fsm "creates an fsm with initial state s0, a reset event, and a map of transitions. [state-transitions] must be a map of state->[[f1 f2 ...] {e0->s0, e1->s2, ...}]" [s0 reset-event state-transitions ] s = atom s0 {fn [evt] if (evt == reset-event):: println "Reset event, returning to " s0 swap! s (fn [_] s0) : [actions transitions] = state-transitions @s if-let [new-state (transitions evt)]:: println "Event" evt "causes transition from" @s "to" new-state doseq [f actions] (f) swap! s (fn [_] new-state) : println "Unexpected/unhandled event" evt "in state" @s }

and we can use : for the fn too since there are no top-level empty lines in there:

defn make-fsm "creates an fsm with initial state s0, a reset event, and a map of transitions. [state-transitions] must be a map of state->[[f1 f2 ...] {e0->s0, e1->s2, ...}]" [s0 reset-event state-transitions ] s = atom s0 fn [evt]: if (evt == reset-event):: println "Reset event, returning to " s0 swap! s (fn [_] s0) : [actions transitions] = state-transitions @s if-let [new-state (transitions evt)]:: println "Event" evt "causes transition from" @s "to" new-state doseq [f actions] (f) swap! s (fn [_] new-state) : println "Unexpected/unhandled event" evt "in state" @s

compare to the original clojure:

(defn make-fsm "creates an fsm with initial state s0, a reset event, and a map of transitions. [state-transitions] must be a map of state->[[f1 f2 ...] {e0->s0, e1->s2, ...}]" [s0 reset-event state-transitions ] (let [s (atom s0)] (fn [evt] (if (= evt reset-event) (do (println "Reset event, returning to " s0) (swap! s (fn [_] s0))) (let [[actions transitions] (state-transitions @s)] (if-let [new-state (transitions evt)] (do (println "Event" evt "causes transition from" @s "to" new-state) (doseq [f actions] (f)) (swap! s (fn [_] new-state))) (println "Unexpected/unhandled event" evt "in state" @s)))))))

suggested:

defn make-fsm "creates an fsm with initial state s0, a reset event, and a map of transitions. [state-transitions] must be a map of state->[[f1 f2 ...] {e0->s0, e1->s2, ...}]" [s0 reset-event state-transitions ] s = atom s0 fn [evt]: if (evt == reset-event):: println "Reset event, returning to " s0 swap! s (fn [_] s0) : [actions transitions] = state-transitions @s if-let [new-state (transitions evt)]:: println "Event" evt "causes transition from" @s "to" new-state doseq [f actions] (f) swap! s (fn [_] new-state) : println "Unexpected/unhandled event" evt "in state" @s

both are 14 lines long. The clojure is 91 words and 746 characters according to wc, including whitespace. suggested is 91 words and 676 characters, including whitespace.

perl -e 'while ($s = <STDIN>) {$s =~ /(\s*)/; $whitespaceCount += length($1)}; print $whitespaceCount ' < /tmp/t.txt

clojure indentation whitespace is 124 chars. suggested indentation whitespace is 102 chars.

so suggested saves 22 indentation whitespace characters and ((746 - 676) - 22) = 48 non-indentation characters. 48/(746 - 124) = 7.7% of the Clojure non-indentation characters. (i bet the saved characters were parens, although there were a couple of 'let's, and maybe i forgot something else)

and i think suggested is easier to read.

--

now let's go back and tackle the bigger question.

if you have a homoiconic language an immutable variables, like Clojure, then i think it must be more verbose to do this (Python:)

def f(w): if w_condition(w): x = g(w) else: x = h(w) y = transformFn(x) if y_condition(y): z = j(y) else: z = k(y) return z

because 'x' can only hold its value within a 'let'. But this 'let' must itself be inside a conditional branch that determines what value you are assigning (g(w) or h(w)). So this 'let' must be repeated twice, once within the w_condition(w) == True branch, and once within the w_condition(w) == False branch. Anything else that depends on x must also be repeated. So all the code involving y and z must be duplicated. Instead of literally duplicating it, what i guess you would do is split this into two functions, kinda like the CPS transform, as if you did this in Python:

def f(w): if w_condition(w): return z_from_x(g(w)) else: return z_from_x(h(w))

def z_from_x(z): y = transformFn(x) if y_condition(y): z = j(y) else: z = k(y) return z

which i guess is why you see so many small fns in clojure.

i dont like this, because (a) it increases the number of things (in this case, separate functions) in the programmer's mind that they have to keep track of/understand when first reading the program, and (b) it necessitates you to make up a name for the second part of the original function.

now one solution would be to make x a locally mutable variable. This is probably not 'the Clojure way' but this is the Jasper way. Another solution is if Clojure allows you to declare the variable in the f scope, and then not assign to it until farther down, but have that assignment be valid in the entire f scope. i dunno if that's the Clojure way, or even if its possible in Clojure, i'd have to ask.

i'm thinking it's not. (goes and looks it up). ok, it's sort of possible but it's not the clojure way.

http://clojuredocs.org/clojure_core/1.2.0/clojure.core/with-local-vars , http://stackoverflow.com/questions/940712/redefining-a-letd-variable-in-clojure-loop . also, i think these 'vars' are 'local' in the sense of thread local, but NOT limited by lexical scope (not positive..): http://stuartsierra.com/2013/03/29/perils-of-dynamic-scope

--

"prologue directive"s like strict mode and asm in javascript:

function MyAsmModule?() { "use asm"; module body }

--

i want to not have as many parens as lisp. lisp fans and programming language guys i talk to tend to laugh when i suggest that part of the lack of popularity is that ppl dont like the parens, but i think it's true.

it's a factor for me personally, too. mb that makes me a wimp

---

some syntax principals:

--

if x is of type T, should be able to say

x.y

to disabiguate between type T's y and type Q's y.

see http://ghc.haskell.org/trac/haskell-prime/wiki/TypeDirectedNameResolution for detailed discussion of this sort of thing in Haskell, and http://www.yesodweb.com/blog/2011/09/limitations-of-haskell , "Namespace clashing, particularly for record fields" for the need

--

actually the if:: syntax isnt very ambiguous; since the the number of clauses is also mandatorily delimited by :, you can count whether the number of clauses match the number of expected clauses (when there is a syntax error of this sort, there is some ambiguity about which clauses belong to which operator, but there's still an error because there is no ambiguity about the count)

--

with if:: you could even have 'if then else' by treating 'then' and 'else' as KEYWORD arguments, rather than positional arguments, to the if.

if (x == 3):: pr 'x was 3' else: pr 'x was not 3'

note that the number of colons is the total number of block args, not the number of colons to go. with this syntax you could also do:

if (x == 3):: then: pr 'x was 3' else: pr 'x was not 3'

that's just the difference between {if (x == ) {pr 'x was 3'} else/{pr 'x was not 3'}} and {if (x == ) then/{pr 'x was 3'} else/{pr 'x was not 3'}}

--

hmm if there is more code after such an 'if' how do we delimit the end of the if? right now the only mechanism provided is to make it the end of a block, either implicitly by putting an empty line at the end, or explicitly, by surrounding the whole if in {}s.

should we give any other options? i dont think so; whatever else we can think of won't be much better than braces.

--

compile-time conditionals

like C's #ifdef, Nimrod's "when" http://nimrod-code.org/tut1.html#when-statement

--

so i guess the : and the , are slightly similar in that 'a , b' means '(a) (b)' and 'a; : b;' means '(a;) (b;)', except that the , is delimited by EOL whereas the : must be on its own line and otherwise it takes a label

i'm having second thoughts about the 'if::' thing though. why not just use braces if you have to type it twice anyways

if { condition then: first else: second }

that's like:

if((condition), then=(first), else=(second))

in other words it's a node labeled 'if' which has an edge labeled 0 going to the block 'condition', an edge labeled '1' and 'then' going to the block 'first', and an edge labeled '2' and 'else' going to the block 'second'

another way to say that is that the above compiles to a node with label 'if' and edges:

{condition} 'then'/{first} 'else'/{second}

later (is this right? not sure, doublecheck) so i guess then we have:

defn make-fsm "creates an fsm with initial state s0, a reset event, and a map of transitions. [state-transitions] must be a map of state->[[f1 f2 ...] {e0->s0, e1->s2, ...}]" [s0 reset-event state-transitions ] s = atom s0 fn [evt]: if (evt == reset-event) { println "Reset event, returning to " s0 swap! s (fn [_] s0) : [actions transitions] = state-transitions @s if-let [new-state (transitions evt)]:: println "Event" evt "causes transition from" @s "to" new-state doseq [f actions] (f) swap! s (fn [_] new-state) : println "Unexpected/unhandled event" evt "in state" @s }

--

maybe have one sigil for 'edge label' and another for 'node label'?

mb '/' for edge label (and then

for right associativity) and ':' for node label?

this seemingly provides for nice homoiconicity but actually within a data constructor we still want a way to label both nodes in the data, and separately label nodes in the AST, e.g. we might want a node label 'a' in the data but still want to annotate the place where we define a as "ANNOTATION1" in the source code.

--

hmm actually if we alternate between nodes labels and edge labels that helps us disambiguate... hmm..

if:: condition then: if:: condition2 then: thenThenStuff else: thenElseStuff else: elseStuff

if: condition then/ if: if:: condition then: if:: condition2 then: thenThenStuff else: thenElseStuff else: elseStuff

       condition2 
       then/
         thenThenStuff
       else/
         thenElseStuff
  else/
    elseStuff

hmm.. but how do we know the last 'else' shouldn't be part of the inner 'if'? i guess this doesn't work with nesting

--

another mode: 'command mode'. like Tcl's commands (see the bit in the tcl notes file; todo learn more tcl and see if i'm understanding it right): first argument on each line is a command, others are autostringified

---

operator precedence proposal:

Precedence Operator 8 . (note: in haskell this is looser than fn application) (right associative) 7 function application (left associative) 6 all unary operators 5.5 exponentiation 5 (and most everything else) 4 ++ --, 3.5 +++, cons (dunno about this one; in Haskell they are a lower level than addition, also right associative) 3 == != < <= > >= in/elem, <...

...> (trinary ops)
    2             && || (in haskell these are right-associative but i think i'll make them non-associative)
    1.5           $, >> (haskell's $ and >>, i mean) (in haskell these are right-associative)
    1             = (not really an operator)
    0             other stuff that is not operators

note: within these levels, stuff is non-associative and requires explicit parens, unless it is one operator repeated and it associates

---

trinary infix operators

---

infixify is a trinary infix operators

trinary ops could be of the form <...

<bob> could be the binary infixification of bob. <bob
...>, e.g. <bobbob>, <**>
bob> could be the trinary infixification of box.

--

if you are willing to screw with basic arithmetic you can free up all sorts of stuff:

++ for addition -++ for subtraction for multiplication - for division 2 for gt, >= for ge ^^ for exponentiation

now we've freed: +, -, *, /, <, >, ^

note that here - is a syntactic sugar metaoperator, e.g. it turns an operator into its inverse. by default the first argument of the inverted operator corresponds to the result of the normal operator, and the normal operator is inverted with respect to its first argument, but this can be overridden. One syntax for overriding it might be:

normal: bob a b c inverse w/r/t b: -bob a result- c and another might be: inverse w/r/t b: -bob a _ c in this case how do we give the result arguments, though?

it's important to have / and < free if we want to use them as matching delimiters. But if 3 be a delimiter either (that's not exactly true; we can have 4 would be good for that, but now we're using that for gt, lt.

we still have / \ and < > free in this scheme. Could use < > for character-based escape, e.g. <r r> for regexs, <c c> for comments (or mb <-- -->).

we could use / like Haskell's $, and use \ for the opposite, with the convention that by default, a line is left associative except for the /, but if you see \, it's right-associative except for the \. That may be confusing tho.. also if we use \ as an operator or delimiter then we have to find something else for EOL continuation, and character escape, the traditional uses of \. we could use

instead of /.

also, note that $var is interpolation, and ?var is variable (e.g. to specify a random variable in a formula). {} is code. () is parens. [] is data. - is allowed in the middle of a word, e.g. this-is-a-normal-identifier. we need a syntactic metaop for map. *var is glob expansion. we need an 'in/elem'; this should be a symbol, not alphanumeric, because it is in the precedence table. Everything in the precendence table should have a punctuation in its identifier. we need cons, and append, and should both of these be separate from each other and from addition? we need string interp like Python (probably %), and should this have precendece? probably. and we need an annotation region delimiter (perhaps
? / \ ?). we need isa (and should this have precendece? probably). is isa the same as elem combined in some way with <=, e.g. a isa T means that a is an element of some type T1, and T1 <= T? probably. note that, somewhat similarly, cons is append composed with [] (wrap), e.g. cons(a,b) = append([a],b).

might want to use ^ for annotation.. or for arithmetical hyperoperators.

could even throw out multiplication and just use +1 for addition, +2 for multiplication, (+0 would be successor, except that's unary) etc. this would free up ++ and for other things, but would make addition, subtraction, multiplication, and division pretty annoying to type (+1, -+1, +2, -+2), and possibly hard to read.

could use + for addition, ++ for multiplication, +++ for exponentiation, etc. then either - or -+ for subtraction, etc.

the friendlier thing to do would be to leave ++, --, , as addition, subtraction, multiplication, division.

otoh if what we're really interested in is --, , then maybe just the usual +,-,*,/ is best. if other considerations are close, that is greatly preferred, b/c it won't confuse everyone.

we could still say that unary operators must be physically attached to their tokens; so f -a is 'apply f to the inverse of a' but 'f - a' is 'apply - to f,a'. this would allow us to still use * as glob expansion.

note that -3 still works, as -3 is the 'inverse' of 3. (but actually it's only the additive inverse, e.g. 0-3.. the multiplicative inverse is 1/3.. doing these substitutions automatically might be possible, you just need the compiler to know the additive and multiplicative identities.. hmm..)

could have an operator prefix that means 'bitwise', e.g. %, so %

is bitwise OR, %&& is bitwise AND, %%- is bitwise NOT, etc.

--

mb require all symmetric-looking operators (all operators which are palindromes of only characters whose ASCII visual appearance is horizontally symmetric) to be associative?

or even commutative? an example of a common non-commutative associative operation is string concatenation. so, i guess the question is, is it intuitive to allow symmetric-looking operator like ++ represent string concatenation? or should we make them things like +>?

or require all operators to be associative? well, subtraction is not associative ( http://www.computerhope.com/jargon/a/assooper.htm ) and it should be an operator so so much for that. so if we adopt the above convention, then subtraction should be -+ not -.

note that we still need an 'assoc' annotation for functions which are not assigned to operators (esp. since each operator must also have a non-operator form).

my current guess is: yes, require all symmetric-looking operators to be associative (but not commutative)

--

could use ; for EOL, ;; for EOL plus comment-to-EOL, and , vs ,, for row vs. column in data

---

x.f = (f(x))

---

could use / and \ as -> and <- (or probably vice versa, / for <- and \ for ->), and use that for assignment, and reverse = for symmetric reference binding

e.g.

x / 3 print 3

rather than x := 3 print 3

but then must be division, and so we've given up a good paired delimiter, unless we use / for boundaries instead of , as above

---

from the discussion in the middle of jasperNotes3.txt:

now that we're not using =s anyhow, and we have to type `` no matter what, let's use it for arguments, rather than return args:

full: funcname = return args `args` fn_body implicit args: funcname = return args `` fn_body_with_implicit_args one anonymous return arg: funcname = `args` fn_body_with_implicit_args anonymous: (`` fn_body_with_implicit_args)

  a .bob/b .harry/c = [1 2 .harry/3 .bob/4 5]  /// a==1, b==4, c==3
  f .bob/3 .harry/4

note that now we can still use a/b to denote a directed arc in data.

note: could also distinguish between attached and unattached = for keyword vs. other things

---

implicit return like ruby

---

instead of using a separate matched delimiter pair for boundaries, could use something like:

[redBoundary: ] (redBoundary: ) {redBOundary: } <redBoundary: >

--

note that <> does not trigger matching in emacs in text mode, but () [] {} do. so maybe the inner matching that doesn't have to match, which i was calling (), should be <> ; or mb boundaries, which dont have to match, should be <> ?

--

? is sort of no different from quote (and $ no different from antiquote).. but consider statistical random variables; if we say, "?X + 3", where "?X" indicates that X is a random variable, then the +3 operation applies, not to the distribution, but to whatever value ?X takes in some instance. But "mean[?X]" applies to the distribution (todo should we use the [] notation too? if not, what to replace it with? this could also be useful for vectorization and the map token operator and the like). But now if we say "?X + y", where y is a 'normal' variable, then 'var[?X + y]' can be different from 'var[?X + ?Y]', because it matters if ?Y is correlated to ?X.

so what i am getting at is that ?X is not strictly the same as quote(X), because the output of quote(X) is a variable, which is a type of expression, and you can't add 3 to an expression ("quote(X) + 3" is a type error), but you can add 3 to a random variable, even though "?X + y" has similar properties to quote(X + antiquote(Y))

--

separately, let's consider the f(x) vs. mean[X] notation and if not that, find something like it

Since we are using "f x" for f(x), and ?X for random variable X, maybe mean[x] be "mean? X"

i like that. it seems like a better use of ? than just to mark predicate functions.

note that if we do that we are allowing prefix and postfix of the same sigil to mean different but related things. which is also useful with '-' to mark the input that the inverse is w/r/t.

--

i looked up what this is called in math

mean[X] is called the 'expectation operator'. In stats, things called 'operators' are written with square brackets.

this following pages are relevant:

https://en.wikipedia.org/wiki/Notation_in_probability_and_statistics

https://en.wikipedia.org/wiki/Differential_operator

http://quantum.phys.cmu.edu/CQT/chaps/cqt03.pdf
w>' (a 'ket') to refer to 'a wave function for wave w', which is needed because actually wave w has various different wave functions depending on which variable you use (f(x) and g(p), if x is position and g is momentum, for example). i presume you use the ket when you are saying something that applies equally no matter which variable you choose for the wave functions. the <xy> notation apparently means the inner product of two wave functions.

--

variant of operator precedence parsing that might handle the two types of - better (?not sure though?):

http://compilers.iecc.com/comparch/article/01-07-068

---

list of operators from various languages on this page might be helpful:

https://en.wikipedia.org/wiki/Operator_%28computer_programming%29

--

in Pyret, i think : is synonym for begin and ; is synonym for end

---

@ as 'from' for lifting functions on views

--

https://en.wikipedia.org/wiki/Del

del in 3D can be represented as [d/dx, d/dy, d/dz], where d/dx is a function on expressions.

gradient can be represented as del f = the application of del to f = sort of the converse of a map of f over del; instead of getting [f(d/dx), f(d/dy), f(d/dz)], we get [d/dx(f), d/dy(f), d/dz(f)].

Divergence can be represented as dot(del, v) = like the dot product except that when an expression is multiplied by d/dx, the result is the application of d/dx to the expression.

want to be able to make Jasper represent this sort of thing

mb use __apply? or i guess just overload * for this sort of operator?

--

MB make Jasper annotation character unshifted because it will be common

MB . Or - or '

--

clojure uses #(+ % 2) for (`x` + x 2). we probably want something like ``(+ _ 2), e.g. `` instead of + and _ instead of %

--

did i mention that source code is UTF-8 and must be ASCII, except in strings and in character metaescapes?

--

in assembly they typically use the syntax LABEL: for labels, perhaps i should think about the same thing for Jasper

--

do we need a :: meaning 'to the right of this is a type annotation'? if so, does ^ mean this, instead of ::? if so, do we still need a block syntax like {} for annotations, or is ^() good enough?

could just use ^ for annotation of Jasper nets, and ^^ for meta-annotation (e.g. type annotations) and ^^^ for meta-meta-annotation (e.g. comments and custom tool directives). then any other syntax for these could be regarded as syntactic sugar synonyms.

--

Parallax Propellor 'Spin' language has a useful syntax for binary literals and related array slicing:

in the following, 'a' is an array of binary I/O lines. X is a byte. Here are 3 ways to assign values to 8 of the lines in a at once:

a[16..23] := %11111111 a[16..23] := %1111_1111 a[16..23] := x

In the second one, the underscore is ignored; it is just for readability.

--

idea from Hoon: things connected by . should be resolved right to left, e.g. if you have an object test with a field foo, this is referenced as foo.test, not test.foo. My reason for liking this is that it is the same reason to have y = f(g(x)) instead of ((x)g)f = y; since text is left-aligned in the editor, it's nice to scroll down the page, quickly scanning the left-most things for the final operation before assignment to the variable, which is what you are usually looking for.

--

y'know the octave function defn syntax is so convenient to cut and paste, maybe its better than our `` syntax above?

--

  1. hastags seem to be the standard for a single character type prefix for tags, and @user for usernames.

(contrast with +tag @context in the todo.txt standard)

--

maybe just put # at the beginning of 'nonessential' lines rather than have footnotes?

--

maybe try to make Jasper Android-keyboard friendly with an eye towards a 'learn to program' app?

on my keyboard i have:

apparently on the Android 2.2 default keyboard, you get the letters and can also swipe up to access the numbers and:

apparently on the Android 4.1 default keyboard, you get the letters and ,.'-/ and can longpress to access:

apparently on the Android 4.4 default keyboard, you get the letters and !?,./

apparently on Windows 8.1 tablet, you get the letters and ,.':-? and can longpress for the numbers

apparently on the Android 4.4 default keyboard, you get the letters and . and can longpress for @#$%&*-+_!:;/? and maybe two others

using 'google keyboard':

^{}[] as well as some other symbols (bullet, sqrt, pi, division, multiplication, backwards Paragraph sign, delta, a bunch of currency signs, degree sign, copyright, r (reserved), TM, c/o)

---

on my Android, neither the hardware keyboard nor the first few pages of the software keyboard give me the pipe character '

'. So better not use that for anything common.

---

should look at the ASTs of languages such as Python and Haskell and ask whether these can easily be represented in our jasper graphs, and if so, can we generalize this to make it easy to make up new syntaxes? mb also consider ometa

--

in Python we have grammar-supported constructs like while condition:\n body. we don't want a complicated grammar for Jasper (although Python's is not that bad: https://docs.python.org/3/reference/grammar.html ), because we want something close to 'homeoiconic', if not quite. But perhaps we could have some general grammar-supported constructs that can be specialized by the user? e.g. instead of only having almost no grammar (e.g. Lisp's '(while condition body)'), or hardwired grammar like Python's 'while condition:\n body' we could have something like "%constructname arg1:\n arg2", where constructname is a user-defined construct, so '%while arg1:\n arg2', where the % indicates that it is this sort of construct. E.g. predefine a small number of grammatical forms, hardwire these into the grammar, and require sigils to access them.

i'm not saying we should use Python's 'while' grammatical form in particular, or that we should use '%' as a sigil for this, that's just an example.

in this case (while), i probably prefer the more general scheme like (example for 'if', not 'while'):

if { condition then: first else: second }

e.g. we have a potentially user-defined control structure (if), which has a positional argument ('condition'), and two keyword arguments ('then' and 'else').

--

y'know, as for matched delimiters, we have three obvious ones and three obvious uses:

(): grouping within computation []: data constructor {}: blocks of code

perhaps we should just use them in this fashion.

our grammar is still somewhat simple if we don't use e.g. [] for indexing.

--

haskell's convenient operator defn syntax:

a + b = ...

--

import .. hiding ...

--

single + (rather than ++) is arithmetic (addition) for readability/remember-ability? (and same for -, /, *)

--


Footnotes:

1.

 and 

2.

 for lt, <= for leq, 

3.

 and <= are not free, we can't have a single < be a delimiter even without whitespace around it (unlike the typical use of (), e.g. (a+b)*3). And if << is not free then we can't have << 

4.

 be lt but have <<text be a delimiter; but then what about custom lessthan-like comparison operators? you'd want to let the programmer make those start with <<)

if subtraction is -++ or -+ instead of - or --, then we could use -- as the to-EOL comment delimiter. We want to encourage commenting, so it's important that the EOL comment delimiter be easy to type. ;; is another candidate.

we could use / and \ or and
as boundary delimiters. Note that the nesting of boundaries with lexical scopes is not straightforward; e.g.:

transaction a = b if a > 3: c = d doE transaction
else: transaction
doF

in this case the compiler infers that each possible lexical path out of the 'if' is covered by a boundary end. But we may instead want to let the user do:

transaction a = b if a > 3: c = d doE else: transaction
doF transaction

which would be especially helpful if there was a switch with many cases, instead of an if with two cases. Here the compiler has to realize that, for the 'else' branch, the transaction is already closed so the later transaction close outside if the 'if' has no effect in that case.

also, not that with this sort of thing, we might have:

transaction1 transaction2 a = b if a > 3: c = d doE else: transaction2
doF transaction1
transaction2

so now the compiler must track each of transaction1 and transaction2 separately.

so we want to allow the transaction to be specified dynamically?

x = transaction1 y = transaction2 $x $y a = b if a > 3: c = d doE else: z = y $z
doF $x
$y

i have a feeling this may make standard parsing techniques not work. but maybe simple ol' recursive descent still will.

also, we still need a character-based metaregion for other languages (like shell backticks) and for regexs. I used to think that <<