Arc Forumnew | comments | leaders | submit | mpr's commentslogin

Looking forward to hearing more about the development of the macro system!

-----

3 points by mpr 196 days ago | link | parent | on: How many people still lurk here?

Hello! I come around every few weeks to see what's up.

-----

3 points by mpr 423 days ago | link | parent | on: A keyword param implementation

Thanks for pointing that out. I think I fixed both those problems here.

    (mac iter (var list . body)
      (let i (uniq)
        `(if (is nil ,list)
           nil
           (withs (els ,list
                   ,i 0
                   ,var (els ,i)
                   next (fn () (++ ,i)))
             (while (< ,i (len els))
               (= ,var (els ,i))
               ,@body
               (next))))))

-----

3 points by Oscar-Belletti 422 days ago | link

Now the next function returns the next index and not the next element. iter still evaluates list two times and els can cause variable capture.

    arc> (mac iter (var list . body)
          (let i (uniq)
            `(if (is nil ,list)
               nil
               (withs (els ,list
                       ,i 0
                       ,var (els ,i)
                       next (fn () (++ ,i)))
                 (while (< ,i (len els))
                   (= ,var (els ,i))
                   ,@body
                   (next))))))
    #(tagged mac #<procedure: iter>)
    arc> (iter a '(1 2 3) (prn a))
    1
    2
    3
    nil
    arc> (iter els '(1 2 3) (prn els))
    Error: "car: contract violation\n  expected: pair?\n  given: 1"
    arc> (iter a '(a b c d) (prn a ", " (next)))
    a, 1
    c, 3
    nil
This should fix the problems:

    (mac iter (var lst . body)
        (w/uniq (i elst)
          `(let ,elst ,lst
            (if (is ,elst nil)
              nil
              (withs (,i 0
                      ,var (,elst ,i)
                      next (fn ()
                             (++ ,i)
                             (= ,var (,elst ,i))))
                (while (< ,i (len ,elst))
                  (= ,var (,elst ,i))
                  ,@body
                  (++ ,i)))))))

-----

3 points by mpr 422 days ago | link

Thanks again!

-----

2 points by Oscar-Belletti 421 days ago | link

My pleasure

-----

2 points by mpr 465 days ago | link | parent | on: A keyword param implementation

So I'm not sure if the argparse function is completely obsoleted by keyword args, but it certainly is in the use case above. With the keyword arguments I've written, you can pass arbitrary code as the default value. So I've rewritten the scrape function above like this

    (ndef scrape (addr --keys (outfile ((tokens addr #\/) -1)) use-ssl)
      (prn outfile)
      (prn use-ssl))
Yes, there is some funny variable capture business going on here. But I think it's interesting to think about anyway :)

-----

3 points by mpr 466 days ago | link | parent | on: Labels macro in arc

I think the double assignment of the names given to the labels macro is needed in the case that the functions call each other. Take this example:

    (labels ((even (n)
               (if (is n 0)
                 'even
                 (odd (- n 1))))
             (odd (n)
               (if (is n 0)
                 'odd
                 (even (- n 1)))))
      (even 5))
The macroexpansion of this labels call looks like this

    ((fn (g3947 g3948)
      (with (even (fn a (apply g3947 a))
             odd  (fn a (apply g3948 a)))
        (with (even (fn (n) (if (is n 0) (quote even) (odd (- n 1))))
               odd  (fn (n) (if (is n 0) (quote odd) (even (- n 1)))))
          (do
            (= g3947 even)
            (= g3948 odd))
          (even 5))))
     (fn nil nil) (fn nil nil))
If I add a prn to see what we get, we can run the code and see that it works as expected.

    (prn ((fn (g3947 g3948)
            (with (even (fn a (apply g3947 a))
                   odd  (fn a (apply g3948 a)))
              (with (even (fn (n) (if (is n 0) (quote even) (odd (- n 1))))
                     odd  (fn (n) (if (is n 0) (quote odd) (even (- n 1)))))
                (do
                  (= g3947 even)
                  (= g3948 odd))
                (even 5))))
          (fn nil nil) (fn nil nil)))

    ;; odd
Now I will remove the outer with and run the same code, which returns nil.

    (prn ((fn (g3947 g3948)
            (with (even (fn (n) (if (is n 0) (quote even) (odd (- n 1))))
                   odd  (fn (n) (if (is n 0) (quote odd) (even (- n 1)))))
              (do
                (= g3947 even)
                (= g3948 odd))
              (even 5)))
          (fn nil nil) (fn nil nil)))

    ;; nil
I can't explain this in greater detail; I haven't traced the full execution. But given the evidence I believe mutual recursion makes double assignment necessary.

-----

4 points by akkartik 466 days ago | link

You could macroexpand to this, though:

  (with (even nil odd nil)
    (= even (fn (n)
              (if (is n 0)
                'even
                (odd (- n 1))))
       odd  (fn (n)
              (if (is n 0)
                'odd
                (even (- n 1)))))
    (even 5))

-----

3 points by mpr 465 days ago | link

Yes, this simpler macroexpansion is all that's needed, apparently. When I first ported the code above I didn't have an appreciation for what labels needed to do; I just ported it. Thanks for making me think about it a little harder. Here is an implementation that will macroexpand to only one with. I've included a sample macroexpansion, as well as the results of running two functions. One of them, (collatz-seq), uses only simple recursion. The other, (parity), uses mutual recursion.

    (mac labels (fns . forms)
      (with (fnames (map car fns)
             fbodies (map (fn (f) `(fn ,@(cdr f))) fns))
        `(with ,(mappend (fn (name) `(,name nil)) fnames)
           (= ,@(mappend (fn (f) `(,(car f) ,@(cdr f))) 
                         (zip fnames fbodies)))
           ,@forms)))

    (def collatz-seq (n)
      (labels ((collatz (n)
                 (if (even n)
                   (/ n 2)
                   (+ (* n 3) 1)))
               (worker (n seq)
                 (if (is n 1)
                   (cons n seq)
                   (worker (collatz n) (cons n seq)))))
              (rev (worker n '()))))

    (def parity (n)
      (labels ((even (n)
                 (if (is n 0)
                   'even
                   (odd (- n 1))))
               (odd (n)
                 (if (is n 0)
                   'odd
                   (even (- n 1)))))
              (even n)))

    (prn (macex1 '(labels ((even (n)
                             (if (is n 0)
                               'even
                               (odd (- n 1))))
                           (odd (n)
                             (if (is n 0)
                               'odd
                               (even (- n 1)))))
                          (even n))))

    (prn (parity 17))
    (prn (parity 24))

    (prn (collatz-seq 21))

    ;; ---------------------------------------------

    ;; (with (even nil odd nil)
    ;;   (= even (fn (n)
    ;;             (if (is n 0)
    ;;               (quote even)
    ;;               (odd (- n 1))))
    ;;      odd (fn (n)
    ;;            (if (is n 0)
    ;;              (quote odd)
    ;;              (even (- n 1)))))
    ;;   (even n))
    ;; 
    ;; odd
    ;; even
    ;; (21 64 32 16 8 4 2 1)

-----

3 points by akkartik 465 days ago | link

That's very interesting analysis. I'm curious to see where you got the original Common Lisp version from. Is there some reason CL requires two withs?

-----

3 points by mpr 465 days ago | link

Got the CL version from here http://www.pipeline.com/~hbaker1/MetaCircular.html. Someone on ##lisp IRC linked me when I was asking about how to write labels. Since I'm curious too, I'll do a similar analysis on CL and post the results.

-----

3 points by mpr 466 days ago | link

Good point. Then I don't see any need for the double with.

-----

4 points by akkartik 466 days ago | link

By the way, I was sad that you stopped submitting Tcl links after the one :) Don't be discouraged that there was no discussion on it. Sometimes I can't think of anything to say at the moment, but I still enjoy the link.

-----

4 points by mpr 465 days ago | link

Hah! Yes, I was a little discouraged. Thanks for letting me know! I have some stuff in mind, expect to see more soon :)

-----

3 points by akkartik 465 days ago | link

Since I'm on a roll here with my speculatin': also don't correlate how a post is received with how long it takes to get comments. I just didn't see your Y-combinator post for the 8 days before I responded. Which is a complete outlier for me on this Forum and hopefully not a harbinger of things to come.. :/

-----

4 points by mpr 481 days ago | link | parent | on: Implicit importing

I like this idea a lot. I don't like that I've gotten used to the inconvenience of importing math and re in Python. It shouldn't be that hard to use trig functions!

I briefly thought about a static analyzer that would do any necessary imports when the code is first loaded into the interpreter, but there seem too many ways that could get complicated. I agree loading a library on first use is a good way to go.

-----

3 points by digitalis_ 481 days ago | link

Not even a whole library -- just the specific functions that you're using. (Think of all those times you've done `import math` just for the bloody sqrt!)

-----

3 points by mpr 481 days ago | link | parent | on: Y combinator in arc

Considering it for about a minute, I say bracket functions of multiple args is better to have than nested bracket functions. The multiple args case applies more often I would guess. And the nested case is hard to parse, so taking away the power of the multi arg case to have the nested case doesn't seem worth it.

Edit: that is, hard for humans to parse

-----

2 points by zck 481 days ago | link

I agree; the nested case is a bit difficult for humans to parse.

I wonder if there's a way to make (fn (x) ...) even more lightweight.

What if, to do the same thing, we had a new anonymous function syntax?

    {x ...}
It would work somewhat like this:

    arc> (let my-fn {arg (string "I got " arg)}
              (my-fn "this thing"))
    "I got this thing"
This is somewhat analogous to "let"

    (let x ...)
It could also allow for multiple arguments, like this:

    {(x y} ...)
Allowing for multiple arguments prevents destructuring-bind style function calls, though, which is a downside. I guess you could use other kinds of brackets for that?

    {{x y} ...};; this would be non-destructuring-bind
    {(x y) ...};; this is destructuring-bind
    {{(x y} ...);;; this is the same as #2; it would destructuring-bind the first argument, a list.

Heck, we could even build it into the existing anonymous function syntax:

    [{x y} ...]
Would be the same as

    (fn (x y) ...)
But I'm not sure that's a big enough win.

-----

3 points by mpr 481 days ago | link

As per akkartik's comment, we already have multi-argument anonymous functions in anarki:

    ([prn _1 _2] "hello " "there")
But yeah maybe having full lambda list capabilities in the anonymous function would be cool.

    ([((a b) c) (prn c b a)] (list "is" "name ") "my ")
So if the first argument to the bracket function is a list, it acts as a lambda list.

But this interferes with the arc special syntax of having lists act as functions on their indices. Because the following is already valid arc code:

    (let x (list 1 2 3)
      (x 2))
    ;; > 3
In that case having different delimiters might be the way to go. So your brace notation would be used for anonymous functions with full lambda list capability.

    (let x (list 1 2)
      ({((a b) c) (prn c b a)} x 3))
Edit: trying some other formatting...

    (with (f {((a b) c)
               (prn c b a)}
           x (list 1 2))
      (f x 3))

-----

3 points by zck 481 days ago | link

I was thinking that if you have an even simpler way to bind specific variables in the function call, you can nest these anonymous functions, and not have the arguments shadow each other.

> In that case having different delimiters might be the way to go. So your brace notation would be used for anonymous functions with full lambda list capability.

Yeah, I think you're right. The square-brace anonymous function could have optionally a curly-braced first argument. If that argument isn't there, it works as the square-braced anonymous function currently does. If that argument is there, it's the arguments to that function. Since curly braces aren't used anywhere else in Arc, it can only be parsed in that one way.

But perhaps this is all a lot of work to avoid writing `fn (arg1 arg2)`. Other than golfing, I don't know if you really want to nest functions this way. And this seems like a waste of the only paired characters Arc doesn't use.

-----

3 points by rocketnia 480 days ago | link

"And this seems like a waste of the only paired characters Arc doesn't use."

I don't think even the [] syntax really pulls its weight. Between (foo [bar baz _]) and (foo:fn_:bar baz _), the latter is already more convenient in some ways, and one advantage is that we can define variations of (fn_ ...) under different names without feeling like these variations are second-class.

(Arc calls it "make-br-fn" rather than "fn_", but I wanted the example to look good. :-p )

-----

2 points by zck 480 days ago | link

Interesting.

Personally, I've never gotten comfortable with the colon intrasymbol syntax. Even in your example, I'm having trouble parsing it right now. I really don't like how it makes "bar" look like part of the first part, not the second.

-----

2 points by mpr 599 days ago | link | parent | on: Language Opinions

Those criticisms of tcl are true in the sense that yes, everything is a string, but unfounded in the sense that so what? Values are stored as strings so that they can be passed around as data and used immediately as code. But if you pass "5" to a command that needs to use it as an int, a conversion will take place, and the int value will be stored along with the string representation. The most recently used type of a variable is kept as the primary representation for efficiency (basically caching).

Here is a link to some docs about the tcl macro system, written by its author: http://wiki.tcl.tk/11155

From a practical perspective, I agree with what you say about "Past a point, languages don't matter. At some point you have to build stuff with the languages you know." Programming is about building stuff that works and is useful, and no matter how much theory you know, at some point you just have to sit down and type.

My motivation for evaluating and comparing the relative merits of languages is that programming, for me, is so much more fun when using certain languages. It's not about finding some holy grail of PL. It's about finding a language that is powerful and will pretty much let me do whatever I want.

I completely agree with your point about learning languages to keep learning languages. Even though I consider tcl to be more fun to program in than forth, my time writing forth code was extremely valuable to the way I think about programming.

I really like that analogy to bears.

Edit: The tclers wiki, to which I linked above, contains a massive amount of information on the language, programming in general, and many other topics, the likes of which I've never seen concentrated as well in another programming community.

-----

2 points by akkartik 599 days ago | link

"It's not about finding some holy grail of PL. It's about finding a language that is powerful and will pretty much let me do whatever I want."

I don't see the distinction. Isn't "let me do whatever I want" the holy grail of PL? :)

My point is that it's equally important to keep learning new things _to_ want. No single language will consistently do that.

"The tclers wiki contains a massive amount of information on the language, programming in general, and many other topics, the likes of which I've never seen concentrated as well in another programming community."

That is high praise! Feel free to submit your favorite links as new threads.

"..everything is a string, but.. so what? ..if you pass "5" to a command that needs to use it as an int, a conversion will take place.."

Automatic conversion is utterly evil: http://stackoverflow.com/questions/9032856/what-is-the-expla...

Here's another way to think about this. Types are useful but they're also speed-bumps. Dynamic languages push types to runtime so that you can run partially correct programs and gradually make them right. The drawback is that you can never be sure that your program doesn't have a type error; there can always be some code path that you haven't tested that causes everything to come crashing down. Passing just strings around makes this drawback exponentially worse: now you can't be sure your program is right even if you cover all possible code paths and your tests have perfect coverage, because type safety is a function of the lines executed and the data they rely on.

On a hunch I also took a look at Tcl's scope design (http://wiki.tcl.tk/12245) because that's something non-lisps (ahem, Python) mess up in subtle ways. And sure enough, Tcl is brain-damaged in this area. (I have sympathy for this mistake, because I too have spent time trying to "design hybrid approaches that strikes a great balance in maximizing the advantages of both lexical and dynamic scoping while minimizing the disadvantages of each": http://arclanguage.org/item?id=15137. Everyone should play with these things, but there's no known improvement on Lisp's dynamic scope and Scheme's lexical scope -- exactly as they're implemented there. Everyone should play with scope strategies so they can understand why.)

-----

2 points by mpr 599 days ago | link

It looks to me like the confusion in that SO post is because Javascript does something sneaky in order to support several syntaxes that mean the same thing. Tcl has no syntax. Braces aren't used to delimit blocks and denote dictionaries. They're just used as an escaping mechanism, period. Once you know the language, it is always clear how the interpreter will treat your data.

-----

1 point by akkartik 599 days ago | link

The brace ambiguity only covers some of the issues they discuss. And there are many more elsewhere on the internet..

-----

2 points by mpr 599 days ago | link

What do you find brain damaged about tcl scoping?

About the holy grail quote... I should say it's not about __finding__ such a language, more like it's about __searching__ for such a language. Which involves constantly looking at different ones.

-----

2 points by akkartik 599 days ago | link

upvars is a terrible idea. You don't want capturing variables from the lexical environment to be something you have to explicitly ask for everytime. If you don't tend to use lexical scope all the time you end up with all sorts of action-at-a-distance bugs.

Newlisp is another language that uses dynamic scope by default. All it does is save a little bit of implementation effort. In every other way it's bad for users.

Interestingly, Mu plays this game as well. In Mu you have to explicitly specify what level variable you mean. But Mu explicitly isn't a high-level language. It's a language for building Lisp compilers in. And all Lisp compilers implement lexical scope under the hood using these tricks.

So what I'm saying is that Tcl is trying to avoid some work related to automatically managing lexical scope, and trying to pass this off as good for programmers.

But these comments of mine aren't as concrete as I usually strive for. I doubt you'll be persuaded by them. Just keep them in mind as you continue your search.

"..it's not about __finding__ such a language, more like it's about __searching__ for such a language. Which involves constantly looking at different ones."

Yeah, we're completely in agreement there.

-----

3 points by jsgrahamus 599 days ago | link

My workday language, MUMPS, also treats everything as a string, unless a number is needed, in which case it tries to coerce the string into a number.

I do find coding easier in MUMPS. Surely that has nothing to do with 30 years of coding in it or my brain having been rewired for it...

-----

2 points by mpr 599 days ago | link | parent | on: Language Opinions

Also, if anyone is interested in learning/discussing tcl I'd be more than happy to engage.

-----

3 points by mpr 600 days ago | link | parent | on: Execute code in the scope of a hash table

Hey, could you expand on what we might do to hack local macros into arc? I'm really keen on writing macrolet

-----

2 points by rocketnia 600 days ago | link

It would take a bit of refactoring: Changing the `env` list to a table, adding it as a parameter of `ac-macro?` (which should now look things up from that table first), and finally adding an `env` parameter to Arc's `eval`.

-----

More