Arc Forumnew | comments | leaders | submit | akkartik's commentslogin
1 point by akkartik 4480 days ago | link | parent | on: How to getpid in Arc 3.1

Thanks!

-----

4 points by akkartik 4481 days ago | link | parent | on: How to read mutable hash tables

Cool. Some comments:

a) You're reversing your lists, like the value of some-list above.

b) If you include a table inside a list, I think riff-table won't make it mutable. Here's a slightly different file

  arc> (= h (w/infile f "nested" (read f)))
  #hash((this . "this") (nested-hash-table . #hash((three . 3) (one . "one") (two . "two"))) (some-list . (1 2 "three" #hash((four . "foo")) . nil)) (that . 3))
  arc> h!some-list
  (1 2 "three" #hash((four . "foo")))
  arc> h!some-list.3
  #hash((four . "foo"))
  arc> h!some-list.3!four
  "foo"
  arc> (= h!some-list.3!four "bar")
  Error: "hash-set!: contract violation\n  expected: (and/c hash? (not/c immutable?))\n  given: '#hash((four . \"foo\"))\n  argument position: 1st\n  other arguments...:\n   'four\n   \"bar\""
You might want to see how anarki fixes the read and write primitives to be fully general for tables as well as user-defined types:

  arc> (tofile "x" (write:obj a 1 b 2 c (list 2 3 (obj d 4 e 5 f 6))))
  nil
  arc> (= h (fromfile "x" (read)))
  #hash((a . 1) (c . (2 3 #hash((f . 6) (e . 5) (d . 4)))) (b . 2))
  arc> (= h!a 34)
  34
  arc> (= h!c.2!g 7)
  7
  arc> h
  #hash((a . 34) (c . (2 3 #hash((g . 7) (f . 6) (e . 5) (d . 4)))) (b . 2))
c) Why you no like let and with? :) I'd rewrite your expressions above as:

  (def make-mutable-cons (data)
    (let ret nil
      (each el data
        (push el ret))
      ret))

  (def make-mutable-table (data)
    (w/table ret  ; w/table implicitly returns ret
      (maptable (fn (k v)
		  (if (is (type v) 'table)
		       (= (ret (sym k)) (make-mutable-table v))
		      (is (type v) 'cons)
		       (= (ret (sym k)) (make-mutable-cons v))
		      (= (ret (sym k)) v)))
		data)))

  (def riff-table (file)
    (make-mutable-table
      (w/infile i file
	(read i))))
d) Why do you have those calls to sym?

-----

2 points by lark 4479 days ago | link

Thank you for the feedback. Thanks also to fallintothis: I wish the copy functions you wrote omitted unnecessary lines of code.

a,b) Thanks, fixed:

  (def make-mutable-cons (data)
    (withs (ret nil)
           (each el data
                 (if (is (type el) 'table)
                     (push (make-mutable-table el) ret)
                     (is (type el) 'cons)
                     (push (make-mutable-cons el) ret)
                     (push el ret)))
           (rev ret)))
c) let doesn't work with more than one variable; so withs is more general (should let even exist?). I can't tell what the difference between with and withs is, and I had picked the latter when I started needing more than one variable. What is their difference?

d) Seems sym's not needed; removed.

-----

2 points by akkartik 4479 days ago | link

Yes, let is just a simpler form of withs, but it's worth using because it signals that you only need one var and therefore skips extra parens.

withs is the sequential form of with, where you'd like each variable available in defining later variables within the with. You have to say:

  arc> (withs (x 1 y (* x 2)) (+ x y))
..but you can use the simpler with in:

  arc> (with (x 1 y 2) (+ x y))
Not only can you, you should. Using the most general form when a simpler form will do is akin to crying wolf; it's very useful when reading code to be able to tell simpler parts from more complex parts at a single glance.

So use with to signal that you're defining multiple variables at once. Use withs to signal that there's a dependency between the definitions. If there's no dependency, with is more idiomatic. And if there's only one variable, let is more idiomatic. Finally, a withs with multiple bodies and a trivial body is easier to read if you put the final expressions into the body. This:

  (withs (x 1
          y (f x z))
    y)
is better written as:

  (withs (x 1)
    (f x z))
and therefore:

  (let x 1
    (f x z))
