proj-oot-ootSyntaxNotes4

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

gf == f(g(x))

but x

gf 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?>z, where P is punctuation, to be custom ternary operator syntax, and that suggests we ask what the 'default' case where P is the empty string, should be ( x<y>z ). And it seems to me that the most obvious/easy to remember/simple answer would be for x<y>z to apply a function y to x and z. Which means it is just infixify. As a bonus, now we don't have to find other syntax to infixify.

I don't like having to type two (shifted, even!) characters instead of just one to infixity, but it does make it easier to read. And i think the value of the simplicity/making it easier to remember is more than the value of reducing characters in this case.

One remaining question is: how do we tell when we mean <y> and when we just happen to have '<' and '>' in the same expression? And i guess the answer is that '<' and '>' can't appear on the same level without something in between them indicating precedence.

Another question is: so are we then allowing stuff like:

x < blah blah (very long) + very complicated * expression in the middle > y

to mean the infixification of "blah blah (very long) + very complicated * expression in the middle"? That appears to me to be hard to read.

So, let's make the further requirement that <P and P> must be ATTACHED to the inner argument for ternary operation.

But that still permits:

x <( blah blah (very long) + very complicated * expression in the middle )> y

which is more readable but still forces the reader to find the other )> before they know what's going on (as an aside, is this even LL(1)?)

So, let's make the further requirement that the inner argument of a ternary operator must be one identifier or literal, not an expression.

So it must be something like x <y> z.

A further problem is things like <=. Clearly we want <= to be a binary operator. But someone might want to write eg "x <=y". That's in theory slightly annoying for the reader but in practice i think you can instantly see that there is no => attached to the other side of the operator. It may present a technical problem; i guess that's not LL(1) because it would tokenize into "ID SPACE <= ID". You can't tell that the <= ID is not a ternary operator without looking ahead one more token to see if it's =>. So i guess we're LL(2) but not LL(1).

Is LL(k) much worse than LL(1)? Apparently it's simple enough with recursive descent but more annoying for table-based:

http://stackoverflow.com/questions/9000969/how-to-construct-parsing-table-for-llk1

that question points to http://wayback.archive.org/web/20140809080713/http://slkpg.byethost7.com/llkparse.html for details

but http://stackoverflow.com/questions/9000969/how-to-construct-parsing-table-for-llk1#comment12001531_9000969 selects a good solution for our case, where most stuff is LL(1) and there's a few (or so far, just one) LL(2) exceptions: just create a new token class in the lexer for each case of lookahead >1. So in our case, the lexer tries to match a regexp for <PidentifierP?> before it matches against <P as its own thing. Simple enough.

---

should allow list construction by addressing into with []:

x = [1 2 3 4] x 0 == 1 x.0 == 1 x.[1 2 3] == [2 3 4]

(but if f.x is just f(x) then how is this not just f([1 2 3])? or maybe it is, and if 'f' is an array, then this is what it returns on this input)

---

instead of $x for interpolation and ?x for metavariable (interpolation and metavariable are opposites in that one says, treat this as a variable and substitute its value when you otherwise wouldn't, and the other says, don't treat this as a variable and substitute its value when you otherwise would), one idea is $x for interpolation and x$ for metavariable.

for awhile i thought the latter idea seemed more symmetric/regular, but currently i am leaning towards the former, as both of these seem like a 'sigil' sort of thing and so should be a prefix.

---

syntax for fexpsr? $:= instead of :=?

or since we already have lazy annotations on parameters, maybe what really distinguishes fexprs for us is the preservation of the original AST. So maybe $^ on the arguments.

---

idea:

When $ is used at the beginning of a statement, it is a shorthand for an expression that applies something to $ and then assigns the result to $, eg:

{
$ + 1

is equivalent to:

$ = + 1 $

---

i'm having second thoughts about auto-closing parentheses. That'll make text editors which automatically check text files for matching parens angry, will make writing syntax highlighting plugins for external packages harder, and might confuse newbies. Maybe if we provide enough syntax for grouping (eg a 'pipe' like Haskell's $, something like ',' or ':' to parenthesize both sides, a way to reverse fn composition), there wont be too many trailing parens anyhow.

---

in Perl6 "users can quite easily turn any copy-producing method into a mutator using the .= operator:". They give the example '$a .= chomp;'. That's probably the syntax i was looking for for in-place mutation.

but, at least as syntactic sugar, it doesn't save you much: consider "a .= chomp" vs "a = chomp a". And i still hold out hope that a Sufficiently Smart Compiler can automatically figure out when to replace copying with in-place mutation (i guess by noticing when the original value is never used again, then propagating that analysis recursively into the function that was called). So i'll leave it out.

---

how to notate polymorphic type signatures?

x, x -> x: ok.. but sorta unclear..

?x, ?x -> ?x: ok.. but lotsa typing.. mb ok tho

---

on the shell often you need to put something in single quotes to prevent punctuation characters from being interpreted, eg when you pass a URL with question marks etc in it to 'wget'. But if the thing doesn't have punctuation chars then you can just pass it.

---

Rust uses

xfor lambda x: and uses : for type annotation, eg:

fn main() { let x = vec![1, 2, 3, 4, 5]; for v in filter(x.into_iter(),

v: &i32*v % 2 == 0) {
        println!("{}", v); // 2, 4
    }}

---

some syntax from a proposal for Golang:

ok := n -> ch

'ok' is an error code, -> is send-to-channel.

---

maybe .= or =. for 'is'

maybe <:= for <:

---

on scala syntax:

lmm 8 hours ago

It's pretty light on syntax compared to C/ALGOL-family languages. Function bodies are just another block (or expression), as are things like try. Operators are just method calls (except for precedence, and the precedence table is much shorter than C/Java/etc.).

reply

---

could append ! to a fn to turn "fn1 x" into "x = fn1 x"

mb only the first argument is reassigned?

and x +! 3 --> x = x + 3 (ie, C's x += 3; this syntax is more general than C's b/c it can apply to ordinary prefix fns too)

---

some verbosity in Rust:

use std::fs::File;

for std libs, why not just 'use File'?

---

maybe, except for the caps vs upper vs lower-or-camel distinction, make Oot case-INsensitive

this guy points out that it could lead to bugs and hence t security issues:

https://news.ycombinator.com/item?id=11930790

also mb make the caps and upper and lower-or-camel identifiers live in the same namespace; eg if you have an identifier "CONSTANT1", you can't also have "constant1" in the same lexical scope.

---

Elixir has 'sigils', which appears to be an extensible literal constructor notation:

eg in connection with the Calendar module the following are described:

" ~D[2016-05-29] - builds a new date ~T[08:00:00] and ~T[08:00:00.285] - builds a new time (with different precisions) ~N[2016-05-29 08:00:00] - builds a naive date time " -- [2]

---

alternative to Oot Footnotes:

just use '#' to designate lines that should be collapsed by default by the IDE. Or alternately, if we assume that most lines are nitty-gritty error-checking etc, use '#' to mark lines that should NOT be collapsed by default. Or, my favorite, if there are no #s in a block, then the whole block should be shown by default; but if there are any #s in a block, then ONLY the # lines should be uncollapsed by default.

the # does not have to be at the start of the line, and still starts a comment.

actually, still, let's just use ;s

so now #s are free for other stuff

---

Clojure sum-of-squares function

    (defn sum-of-squares [a] (reduce + (map #(* % %) a)))

---

the previous demonstrates the use of # for lambda, and the use of % for implicit argument.

---

jpulec 1 day ago

I think lambda syntax can be a bit cumbersome, but that aside what I really miss is a clean syntax for chaining functional operations. So often I find myself thinking about data in terms of 'pipelines'. i.e. in JS:

  _.chain(values)
   .map(() => {})
   .flatten()
   .compact()
   .uniq()
   .value()

vs Python where doing the same thing becomes either a nested mess of function calls or comprehensions or a for loop.

reply

SoftwareMaven? 1 day ago

But that's a function of API, not the language itself. Django (and most ORMs, I believe) support that kind of behavior:

    MyTable.objects.
        filter(some_row__gt=5).
        exlude(other_row='q').
        order_by('other_row')

The Python iterable APIs have decided to use nesting rather than chaining, but you can still have an underscore-like API: https://github.com/serkanyersen/underscore.py

The bigger problem remains: lambda functions are hideous in Python. map() will forever be ugly if you try to use it in the same way it is used in most functional languages.

reply

chris_7 1 day ago

This sort of API is hard to implement in Python though, because there's no formal notion of interfaces, so you cannot extend all iterables generically. So you need to use free functions (which don't read well when chained) or a wrapper object (ick).

reply

--

moosingin3space 1 day ago

Elixir and F# have my favorite syntax for that:

    values |> map(&({})) |> flatten |> compact |> uniq

Although, the closure syntax is a little clunky before you get used to it.

---

So all of:

Coconut, Elixir and F# use '

>' for chaining (pipeing)?

---

coconut "lazy-ify (suspend)" $ operator:

akkartik 1 day ago

What are all the $ for? They seem ugly..

reply

dkersten 1 day ago

Quoting from the tutorial:

    Second, the partial application. Think of partial application
    as lazy function calling, and $ as the lazy-ify operator,
    where lazy just means “don’t evaluate this until you need to”.
    In Coconut, if a function call is prefixed by a $, like in this
    example, instead of actually performing the function call, a
    new function is returned with the given arguments already 
    provided to it, so that when it is then called, it will be
    called with both the partially-applied arguments and the new
    arguments, in that order. In this case, reduce$((*))
    is equivalent to (*args, **kwargs) -> reduce((*), *args, **kwargs).

https://coconut.readthedocs.io/en/master/HELP.html (about a third of the way down, just search for "$" and look near the first occurrences)

I agree though, it makes the code look very... noisy.

reply

guitarbill 10 hours ago

...Would have been cool if it was *, which is already used for arguments.

---

guitarbill 1 day ago

I'm all for syntactic sugar, but my experience with teaching programming is that Python is easy to pick up because of the lack of weird operators. Decorators are an obvious example where many struggle; and this looks like another.

...

IMO Python operators feel more cohesive/uniform than Ruby or Perl.

reply

dkersten 17 hours ago

Python has lots of weird operators.

List/dict comprehension, list slicing, * and in arguments. Comma for tuples (which make sense to me, but add a lot of magic, especially since, in my experience, beginners often assume that the parentheses are what make tuples). There's many more examples, but those are the ones that immediately spring to mind.

Reading actual production Python code is not any easier than many other languages and certainly far from "executable pseudocode"

reply

---

do we need BOTH:

g'
uniq'

both Unix

and F#> [3] seem to be the second one (sorta... eg in Unix 'createList uniq' doesnt map 'uniq' over each item on the list, rather 'uniq' in run once, given the stream as input). Clojure -> ('threading') seems to be the former; '-> x f g) = g (f x). Some OOP languages use . chaining for that; x.f.g.

But as noted in the 'sorta', these aren't as different as they seem, because eg Unix 'createList

uniq' is actually like uniq(createList()); it's just that uniq's signature takes a list (well, stream) input.

Clojure also has '->>' which is like -> but passes into the last argument of the function instead of the former; and 'as->' ('pass binding'):

"(as-> expr name form1 form2 form3 …) evaluate each form in turn, such that in form1 the name has value of expr, and in form2 the name has the value of form1's result, etc."

Clojure also has some-> and some->> which "short-circuits execution when an expression evaluates as nil at any point in the chain".

[4] [5]

---

i keep thinking it might be nice to make use of the old Perl idea of having a sigil indicate if an identifier instance should be considered as in 'list context' or 'scalar context'.

if we did that, then Clojure -> and F#

> could both use the same syntax, but the thing on the left could be interpreted as a scalar or a list based on its context; eg

$createList

uniq

vs

@createList

uniq

but is that really useful? 'uniq' expects a stream, so $createList would just give a stream with just one item in it (that item happens to be the list created by createList). And we could do that anyways with a list constructor, eg

[createList]

uniq

---

for creating new versions of immutable data, F# appears wo use 'with', eg:

state with UnpaidItems?=newLis

[6]

---

i kinda like F# but think its syntax is not quite lighweight enough:

https://fsharpforfunandprofit.com/posts/designing-for-correctness/ :

:

let addToActiveState state itemToAdd = let newList = itemToAdd :: state.UnpaidItems? Cart.Active {state with UnpaidItems?=newList }

...

type ActiveState? with member this.Add = addToActiveState this

:

would prefer just to define a class and omit 'member' and 'state' (in addition to less verbocity, this removes the need to have two names for the method, Add and addToActiveState):

class ActiveState? add itemToAdd : let newList = itemToAdd :: this.UnpaidItems? Cart.Active {this with UnpaidItems?=newList }

:

   match newList with
   | [] -> Cart.Empty NoItems
   | _ -> Cart.Active {state with UnpaidItems=newList}

would prefer to just use Python-style truthiness:

if newList: Cart.Active {state with UnpaidItems?=newList} else: Cart.Empty NoItems?

: let addToEmptyState item = returns a new Active Cart Cart.Active {UnpaidItems?=[item]}

'let' is too verbose, would prefer Haskell-style:

addToEmptyState item = returns a new Active Cart Cart.Active {UnpaidItems?=[item]}

i note that a common complain here is being forced to make up more names than are needed; extra names for ADT constructors vs their types (the common case is that the constructor's types are unique so this isnt usually needed), extra names for functions as member functions and the raw functions (the common case is that these functions are only used as member functions, so this isnt usually needed), and probably (if it's like Haskell) extra names for extra variables b/c even local mutation isnt permitted. I would prefer to let the common case be short and make the user do an extra assignment when the powerful case is needed (eg make a type alias when you need two constructors of the same type; define the raw function using 'unbound method' reflection on the class when you need to use it outside of the class, or similarly, define the class method as an alias of the raw function.

---

F#'s sum types with 'of' (ADTs) are interesting, although i always find this sort of renaming a little confusing and wordy (eg why not just abolish Empty, Active, PaidFor? and just have Cart.EmptyState?, etc? When you really want to just say "Empty", you can say 'Empty = Cart.EmptyState?', but o/w it's a little confusing this way b/c you see Empty in the source code and you have to look up what it is, then you see that it's a constructor for EmptyState? in Cart, and you then have to look up EmptyState? too; also this way adds syntax ('of'); is there anything you can do with this way that you couldn't do with an 'Empty = Cart.EmptyState?' later? Perhaps the reason is that this way lets you have two Cart choices that are the same type, eg you could have 'type Cart =

choice1 of Int choice2 of Int'? If so, that should be the special case, b/c usually you see something like this, where someone has to invent two words like 'Empty' and 'EmptyState?' where there should just be one):

type CartItem? = string placeholder for a more complicated type

type EmptyState? = NoItems? don't use empty list! We want to force clients to handle this as a separate case. E.g. "you have no items in your cart"

type ActiveState? = { UnpaidItems? : CartItem? list; } type PaidForState? = { PaidItems? : CartItem? list; Payment : decimal}

type Cart =

Empty of EmptyState?
Active of ActiveState?
PaidFor? of PaidForState?

[7]

---

in F#, i had to pause for a second to remember that <> is !=. I think != is clearer:

let newList = state.UnpaidItems?

> List.filter (fun i -> i<>itemToRemove)

---

if oot were F#, it would have punctuation for 'match'

(this is probably where we use a generic punctuation for Urbit 'kelps' or similar, right?)

---

let's compare the F# and C# versions (the safer, F#-equivalent one at the bottom of the page, not the less safe one at the top) of https://fsharpforfunandprofit.com/posts/designing-for-correctness/ , and then write a pseudocode Oot version.

OK so the C# version is much longer, so we'll mostly be looking at why that is.

The biggest problem with the C# version, however, is that it requires something clever in order to do an analog of F#'s typechecked 'match' on discriminated unions; the 'Do' function.

Incidentally, having to have two versions of Do (i assume this was required for some reason), one which returns stuff and one which doesn't, shows that the real difference between functions and procedures should be that functions are pure and procedures may be impure (side-effects or nondeterminism; eg I/O), not that functions returns stuff and procedures don't.

OK, starting from the type declarations in the F# version.

It doesn't quite compare to the top of the C# version, b/c in C# the method bodies are defined with the types (as i like). Also, the author's putting in more documentation into te C# version, like the / <summary> lines. Also, the type signatures are explicit in C# (which i don't count as a huge loss b/c imo many of them should be explicit in F# too by the time the program is done, although i do prefer optional static typing).

The C# version also requires 'public' everywhere also a few more access modifiers elsewhere (eg 'get; private set'). I think 'public' should be the default. C# also requires the keyword 'return' rather than returning the last line of a fn which imo is better. C# also seems to require semicolons. C# also seems to require a verbose list creation: 'new[] {item}' instead of '[item]'. C# also seems to require explicit 'new's all over.

Afaict the implementation of the first C# method could have been a oneliner:

return FromState?(new ActiveState?(new[] {item}));

compare to:

Cart.Active {UnpaidItems?=[item]}

the length of the C# is caused by the things i said above ('return' keyword, explicit 'new'; longer list construction) but also by the need to call FromState?, which is part of the author's discriminated union machinery (the part that avoids needing reflection by creating a 'tag' value for tagging the discriminated union).

The same stuff explains the rest of the state classes (also note that the author puts opening and closing braces on their own line).

Then there are a few pages in the C# version under 'Execute of shopping cart proper'. These are the author's custom implementation of a typechecked discriminated union in C#. This wouldn't be needed in a language with discriminated unions (like F#, or Oot).

Then we come to the ShoppingCartExtension?, which correponds to the F# 'cart level helper methods'. Whereas F# just lets us define new methods on the relevant types, in C# this stuff goes into a static 'extension' with 'extension methods', see [8]. IMO these 'extension methods' seem like a verbose (and conceptually confusing) way to do what F# and Haskell do more naturally without any need for a static class or special 'extension method' syntax, although i think it does allow them to 'chain' or 'thread' methods (ie to write g(f(x)) as x.f.g). Also 'Console.WriteLine?' is more verbose than 'printfn'. Aside from this, and from the points mentioned previously (access modifiers, {}s on their own line, etc), this part is pretty much the same as the F#.

The C# part ends with a test, which isn't even in the F#, so we can neglect it.

In summary, a big thing is that F# has discriminated unions with 'match' and C# doesn't, and other than that there's just a bunch of small stuff that we've already anticipated for Oot; plus the author chose to be more verbose and to document (and test) more in the C# version.

To summarize:

---

so here's a sorta-pseudo-Oot version:

type CartItem? = strT ;; placeholder for a more complicated type

type Cart {

Empty {
    Add item := Cart.Active {UnpaidItems/[item]}}
  | Active {
    Add item := //= {UnpaidItems :.+ item}
     ;; let's break that down. //= is centis.
     ;; //= with nothing on the left is short for 'this //=', that is,
     ;; return a new version of the current object after applying changes.
     ;; the list of changes are in the braces; here there's only one change there.
     ;; each change is a pair separated by ':'
     ;; but the : can be postfix-modified like x =+ 1 for x = x + 1;
     ;;   in this case we have :.+, so this is short for
     ;;   UnpaidItems : UnpaidItems .+ item
     ;; and .+ is 'append'
     ;; so in all, this is 'return a new version of the current object, with UnpaidItems replaced by UnpaidItems with 'item' appended'; in other words, it's like
     ;;    this.UnpaidItems.append(item)
     ;;
     ;; hmm not terribly impressed by that, why not just actually have something more like
     ;;    this.UnpaidItems.append(item)
     ;; hmm...
     ;; ok see the next two sections, i'm going to start over and do it that way

}

---

regarding something like: ;; this.UnpaidItems?.append(item)

first off, we want 'this' to be implicit when in class methods, like in C++, rather than explicit, like in Python

now what to do about immutability here?

hmm... maybe have two forms of classes; if you say this.x = x + 1, and the class is in the non-mutable form, then maybe that method returns state, whatever_it_explicity_returns:

class1 ::= { &x = 0 method1 := x =+ 1 _ }

instance1Initial = class1 = {x : 0} instance1New = instance1.method1 instance1Initial.x == 0 instance1New.x == 1

&instance2Initial = new class1 {x : 0} &instance2.method1 == 1

notes: in this example,

--- ok here's an updated sorta-pseudo-Oot version:

type cartItem = strT ;; placeholder for a more complicated type

type cart { Disjunct , EMPTY { add item := {&this = cart.ACTIVE {unpaidItems:[item]}; _} , ACTIVE { unpaidItems : [cartItem] List

    add item := {unpaidItems =.+ item; _}
    
    remove item := {
      newList = unpaidItems -+ item;
      if newList
        unpaidItems = newList
      else
        &this = cart.EMPTY
      _}
      
    pay amount := {&this = cart.PAIDFOR {paidItems/unpaidItems; payment/amount}; _}
  , PAIDFOR {paidItems: [cartItem] List; amount : decimal}}

addItemToCart cart item := match cart { cart.EMPTY cart.add item cart.ACTIVE cart.add item cart.PAIDFOR {P "ERROR: The cart is paid for"; cart}}

removeItemFromCart cart item := match cart { cart.EMPTY {P "ERROR: The cart is empty"; cart} cart.ACTIVE cart.remove item cart.PAIDFOR {P "ERROR: The cart is paid for"; cart}}

displayCart cart item := match cart { cart.EMPTY {P "The cart is empty"; cart} cart.ACTIVE P "The cart contains %s unpaid items" % [cart.unpaidItems] cart.PAIDFOR P "The cart contains %s paid items. Amount paid: %f" % [cart.paidItems cart.payment]}

that's much more succinct than the F# version.

we want some syntax for Disjunct, though, and for 'match' (which is the same as 'switch' for us, right?)

---

of course what we'd really want for the shopping cart example is just to hold the cart state in a variable, and have the type system know about that variable

i guess all we really need for that is the 'promise' and 'demand' operators for type system assertions (ie the programmer promises that this assertion is true and the compiler should assume it's true, or the programmer demands that the compiler prove this assertion or refuse to compile if it cannot prove it). a demand at the beginning of a function is a precondition and a promise at the end of it is a postcondition.

here's a pseudo-Oot version like that:

type cart { state = EMPTY : EMPTY

ACTIVE PAIDFOR
  items = [] : [cartItem] List
  amount = 0 : decimal
  add item := {
    Assert Demand state in [EMPTY ACTIVE]
    unpaidItems =.+ [item]
    state = ACTIVE
    _}
  remove item := {
    Assert Demand state == ACTIVE
    unpaidItems -+ item;
    if newList
      state = ACTIVE
    else
      state = EMPTY
    _}
      
  pay amount := {
    Assert Demand state == ACTIVE
    payment = amount
    state = PAIDFOR
    _}

addItemToCart cart item := match cart.state { EMPTY cart.add item ACTIVE cart.add item PAIDFOR {P "ERROR: The cart is paid for"; cart}}

removeItemFromCart cart item := match cart.state { EMPTY {P "ERROR: The cart is empty"; cart} ACTIVE cart.remove item PAIDFOR {P "ERROR: The cart is paid for"; cart}}

displayCart cart item := match cart.state { EMPTY {P "The cart is empty"; cart} ACTIVE P "The cart contains %s unpaid items" % [cart.unpaidItems] PAIDFOR P "The cart contains %s paid items. Amount paid: %f" % [cart.paidItems cart.payment]}

that's actually a little longer than the previous but imo it's clearer. Note that we need syntax for:

and note that the typechecker needs to be able to reason about the values of enums (eg to detect that inside any particular branch of the 'match cart.state', the value of 'cart.state' is guaranteed to be a certain thing, which can be checked against the demanded assertions).

---

C# seems to do annotations with #, eg

"

  1. region ShoppingCart? State classes "

[9]

---

/= for Urbit centis (replace-with-changes), F# 'with'?

or mb = to fit with the pattern of for kelps? (was it : ?)

= with nothing on the left can be short for 'this ='

and what about when we want to do something like 'x =+ 1' within the centis? maybe :+, eg allow an arbitrary operator to be postfix-connected to the ':'?

but if we do that, and :: is 'append', that's kinda confusing. But maybe .+ is append.

---

how important is it/how strictly do we want to adhere to the idea that all of the syntax that affects parsing is in the core language (not added in the high-level language)?

---

we should probably have a 'hint sigil' to quickly indicate annotations that have no semantic effect on the result, but that optimize. Eg 'treat this as an array', 'do this in parallize this', 'this matrix is triangular', 'this integer variable will never be larger than 2^32 - 1', 'all arithmetic in this module can be approximate, and will never need more than 64-bits (eg you can use floating point doubles and long integers for everything rather than arbitrary-precision rational arithmetic)', etc.

---

city41 1 day ago [-]

But the problem is you really do need a "paredit" for your editor when developing in Clojure. It's a double whammy for beginners, and a huge one at that. Pretty much every other language ever is perfectly editable in everyone's editor as it exists today.

reply