Skip to content

Instantly share code, notes, and snippets.

@diiq
Created July 17, 2011 17:26
Show Gist options
  • Save diiq/1087830 to your computer and use it in GitHub Desktop.
Save diiq/1087830 to your computer and use it in GitHub Desktop.
Test for psybas conversation

Verifying my understanding of psychotic bastard:

set x: fn (b) : cons : 't (cons 'b ())

is a valid way to say

(set x (fn (b) (cons (' t) (cons (' b) ()))))

Correct?

Currently true code samples?
-----
set x: fn (b): cons 't (cons 'b ()) ===> (set x (fn (b) (cons (' t) (cons (' b) ()))))
set x: fn (b): cons 't: cons 'b () ===> (set x (fn (b) (cons (' t) (cons (' b) ()))))
-----
set def:
'('name ... 'code
leak: really name
set (really name) code)
and
set def:
'(('name ... 'code):
leak: really name
set (really name) code)
(set def
(' (((' name) ... (' code))
(leak (really name))
(set (really name) code))))
[MAYBE this, too:
set def:
'('name ... 'code
leak: really name
set (really name) code)]
-----
@AdamHeck
Copy link

But while the function composition examples would evaluate to the same answer, they don't convert to the same s-expression as the others. print . car . cdr would get converted to something like (fn (x) (print ((fn (y) (car (cdr y))) x))

@diiq
Copy link
Author

diiq commented Jul 17, 2011

Yeah, that answers my next question, which was gonna be about composition. I had been using the colon for that, too (since it works as a meta-compose):