(and further to (f 1 z), but let's pretend x is being bound to something less trivial.)

Even if a body is a little more complex:

  (withs (x complex-expr1
          y (complex-expr2 x)
          z (complex-expr3 y))
    (car z))
it's sometimes nicer to read as:

  (withs (x complex-expr1
          y (complex-expr2 x))
    (car (complex-expr3 y)))
let, with and withs are nothing but macros, so if you avoid using them you're giving up the benefits of lisp. Might as well go back to a weaker language. In general, my idea when learning lisp was: "Since I come from an imperative background my tendency is to define intermediate variables. Therefore I will start by avoiding all temporaries, and only after I get it working will I insert the fewest possible temporaries to make the code readable." It's stood me in good stead.

-----

3 points by lark 4475 days ago | link

Thank you for writing up such a clear explanation.

There are definitely safety benefits to using the simplest form. But I would prefer to use the most general form (withs) because I won't need to worry about using the most idiomatic form or to switch from one form to another as the program changes. It frees me to worry about other things.

Being able to tell simpler parts from more complex parts at a single glance isn't as much of a concern for me. I noticed I don't go back to even read code of programs that didn't solve a big enough problem. The programs reach a dead end and don't develop further. There are more important things to worry about when writing a program.

Also note what let, with, and withs offer could be provided in other languages too. Them being implemented as macros in Arc is incidental. You're not giving up the benefits of lisp by not using them.

-----

3 points by akkartik 4494 days ago | link | parent | on: Julia

I haven't actually played with it, but from reading the docs[1] Julia's macros -- like Dylan's or Nemerle's -- feel clunky compared to orthodox lisp. The clunkiness isn't caused by infix, though. Infix is easy to desugar, which might be easier to see with wart:

  $ git clone http://github.com/akkartik/wart
  $ git checkout c73dcd8d6  # wart is currently (and perhaps indefinitely) broken
  $ ./wart
  'a+b
  => (+ a b)
No, the real trouble lies in their use of f(x) syntax for function calls rather than (f x). As a result there's no way for a when macro to look like the builtin if keyword.[2]

That said, I'm sure it's useful to have even some clunky way to do code-generation. The key is that they stuck with a purely expression-based language, and didn't give in to the siren song of statements.

[1] http://docs.julialang.org/en/latest/manual/metaprogramming

[2] http://arclanguage.org/item?id=16924

-----

2 points by malisper 4494 days ago | link

I just quickly tried wart and it does seem pretty impressive to me. The only thing I'm worried about is the rules for how something is grouped. I may just need to use it more to see if it's an actual issue, but I feel that having all of the different rules can make it very easy to write hard to find bugs. Other than that I find wart to be a very interesting idea which will be worth checking out.

Also is there any way to see what my code looks like if I were to type it using parenthesis?

-----

2 points by akkartik 4494 days ago | link

Thanks for trying it out! I've tried very hard to keep the rules minimal and easy to understand. Only two sentences in https://github.com/akkartik/wart/blob/c73dcd8d6/004optional_... are rules:

1. "Multi-word lines without leading parens are implicitly grouped with later indented lines."

2. "Indentation-sensitivity is disabled inside parens."

That's really all there is to it.

> is there any way to see what my code looks like if I were to type it using parenthesis?

It's a little clunkier than visualizing infix, but you can introspect on any function or macro. Here's an example session:

  $ ./wart
  ready! type in an expression, then hit enter twice. ctrl-d exits.

  def (foo x)
    if a b
      c d
      :else e

  foo
  => (object function {sig, body, })  # wart doesn't print table values by default

  (body foo)
  => ((if a b (c d) (:else e)))
Here you can see that turning (c d) and (:else e) into calls is probably not desired.

-----

3 points by malisper 4493 days ago | link

I kept looking and found that read has the functionality I was looking for.

  (read)
  5+5

  => (+ 5 5)
I also noticed that if I want to generate lists which are expressions, it is not as straightforward as it should be (I'm trying to get a list of the number 5 the symbol + and 5 again but wart for whatever reason rearranges them).

  '(5 + 5) 
   => (+ 5 5)
I'm going to guess this is a bug.

-----

1 point by akkartik 4493 days ago | link

Nice!

Yeah, that's a bug. I haven't yet bothered to create a version of read without the infix transform. For now you have to:

  '(5 (+) 5)
If you decide to try to fix this I'd love to hear your experiences. The point of wart was to be easy to hack on, but I'm losing steam because it's been hard to get feedback on that score.

-----

2 points by malisper 4493 days ago | link

I'm going to have to familiarize myself with the internals before I actually try to fix it. After looking for a little bit, I think the issue is in the transform_infix function where it handles the quoting.

  if (is_quote_or_unquote(n.elems.front())) {
    list<ast_node>::iterator p = n.elems.begin();
    while (is_quote_or_unquote(*p)) {
      trace("infix") << "skipping past " << *p;
      ++p;
      if (p == n.elems.end()) {
        trace("infix") << "just quotes/unquotes " << n;
        return n;
      }
    }
I would think that after you determine that the first element is a quote, you would just ignore the rest instead of going through and checking which ones are also quoted. Since I'm not that familiar with c++ or how the internals of wart work, I'm going to have to leave it to you to see if this is actually the problem.

-----

1 point by akkartik 4493 days ago | link

Thanks for the investigation! Yes, that would be the way to disable transforms inside a quoted s-expr.

But I think that still would leave issues. For one, it is approximately as likely that a list innocuously contains a literal '+ as that we're constructing a fragment of code that is eventually intended to be eval'd. We need a way to say, "this is code" or "this is never going to be eval'd." A second issue is that quoting isn't the only way to read data. Imagine using read to read a list from a file. How would we suppress infix there?

I actually think reading data from a file is the bigger issue. Small quoted lists in code will be noticed, and can be replaced with some (klunky) combination of list and cons. It's far worse if you have a multi-megabyte file that silently gets corrupted because of one character.

-----

2 points by akkartik 4493 days ago | link

Another issue with just disabling transforms inside quotes. This would stop working:

  ',car.x

-----

2 points by malisper 4493 days ago | link

I'm just wondering if the second rule is really such a good idea. If one wants to use indentation-sensitivity within parens, it is impossible. I'm not sure how often trying to write code like that would come up in practice, but I think the programmer should have the option in that case of whether indentation-sensitivity is actually being used (by using brackets or something else to make it clear).

-----

2 points by akkartik 4493 days ago | link

I'm happy to rethink it if you can suggest an alternative way to suppress indent-sensitivity. I didn't have the second rule at the start, but was swayed by a discussion with the "readable s-expressions" project: https://www.mail-archive.com/readable-discuss@lists.sourcefo...; https://www.mail-archive.com/readable-discuss@lists.sourcefo.... It makes for an easily understood rule. What I had before took more than two sentences to explain: https://github.com/akkartik/wart/tree/7db61146b0#readme (search for 'suppress grouping')

-----

2 points by malisper 4492 days ago | link

Just have brackets (maybe curly braces) use indentation sensitivty.

  def (foo x y z)
    if [and
          x = 5
          y = 10
          z = 15]
      prn "success"
      prn "failed"
instead of

  def (foo x y z)
    if (and (x = 5) (y = 10) (z = 15))
      prn "success"
      prn "failed"
or

  def (foo x y z)
    if (and (x = 5)
            (y = 10)
            (z = 10))
      prn "success"
      prn "failed"

-----

1 point by akkartik 4492 days ago | link

But you have to have some way to suppress indent-sensitivity. How would you represent these?

  '(a reeeeeaaaally
    long line)

  def (foo a b c
           d e f)
    ..
Oh, are you suggesting suppressing indentation inside () but not inside []?

-----

2 points by malisper 4492 days ago | link

Yes, parens will not use indentation sensitivity, but brackets will. In that case the examples you gave would be valid code.

-----

1 point by akkartik 4492 days ago | link

Ok, interesting idea that I hadn't considered before.

As it happens, I've been watching a discussion about a different use for brackets: http://lambda-the-ultimate.org/node/4879. Lots of people (scheme, clojure, this liso guy) seem to have an idea about what to use brackets for, and it's not clear what the best use is for these precious punctuation characters.

-----

2 points by malisper 4491 days ago | link

I realized that we actually don't need to have a closing bracket to signal the end of the expression. We just need to implement something like the $ operator in haskell.

  def (foo a b c)
    if $ and
           a = 5
           b = 10
           c = 15
      prn "success"
      prn "failed"
There should be enough information based off of the indentation to tell how it should be parsed. $ should mean something along the lines of everything indented after the next symbol is a single expression.

-----

2 points by akkartik 4491 days ago | link

You'll like the old discussion on Bullet which had some similar ideas, including considering how to suppress paren-insertion inside such a special operator, etc.

http://web.archive.org/web/20120210015823/http://seertaak.po... (http://arclanguage.org/item?id=15769; http://www.reddit.com/r/programming/comments/pekx5/bullet_ad...)

-----

1 point by akkartik 4491 days ago | link

I finally installed Julia, and my comment needs a correction. Macros sometimes don't need the parens.

  julia> macro foo(ex)
	   :($ex ? nothing : error("Assertion failed: ", $(string(ex))))
	 end

  julia> @foo 2 > 0

  julia> @foo 2 > 3
  ERROR: Assertion failed: :((2>3))
   in error at error.jl:22
However, I have not been able to get this to work with multiple args:

  julia> macro foo(ex, msg)
	   :($ex ? nothing : error("$($msg): ", $(string(ex))))
	 end

  julia> @foo(2 > 3, "aaa")
  ERROR: aaa: :((2>3))
   in error at error.jl:22

  julia> @foo 2 > 3, "aaa"
  ERROR: wrong number of arguments
I don't know if this is a real limitation, or I just don't know enough yet.

-----


Yeah I've run into this already. In the screenshot for my original solution I highlighted a variable called test. Oops, poor name. Trouble is that this change now causes poor names in tightly localized contexts to bleed through the entire codebase :/

There's two directions to solve this:

a) Automatically detect variables with long lifetimes (measured in lines) to highlight. Perhaps this is a refinement of highlighting for

  *globals*
b) A way to attach metadata on a per-function or per-fragment basis. Highlight test in this function but not elsewhere, etc.

-----

1 point by akkartik 4501 days ago | link

Heh, I just thought of a name for the plugin that turns the bug into a feature: synaesthesia.vim :)

-----

2 points by rocketnia 4501 days ago | link

There was some interesting discussion on that in the Reddit thread. :)

-----

2 points by akkartik 4501 days ago | link

You got me, that's where I stole the idea from.

-----

2 points by akkartik 4518 days ago | link | parent | on: Using racket libraries in Arc

Can you give us a simple, reproducible set of directions based on say arc3.1?

-----

2 points by akkartik 4518 days ago | link | parent | on: Using racket libraries in Arc

I haven't tried this. I wonder if it's because arc's running on the mzscheme language.

One suggestion: try https://github.com/arclanguage/arc-nu, the clean racket-only repo. I've not played with it much, though.

-----

2 points by akkartik 4523 days ago | link | parent | on: Hacker News Down

Yeah this is a long-standing bug that is fixed in anarki.

I just opened a bug in the new HN bugtracker: https://github.com/HackerNews/HN/issues/54. Thanks!

-----


Thanks for the parallels with Factor! I love Factor too, though I'm less knowledgeable than you.

I'm going to keep an eye out for lower-level primitives to genericize.

-----


As an example, consider all the ways you can traverse an s-expr:

  arc> (each x '(1 2 (3 4)) prn.x)
  1
  2
  (3 4)

  arc> (each x (tree '(1 2 (3 4))) prn.x)
  (1 2 (3 4))
  1
  (2 (3 4))
  2
  ((3 4))
  (3 4)
  3
  (4)
  4
  nil
  nil

  arc> (each x (leaves '(1 2 (3 4))) prn.x)
  1
  2
  3
  4
  nil
  nil

  arc> (each x (code '(1 2 (3 4))) prn.x)
  (1 2 (3 4))
  1
  2
  (3 4)
  3
  4
The last addresses fallintothis's bug with ifs-with-do: http://arclanguage.org/item?id=18247. See https://github.com/arclanguage/anarki/commit/74ba10b700.

-----


I just did a bunch of cleanup on anarki:

1. defgeneric now dispatches on a condition rather than on a specific type. It's a little more verbose but more flexible. I'd already done this in wart almost 3 years ago (http://arclanguage.org/item?id=13790), just been too lazy to update anarki.

2. I've gotten rid of defgeneric; any def can now be generic. This is nice because I don't have to do the whole dance of bootstrap def then unset then defgeneric then defmethods. There's no more code that will get silently overridden.

3. I've merged defmethod and extend into a single name since they now do essentially the same thing. For the new name I'm trying out defextend, but I'm not attached to it.

4. I deleted a couple of libraries that had suffered bitrot or were obsoleted by newer/simpler ideas. They haven't been touched for several years, but feel free to bring them back.

5. As outlined above I've deleted ontree. Use each or walk instead.

6. I've deleted treewise and replaced its calls with reduce.

7. I've deleted tree-subst and replaced its calls with subst, which in turn relies on extensions to map.

All this feels a lot more coherent: https://github.com/arclanguage/anarki/commit/5f26254e7d#diff...

The changes: https://github.com/arclanguage/anarki/compare/1b335cd6ce...5...

-----

More