Arc Forumnew | comments | leaders | submit | diiq's commentslogin
3 points by diiq 5053 days ago | link | parent | on: Why so proper, alist?

Eight's documentation is in a terrible state (in part because there are still many things about which I've yet to make up my mind), so blame me for any confusion.

Here's the gist: Fexprs, like macros, take expressions as arguments (duh). Those expressions are made up of symbols (duh). Because a fexpr is evaluated at runtime, those symbols may already be bound to values when the fexpr is called. Eight keeps track of which symbol is bound to which value at the place the expression originated (where the programmer wrote it) --- even if you cons expressions together, or chop them into pieces. This eliminates the need for (uniq), but still allows for anaphoric fexprs when symbol-leaking is desired.

When I wrote the docs on github, I called an expression plus any accompanying bindings a 'closure' (even though it wasn't a function). I also didn't know the word 'fexpr'. I've read a few dozen more old lisp papers since then, and hopefully on the next go-round my vocabulary will be much improved.

-----

1 point by evanrmurphy 5053 days ago | link

Some of your documentation is excellent, actually. This page, for example: https://github.com/diiq/eight/wiki/Better-Questions

-----


That's why I pointed you down to the appendix. A journey of a thousand miles begins with a single step --- but so does a journey down the street to the chemists.

Face that you need to dive in. Have you picked up a book yet? No more questions until you take the first step. Read the introduction and chapter one. Follow the instructions on how to install the language. Do the exercises. A carefully written and revised book will be a far better first step than an argumentative grab-bag of Arc hackers.

Then start asking questions --- because you'll have interesting ones.

-----

1 point by crownoflaurel 5460 days ago | link

Okay. I'll come back when I've found a book. It might take a little while....but I'm sure it will happen.

Thanks for all your help. I'm sure you'll regret it when I visit again, with an entire list of questions.

-----


I sat here for nearly half an hour, trying to compose a response --- because while I think that programming is one of the most gratifying skills I have, your question forced me once again to realize that I don't remember how I acquired it.

Learning to program is not at all like learning to juggle --- it's more like learning to read. When I learned to juggle, each new complication made practicing harder (5 balls vs 3); when I learned to read, the very act of reading had side-effects (new knowledge, the recognition of allusions) that made reading easier.

Because of that, it's impossible to say, "Oh, I learned to read last Wednesday." The process is gradual and it is governed by arcane laws of acceleration. The same thing happens with programming --- learning helps you learn faster! So don't worry about your first step --- even if you pick up the worst programming book ever written. So long as you persevere, the knowledge you gain will make picking the next book easier.

As long as I keep reading, I am still learning to read. As long as I keep programming, I am still learning to program. All you need is enough information to start --- and that can come from any book, or any tutorial!

For an ambitious start, try the books and tutorials recommended at the very bottom of this page: http://norvig.com/21-days.html

-----


Your first thought is correct.

    (map if '(t nil) '((pr "a") (pr "b")))
returns

    ((pr "b"))
because you've quoted the arguments. This is consistent; if you quote the arguments to if, you will get the same result (as you pointed out). Why would you expect a quoted argument to be evaluated?

The point is, just as you say, that you can pass functions which are normally considered 'special' and they will behave like any other function. Yeah, it's been done before (though you'll notice that picolisp comes up pretty often in your Google search; it's a canonical example) --- but that doesn't make it a bad idea; it's one less thing for the user to keep track of. I watched a person give up learning CL when they tried to pass a special form and discovered, after much frustration, that it was impossible.

-----

1 point by fallintothis 5466 days ago | link

This is consistent [...] Why would you expect a quoted argument to be evaluated?

I misspoke regarding consistency. What I meant was that the "useful" way (conditionally evaluating the branches) would be inconsistent, but the consistent way doesn't seem very useful (because the point of special forms is to behave specially).

Yeah, it's been done before [...] but that doesn't make it a bad idea

Certainly not! I didn't mean to imply first-class macros/special forms are a bad idea --- let alone just because they've been done before. I meant that the ideas are similar; since there's been more discussion about first-class macros, it might be useful to ponder those.

Even so, first-class special forms seem less promising than first-class macros. rntz already pointed out that functions can mimic PicoLisp's behavior for special forms, but you can even mimic the "useful" behavior for certain cases of if:

  arc> (def iffn (x y z) (if (eval x) (eval y) (eval z)))
  #<procedure: iffn>
  arc> (iffn '(prn "test") '(prn "then") '(prn "else"))
  test
  then
  "then"
  arc> (iffn '(do1 nil (prn "test")) '(prn "then") '(prn "else"))
  test
  else
  "else"
But that's still kind of a joke. And what about ones that don't even evaluate their arguments, like fn? If

  (car:map if '(t) '((pr "a")))
corresponds to

  (if 't '(pr "a"))
then does

  (car:map fn '(t) '((pr "a")))
correspond to

  (fn 't '(pr "a"))
which isn't even valid syntax? (At least by the way Arc's quote and fn interact; I understand PicoLisp's quote and fn are unified to just quote.)

I'm not against first-class special forms iff we can reason about them. It's just that reasonable interpretations look like solutions in search of the problem.

-----

1 point by rntz 5468 days ago | link

It's consistent, but it means that passing special forms to higher-order functions is essentially just a form of punnery - it lends you no more expressiveness. There is no way (unless I'm mistaken) to get (map if '(t nil) X), where X is some expression, to evaluate the first element of X (or its evaluation) but not the second. So I could as well just define a function:

    (def iffn a
      (iflet (c . a) a
        (iflet (x . a) a
          (if c x (apply iffn a))
          c)))
    
    (map iffn '(t nil) '(1 2) '(5 6))
    => (1 6)

-----

1 point by diiq 5484 days ago | link | parent | on: A Lisp Concise: 'while' in arc and eight

Sorry, I'm still not understanding your proposal. From what I can tell, your example assumes that we can fully type the outputs of functions as well as inputs; I don't see that as a particularly easy thing. Just like any duck-typed language, the return type can be dependent on runtime data. Take, for instance:

    (def pathologic ('a b)
        (if b 
           (fn (c 'd) (,a c ,d))
            a
           (fn ('c d) (,a ,c d))
            5))
And then:

    ((pathologic one-thing another) (list 3 4) '(a b c))
I can't type the function in this expression until the time of evaluation; the same is generally true of higher order functions.

In response to declare leak: I'm not concerned with optimization right now, more with the parsimony and elegance of the language.

In response to your interpreter &c., yes, the idea of first-class macros isn't new. Picolisp has been doing it since at least 2002. As far as I know, Eight is the only language that does so while maintaining lexical scope.

-----

1 point by rocketnia 5484 days ago | link

Right, things can get pretty hairy, and sometimes it won't be possible for the compiler to figure out whether a given expression will be needed unevaluated at runtime, in which case those expressions need to be preserved somewhere in the compiler's result, just in case. The preserved code could still be compiled, too, if the application's file size isn't as important as its speed, or if doing that would save from having to bundle an interpreter in there too. (Take a look at 'compose. If it's exposed in a compiled module, the code (g x) has to be preserved.)

But even for your 'pathologic example, a compiler might be able to determine that the result of the expression must be some result that one-thing could produce. That's because the 'a parameter, 'one-thing, is known at compile time to be a true value (since it's a symbol), and so both of the possible function types successfully accept two parameters and result in something 'a (one-thing) would result in when called.

This might be a bit tough for a compiler to do until it's really mature, but it does fit within the scope of a type system. In particular, the type of (if b x a y 5) might be expressible using an "if" type combinator, which I imagine can be manipulated and simplified as long as there's a way to ask a type whether its values are guaranteed to count as true or false.

More and more pathological examples could be built, though, and that's the halting problem for you.

I'm not concerned with optimization right now, more with the parsimony and elegance of the language.

As it should be. ^_^ Compiling is nothing if not one big optimization, though, so optimization was what I was talking about.

In a non-optimization defense of declare quick-syntax-transformer, it could also potentially make IDE integration slightly better, since the IDE would be able to determine more about the code being generated.

But yeah, it's not very elegant to have something in the language that programs appear to work just as well without, and it's not very parsimonious to have something in the language spec which is likely to mean nothing to lots of implementations. It's just something to keep in mind if there turns out to be a clever tweak to the language that'll help everybody.

-----

1 point by diiq 5483 days ago | link

Compiling is nothing if not one big optimization, though, so optimization was what I was talking about.

Ah. There's our confusion. I define compiling as the transformation of code in one language to code in another (lower level) language.

Sure, Eight can be optimized. I don't see how it can (by my definition) be compiled.

-----

1 point by rocketnia 5483 days ago | link

I was thinking about adding "(and translation)" to that sentence, but I thought that was the easy part. After all, you can just write an interpreter that compiles to the lower level language and package it up with every Eight program. It isn't an especially insightful translation, perhaps, but it's a start.

-----

1 point by diiq 5482 days ago | link

Yes, I concede --- you're right. I should be thinking in terms of partial evaluators, rather than full compilation.

With your permission, I'd like to cp this conversation to the Eight github-wiki, so we can stop cluttering up the Arc Forum ;)

-----

1 point by rocketnia 5481 days ago | link

Sure, that's fine by me.

Speaking of partial evaluators... I was thinking about this topic some more yesterday, in regards to my everything-is-a-multimethod language idea, and I came up with this. I imagine having

  (aif a b c d e)
translate something like this:

  (%apply %compile-to-apply '(aif a b c d e))
Here, %apply is a special value that the compiler knows will evaluate all its parameters, then apply the first parameter to the next. Meanwhile, %compile-to-apply actually breaks down the syntax passed to it so that there's a simpler %apply to execute. Since %apply, %compile-to-apply, and '(aif a b c d e) are constants, a partial evaluator can simplify this to:

  (%apply (%compiler-of aif) '(a b c d e))
Here, %compiler-of is yet another compiler-internal procedure that looks at the signature of its argument in order to produce another procedure that does the actual work of compiling a parameter list to a simpler %apply form. Since (%compiler-of aif) is usually a constant and doesn't usually have side effects, this can be evaluated some more at compile time:

  (%apply ??? a 'b '(c d e))  ; where ??? is an implementation of aif's body
Even if aif isn't a constant, as long as the compiler knows enough about what aif will be (its type), it might be able to reduce (%compiler-of aif) anyway.

Now is when (%apply ??? a 'b '(c d e)) would be inlined if possible, in the hopes of compiling '(c d e). With all of Eight's 'leak, 'comma, and 'asterix, I think that might take a few more techniques than I've thought about. My language's approach to afn would probably work a bit more like this:

  (%apply (%compiler-of aif) '(a b c d e))
  (%apply ??? (list (fn () a)
                    (fn (it) b)
                    (fn () c)
                    (fn (it) d)
                    (fn () e)))
...and therefore avoid having to working with syntax in the method body. Then again, this language is probably going to be considerably more mind-melting to try to read. Here's a generous mockup of the kind of thing I have in mind for the base language (which I'm calling Blade):

  (def (aif (-repeat branch (condition) (consequence it))
            (-optional (else) (fn ())))
    (with-first (finding-on branch
                  (= it (condition)))
      (consequence it)
      else: (else)))
I'm not sure whether you actually wanted to know any of this stuff, heh, but I hope it helps. ^_^

-----

1 point by rocketnia 5484 days ago | link

Your example brings up an interesting side point: 'fn has a special interaction with , and *? Otherwise, ,d and ,c are referring to top-level things. Sounds like 'let probably has the same interaction, since if you could do (let it it ,then), you wouldn't need ,(leak 'it then). Is this something velcros can do too? Something like (def my-let ('var val ... ''body) ...)? ^_^ I'm probably going to check out the Eight interpreter myself at some point, but I figured I'd ask.

-----

1 point by diiq 5483 days ago | link

, and * are just read-macros. ,a -> (comma a) a -> (asterix a)

The lambda-list of fn* is: ('lambda-list ... 'body) so ,c and ,d are not evaluated at the time of function creation.

Let is defined in terms of fn.

I'm not sure what behavior you're hoping for from that double quote.

-----

1 point by rocketnia 5483 days ago | link

Ah, the fact that they're shorthand is what I expected, I was under the impression that even so, they were expanded before the velcro was finally called. Otherwise

  (aif *elses)
seems like it wouldn't work. Time for me to go get the interpreter.

-----

1 point by diiq 5484 days ago | link | parent | on: A Lisp Concise: 'while' in arc and eight

I haven't written one yet. I'm concentrating on discovering what new idioms eight offers, and building useful base set of operators.

The difficulty in writing such an interpreter is also dependent on whether you let the base interpreter handle the closure algebra, or if you rewrite closure-handling in the meta-interpreter.

But Eight is available to download --- feel free to write one yourself ;)

-----

1 point by diiq 5485 days ago | link | parent | on: A Lisp Concise: 'while' in arc and eight

Yes --- that's the best part! For instance, the example aif I gave uses a mixed quoted/unquoted lambda list.

For an example of the exact form you gave:

    (def f ('a b) (print a) (print b))
    (f (list 1 2) (list 3 4))
Outputs:

    (list 1 2) 
    (3 4)
Any combination of quoted and unquoted arguments is valid. Those that are quoted act like macro arguments, and those that are unquoted act like functional arguments.

-----

1 point by diiq 5487 days ago | link | parent | on: A Lisp Concise: 'while' in arc and eight

Thank you for spending such a non-trivial amount of time explaining this! You'll have to forgive me if I take a day or two to absorb your logic before I can properly respond.

-----

1 point by diiq 5487 days ago | link | parent | on: A Lisp Concise: 'while' in arc and eight

Right, so the major difference between a traditional closure and a closure in Eight is that a closure in Eight still looks and acts just like a list. I can do this:

     (def foo ('bar)
        (print (car bar)))

     (foo (+ 2 3))
And what would be printed is:

+

Which is not useful, but it's how you'd expect a macro to work. But in Arc, once you've wrapped something in (fn) to make a closure, it becomes inaccessible; I can't do:

    (pr (car (fn () (+ 2 3))))
EDIT: I didn't answer the question.

    (set! a 10)
    (while (> a 3) (print "hello") (set! a (- a 1)))
Which outputs:

hellohellohellohellohellohellohello()

[Warning to those you want to try this in the Eight interpreter, - and + are not implemented yet, use 'minus' and 'plus']

-----

1 point by diiq 5488 days ago | link | parent | on: A Lisp Concise: 'while' in arc and eight

Thank you.

I'm still waffling on 'Eight' or 'eight' --- in my notes I write it '8', which is even less clear ;)

-----

1 point by rocketnia 5488 days ago | link

Whatever you call the language, there should be an editor scriptable in it called ACHT: Another Curvy Hacking Tool. XD

-----

2 points by diiq 5487 days ago | link

Hah! Then code snippets can be 'Pieces of Eight' and libraries must be Reales.

And the qualitative feeling of programming in this language? Fullness. 'Cause you just Eight.

-----

More