print : car : cdr:
    (\b: (\a: a :: b :: ()) 't) 'b

You have replaced all my toenail clippings with flyspecks.

I will have more question-plaints soon.

@AdamHeck
Copy link

Stylistic nit: I'd prefer to write that as

print: car: cdr:
    (\b: (\a: a :: b :: ()) 't) 'b

to emphasize both that colon eats the rest of the line and that it's not actually an operator. I see the s-expression groupings better with the colon like this than with spaces on either side.

But I agree, one of the big wins of having the colon is being able to write that. I'd really only use composition if I needed to hand print . car . cdr to something else.

@diiq
Copy link
Author

diiq commented Jul 17, 2011

Well, we may have to discuss that more, because I am taking a disliking to the lines that look like

foo: barbaz: crunk: see

it's almost purely visual thing, I suppose --- but also because the colons there have considerable effect --- to me, that emphazises that they work until the end of the line ----

But, talking myself back out of it, you're right. : is NOT an operator, and should be used like one:

reduce |:| a-list-I-made x

is meaningless pseudo-balderdash-aphonics.

reduce |.| a-list-I-made x

means something. I guess you twisted my arm. Hard sell. Uncle.

Which brings up another point: have you decided how to demarcate the prefix-version of an infix operator? I'm using || 'cause I think I saw you use that once.

@AdamHeck
Copy link

No, I don't have a defixing syntax. Using punctuation limits your pool of operators. || is bad because we want || itself to be an operator. Thinking out loud, we could limit the range of valid symbols to not start with punctuation and when transforming infix to prefix, prefix the operator with something like op-. So you'd have

def op-. lhs rhs:
    \x: lhs: rhs x

And foo . bar becomes (op-. foo bar). Now you're not escaping infix symbols, you're using their proper name.

So how icky is limiting symbol space to you?

@diiq
Copy link
Author

diiq commented Jul 17, 2011

Well, so, pretty icky, but I'll hold my tongue for a few moments while I get the whole picture .

How would the opposite work --- infixing a normally-prefix operator? Are you still considering <<anglerfish>> ?

Do you suppose you'll infix an outfix more frequently, or will it usually be t'other way 'round?

@AdamHeck
Copy link

Yeah, I'd still do infixing that way, because that's still /right/. Though I'm contemplating just using <chevrons>.

In my Haskell code, I did both a fair amount, but probably more infixing than defixing.

Infixing gets used for combinators and tests and is a big win in being able to use short, clear names:

(edit: had to shennaniganize to get infix terms to not be treated as tags, even in <pre>)
(label "One" <above> label "Two") <beside> label "Three"
id <is-free-in> expr

Whereas defixing is just used to get a handle on a function:

lookup-with (op-== id . ident-id) scope

@diiq
Copy link
Author

diiq commented Jul 18, 2011

So the usual way to make a user-defined infix operator is to make a function and use <<<anglerons>>> on application, and the whole op-== hack is reserved for a select few, ultra-short operators, like arithmetic?

@AdamHeck
Copy link

Still thinking about this, but how about: if the pb-identifier is entirely punctuation, it is treated as infix, and it's s-identifer is op-#$%@. If the identifier starts with a character, it's a normal identifer and needs <angloids> to make it infix.

That seperation also leaves room for a rule for operators that have no space after them to be unary operators. I'd want to limit this to either single character operators for readability issues, either in the parser or just in coding style.

Examples:

-foo        ===>  (- foo)        # by way of special unary op rule
- foo       ===>  (- foo)        # operator in functional position
bar * -foo  ===> (* bar (- foo)) # by unary op rule
bar * - foo ===> (throw 'parse-error) # tries to do (bar *) - foo
!bar || foo ===> (|| (! bar) foo)

Something to think about, anyway.

@diiq
Copy link
Author

diiq commented Jul 18, 2011

Hmm. I worry about the operators that are always unary:', ,, &c. That proposal doesn't break them, but it does make them wonkus half-breeds.

You like || better than or?

@AdamHeck
Copy link

Ooh, I forgot about ' and ,! This would make - and ! work just like those! So I'm missing how this makes them wonkus half-breeds. Yeah, you'd be able to write foo ' bar and it'd come out translated as (' foo bar) and that's an error in most lisps I think, but in sytax land, that's just as okay as (quote foo bar). I think I'm comfortable with "don't use functions in infix position that can't support it" alongside "don't pass too many arguments to functions" in the list of things to trust the programmer with.

I do like || better than <or>.

@diiq
Copy link
Author

diiq commented Jul 19, 2011

grumble grumble. OK. I'll take it for the moment. Would you accept a longer prefix than op-? Not that I can come up with anything acceptable at the moment, I'm just trying to mitigate the effect on the namespace.

But you've got me more or less convinced.

Thinking about edge cases:

(hello
there
neighbor)

vs

(hello
 there
 neighbor)

vs

(hello
    there
    neighbor)

vs

(hello \
    there neighbor)

Which are acceptable?

(Also, are the continue-this-line \ things still around?)

@AdamHeck
Copy link

I'm not sold on the op-, so yeah, that can change.

The rule I think makes most sense is that when parens are opened, the next line can be anywhere from column 0 to the same column as start of the first token after the open paren. The h in hello is in column 1 in your examples, so only number three is an issue (line two has indentation without a : or \. And yeah, I think backslash has to stay.

The phrasing of that using the law of least special cases might go thus: a line starting at column x ends any indentation block that started at column x or greater. Since inside fresh parens there's no indentation block to end, no indentation block is ended by starting the next line further left. The indentation level of a line inside parens is decided by the first character in the first token after the open paren. The indentation of multicolon lines is still the first character in the literal line. (That las sentence just makes

print: car: cdr:
    \f: f pineapple pear

work like it looks like it should (the print, car, and cdr blocks all start at column zero)).

This phrasing would allow the following shennanigans:

foo:
    (hello
   there
  neighbor
 )

for (foo (hello there neighbor)). It'd even allow this:

foo:
    hello
   there
  neighbor

for (foo hello there neighbor)

Shennanigans to be frowned on, sure, but parsers (lispy and wetware both) would have fewer special cases to apply.

@diiq
Copy link
Author

diiq commented Jul 19, 2011

Is \ still a special form, or does
\x: do (things to) x
only work with one argument? Can I do
\x y z: do: x: (to y) <with> z
when I mean
(\ (x y z) (do (x (with-op (to y) z))))

[aside: it was easier to write the psychotic bastard version than the s-expression just now, and easier to read, too. I am excited for the final spec so that I can use it. I think you're actually managing the miracle]

@AdamHeck
Copy link

Yes, that's how I'd like \. (side note: magic parens on infix operators means you can do \x y z: do: x: to y <with> z). The thing with \ is it needs def-colon not only to work with multiple args, but to span multiple lines in an unsuprising manner. Without special syntax,

\x y:
    print x
    y

becomes

(\ x y (print x) y)

But with def-colon in the language, you should be able to define \ in terms of fn.

def-colon \ (args-before-colon args-after-colon):
    fn args-before-colon ,@args-after-colon

or similar. def-colon would add \ to a table of keywords with the macro-looking thingy as the transform to apply for that keyword. Then when a colon is read, the first token is checked against the table, and if a match is found, that handler is applied instead of the default rule. (I'm not sure how block-chaining works yet for if/else, try/catch/finally, etc., but it'd tie in here)

This has several downsides:

  • We can have a keyword explosion to the point that the ease of reading disappears again.
  • pb-read has to eval as it reads each expression in order to update the table.

This has upsides:

  • Not limited to special cases thought of while writing the pb parser
  • Can query the syntax table in a repl.

And if you don't know what the first token in an expression does anyway, you're screwed.

Admittedly, you could have a more powerful and more general def-syntax, but that'd have more design decisions than the rest of pb combined. I guess def-colon is a bit of a hack to cover for not knowing what def-syntax would look like. If you're building a parser using combinators, I suppose you could allow binding parsers to keywords and let your language get hella crazy:

def-syntax basic:
    parse-and-compile-basic

basic:
    10 PRINT "Hello, world!"
    20 GOTO 10

And now you're absolutely destroying readability for the sake of "Whee!"

Think think think...

@diiq
Copy link
Author

diiq commented Jul 19, 2011

So, what's your current list of use cases for def-colon? Lambda seems like a very minor thing to me; it's not objectionable --- in fact, I think I prefer --- to say

\ (x y z): do x: (to y) <with> z

because it makes it very clear that \ (x y z) is one unit. The concern I have is for things like this:

quote:
    if (x == 5):
        do some stuff
    else:
        do other stuff

That looks like illegal syntax; even if I understand the semantics of if/else, the syntax makes it look like two separate blocks, and the idea of if and else sounds like two separate blocks --- that's why you want to write it that way --- but it's NOT. And it CAN'T be. If is a function; else is not.

I'm pretty convinced at the moment that def-colon is a misstep, unless you've got other compelling cases. How do you feel about this:

if test:
    then: stuff
    else: stuff

It can do one line:

if test: then stuff

if test (then stuff) (else stuff)   # I admit this one isn't perfect, but one-line-two-paths is bad style anyway.

And also long chains:

if test:
    then: 
        stuff 
    elif test2:
        other stuff
    else:
        borges

It's not a special case. It makes a block look like a block.

[re: (to y) --- I understand, but I prefer it in parentheses for the same reason I parenthesize arithmetic. If I am hesitant for even a moment about application order, why not make it explicit?]

@diiq
Copy link
Author

diiq commented Jul 19, 2011

I am thinking that maybe some of the reason we don't see eye to eye here is that I am using an language where both \and if are first-class functions. If if/else could not be passed around and manipulated just like car, it might be a little more OK to make it special --- though I would still argue that it's confusing for macro-writing.

On another note, I wonder if I can trick set (whatever it'll be called) into letting a person do something ridiculous like (set * <<op-*>>) 'cause that'd be pretty boss.

@AdamHeck
Copy link

I

For an if syntax using only normal magic colon, i'd go with (if pred then else), alias then and else to begin/do/progn, which then allows all of the following forms:

if (x == 5):
    oneline-thing x
  else:
    one-or-many-line-block
    blah blah

if (x == 6):
  then:
    many line-thing
    full  lines
  else:
    one  many: line-thing.
    blah blah

if (x == 7):
  then: stuff
  else: antistuff

if (x == 8):
  stuff
  antistuff

if (x == 9) (stuff) (antistuff)

Biggest downside I see is adding another line to the x == 5 case's if block without adding a then.

II

I agree, def-colon is wrong. It feels like a hack.

My motivation is that PB started with the question "What should code look like?", and your if blocks and mine above all make me cringe (well, maybe not x == 9 above). then is unecessary when you have a colon after the condtion. Yeah, if is a function, and yeah, if can be passed around. In my head, if/else is a syntactic transform, such that

if (x == 10):
    foo bar
    baz 2
else:
    bar foo

translates directly to

(if (== x 10) (begin (foo bar) (baz 2)) (bar foo))

For me the thing with if is that in s-expression land, it's a hack to do anything other than a 3 argument if, i.e. (if predicate do-this else-that). Implicit progn on the else case is a hack and so is arc style if. But if you do that, you get code that looks like this:

(if (eq x 11)
    (bam bam)
  (pebbles))

or in PB:

if (x == 11):
    bam bam
  pebbles

This (along with unecessary parens and prefix operators and /their/ extra parens) was one of the three big pet peeves I had with reading and writing s-expressions: the else keyword massively improves the scannability of if statements.

So I'm pretty convinced there needs to be an else keyword and there needs to not be a then keyword. There's no way this line of reasoning only applies to if, so that leaves me at syntax-rules.

(Haskell before Haskell2010 required elses to be indented relative to ifs in do notation (where indentation matters). There was much rejoicing when they allowed same line ifs.)

III

cond gets ugly if you expect magic parens. The following two are equivalent:

cond:
    foo bar:
        baz

cond:
    foo:
        bar
        baz

So you have to make sure to do:

cond:
    (foo bar):
        baz

This isn't fixed by def-colon either. You need stronger sauce or something crazy, like a third line terminator that does another kind of parenthesizing for that kind of thing, like

foo bar ->
    barcalva

for

((foo bar) barclava)

Or you can just be careful where you stick your parens.

IV

Re: lambda. I had been playing with \x y: as an alternate syntax for fn (x y), but having both in the language. If you only have one or the other, then yes, it has to be \ (x y):, because you need to be able to go \ args:. You've also hit on something I missed, that \x y: doesn't play right with a generic unary operator rule and that a space is necessary between \ and the first argument.

V

My use case for syntax rules beyond if is very nebulous. My original plan was to write up PB with magic colon, infix operators, and just enought def-colon to get if/else working, then see what I needed as I coded (I had expectations of wanting foo.bar being (foo 'bar) for structures/namespaces/functions designed to look as such and foo[bar] being (foo bar) for hash/array accesses and things designed to look that way.

If I haven't convinced you about if/else, I'd recommend just doing magic-colon and infix operators and doing the same test I've never gotten around to: trying it out to see how it feels.

@diiq
Copy link
Author

diiq commented Jul 21, 2011

I'll write more tomorrow, but the most ridiculous solution to IV is that \ is an unary operator:

def \ ('args):
    fn (... 'code):
        fn (just args) (just code) 

Which is oystery notation for a function that takes an arg list, and returns a function that takes a code block and returns a function.

You do need the parens there but I really think they're OK --- an arg list is a list, and can be denoted suchly.

But you're right, I'm being an armchair-designer again. I have to replace the glib parser anyway, might as well go after PB; it seems like the mostly-magic colon, line-continuing , and logical-line definitions are pretty much settled, so I can start from there.

(Hmmm. Does line-continuing-\ conflict with lambda-\ ?)

@diiq
Copy link
Author

diiq commented Jul 21, 2011

Ohmygoodness, I woke up with the most terrifying idea in my head (note: oif is a built-in if without an else)

((\():
    leak if
    leak else
    set! do-else 't
    set! if: \(test ... 'code): 
        set! do-else 't
        oif test: 
            *code
            set! do-else ()
    set! else: \(... 'code):
        oif do-else *code))

Make if and else actually be two separate functions that share a scope! This blows up when you use call/cc, but it makes me wonder if the right direction is manipulating the primitives to fit the syntax, rather than t'other way 'round.

@diiq
Copy link
Author

diiq commented Jul 21, 2011

More alarming solutions: a c-preprocessor style string-replace:

(set-parse "*" "<<binary-*>>")
(set-parse "else" " else")

@diiq
Copy link
Author

diiq commented Jul 25, 2011

OK, how far is the reach of an infix operator? Like, what does this translate to:

a: b: c (d) <<operator>> (e: f) g h: i

My first instinct is to call it at the colon and the end of line:

(a (b (operator (c (d)) ((e (f)) g h (i))))

Is that what you're calling the 'logical' line? What about this?

a: b <> c d:
    e f g
    i j k

@diiq
Copy link
Author

diiq commented Jul 26, 2011

I'm allowing

a b <<c d e>> f g

to mean

((c d e) (a b) (f g)

am I doing that right?

EDIT: This is called psychotic bastard not because it is a crazy idea but because attempting to parse it makes me want to kill.

@diiq
Copy link
Author

diiq commented Jul 26, 2011

Shit, and what about (damnit, pre can't handle << and >>. Substituted [[ and ]])

a b [[operator]] d e:
    f g

is that

(operator (a b) (d e (f g)))?

@diiq
Copy link
Author

diiq commented Jul 28, 2011

Also parens are hard, too

abbot and costello (are:
    going to the
    store to buy) some bread

(abbot and costello (are (going to the) (store to buy)) some bread)?
(abbot and costello (are (going to the) (store to buy)) (some bread))?

Or is it illegal? If so, what about:

(\x: 
    do some things
    mostly to x I guess) 5

EDIT: I guess it HAS to be (abbot and costello (are (going to the) (store to buy)) some bread), or else everything falls apart.

@AdamHeck
Copy link

Operators really need to be held in by colons on both sides, because the letting them run to the end of the line is wrong in block form. (I have this wrong in my parser right now, and didn't realize it was wrong until I tried to explain how it works. Essentially, an infix operator that comes before a colon and isn't in a paren shouldn't extend past the colon:

embrace [[compose]] extinguish:
    lotus
    novell

# Requires def-syntax
if a > b:
    print blammo

I'm not 100% sold on this; I've written a few snippets and know that there are some cases where you want to have colon syntax fire first:

header  body:
    article1
    article2

but I am about 98% sold.

Your Abbot and Costello edit is exactly right. The other function in that post is legal for me, but I thought not for you. Did you forget parens around the argument list, or have you come around on that one?

I had not considered << and >> wrapping anything other than a single identifier. Interesting.

I don't know if we got this wrong anywhere else, but in your example

a: b: c (d) <> (e: f) g h: i

there is a colon that doesn't do anything (after e). The one after h only has the effect of limiting the scope of the operator. Both f and i don't get parens from the colons because they're already a single expression. IOW,

e: f

and

e:
    f

and

(e f)

are all the same.

Aside: Seriously, how did you people deal with closing parens until the right one flashes for decades? I wonder this every time I translate something back to s-expressions.

@AdamHeck
Copy link

I knew I had a better example that's pro colon-fires-first:
rest-of-line <- many: indented-past block-indent >> basic-expr

Here <- is roughly let. The block this comes from is monadic, but that's not important right now, the gist is the let/set/define operator should have the lowest precedence (colon should fire first).

(Also not important is that the line is ugly.)

Hmm.

@diiq
Copy link
Author

diiq commented Jul 28, 2011

Easy answers first:

I came around on the arg-list-parens for a single argument; I still think you're crazy for wanting \x y z: x + y + z.

Re (e: f): Yes, there are places where the colon is unnecessary. I've found it useful in test cases to have those --- sometimes for readability and sometimes to try and fool the parser.

Re: aside: I dunno --- I was born at the right time, and always had flashy whatsits (though I hear they have pills for that now). To your credit, I had to write some s-expressions to explain something on G+, and it just about made me throw the keyboard ('cept it was a laptop). Psychotic Bastard is bizarre (and seems more so the more I try and explain it to myself) but it's pleasant to write.

(Oh god, I just realized I've been ignoring infix precedence. Poop. I thought I had this worked out right finally.)

You're going to give : a precedence between operators??? It's not an operator! It's a delimiter! I... ugh... arrgh. We gon' have words, friend.

@diiq
Copy link
Author

diiq commented Aug 2, 2011

Every possible method for doing if/else is completely and utterly wrong and I despise each of them, individually, for the hateful snowflakes they truly are.

@AdamHeck
Copy link

AdamHeck commented Aug 3, 2011

I had a code sketch for if/else that was a pretty naive 4 lines long. I put in a working but hideous version tonight via def-keyword. It weighed in at around 50 lines, two of which were

                             ; OMG HACKS

and

                             ; END OMG

I'm taking this as a sign that I haven't got def-keyword right yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment