Arc Forumnew | comments | leaders | submit | Pauan's commentslogin
2 points by Pauan 4666 days ago | link | parent | on: Cyclical data structures in arcueid

My solution to this (while breaking compatibility with Arc) is to just not have mutable cons cells. I only see two legitimate uses for mutable cons cells:

1) Speed. But honestly, if you're micro-optimizing your Arc code by mutating conses...

2) Intentionally infinite lists, like akkartik's "nils":

  (= nils (list nil))
  (= cdr.nils nils)
I'm not concerned about the first case, especially because a sufficiently smart compiler should be able to handle immutable cons cells reasonably (I believe Racket does).

As for the second case... I think that's solved by adding in a more general system for dynamically generating lazy lists. Then akkartik's "nils" can be written like this:

  (= nils
    ((afn ()
       (lazy-cons (fn () nil)
                  (fn () (self))))))
Basically, lazy-cons takes two thunks: the first is called to extract the car, and the second to extract the cdr. This lets you dynamically create all kinds of nice things, like streams, and it isn't limited to constant values like cyclic lists are.

Thus, you get all the benefits of immutable conses and a strictly more powerful system for lazy list generation. The only possible downside is some performance, which I don't think would be a big deal.

Of course, I already know you're going for full Arc compatibility, so this post is probably useless, but oh well.

-----

2 points by dido 4664 days ago | link

Removing mutating cons cells would only solve part of the problem. One would also need to essentially eliminate hash tables, and practically all mutating state. And no, I never understood how monads worked. XD

And yes, the ultimate goal I have for Arcueid is to make a fully compatible Arc implementation, so breaking this sort of compatibility is out of the question as far as this project is concerned, but it is interesting to consider.

It would, for one, make real-time garbage collection a lot simpler...

-----

1 point by akkartik 4666 days ago | link

I'm flattered that you remember wart's nils :) And why would you imagine I care about compatibility?! Oh, you mean dido.

It's a good question whether you ever need cycles outside of these two scenarios. Hmm..

-----

2 points by Pauan 4665 days ago | link

"Oh, you mean dido."

Yes, my post was directed at dido.

---

"It's a good question whether you ever need cycles outside of these two scenarios."

I suppose there might be some algorithms that work better with mutable conses, but honestly, I think conses work best when they're functional, since they have a recursive definition. Making them mutable muddles a lot of different things with no real gain because you rarely mutate conses in Arc.

-----

1 point by Pauan 4669 days ago | link | parent | on: Number - Real

Since that's being done in Anarki, I'd rather use $.exact->inexact, rather than relying upon Racket's auto-coercion. My trick is best used in Arc 3.1 which doesn't have (easy) access to Racket.

-----

2 points by akkartik 4669 days ago | link

Ok, done. For some reason I thought your trick was more permissive..

-----

2 points by jsgrahamus 4668 days ago | link

I like the (real (/ 3 2)) format. You easily get both.

-----

2 points by Pauan 4669 days ago | link | parent | on: Number - Real

"Should exact or inexact be the default subtype for num?"

I see at least a few good solutions:

1) Use floating point everywhere. This is what JavaScript (and Nulan) do. Simpler, but it means you give up exact math on rationals.

2) Use floating point by default but support coercing to exact. This may or may not lose information, I don't know. Alternatively, support exact by default and coerce to floating point.

3) Do things the way Racket does it, but when printing the number, coerce it to floating point. This is what Arc/Nu does:

  > 3/2
  1.5
This has the benefit that math calculations are exact, but the result is printed in a more readable fashion (more readable to me anyways).

-----

1 point by akkartik 4669 days ago | link

Thanks, I ended up going with the third option, but with a twist: display prints inexact but write prints exact. So at the prompt you still see exact numbers:

  arc> (/ 3 2)
  3/2
  arc> (prn "aa: " (/ 3 2))
  aa: 1.5
https://github.com/nex3/arc/commit/a9dc15d701

-----

1 point by Pauan 4669 days ago | link

Well, the whole reason I did it in Arc/Nu was because I liked to use the REPL as a calculator, and I hated having it print rationals. So, having the REPL print rationals kinda defeats the point in my eyes, but I don't care since I don't use Anarki.

-----

2 points by akkartik 4669 days ago | link

Yeah, I don't actually want to see rationals at the prompt. It's just that lisp has this nice distinction between 'write and 'print, with the guarantee that 'read can handle anything 'write emits. I'm loath to have write+read change a value in subtle ways.

In this case, that the repl uses 'write is an unfortunate constraint. Still thinking..

-----

2 points by akkartik 4668 days ago | link

Update: I just realized my print solution sucks:

  arc> (prn (/ 3 2))
  1.5
  arc> (prn (list (/ 3 2)))
  (3/2)
Rolling it back. Let's just explicitly use real for now.

-----

2 points by Pauan 4670 days ago | link | parent | on: Number - Real

Fun fact: in Racket, you can use this:

  (+ (/ 3 2) 0.0)
The same thing works in Arc and it'll be much faster than calling `num`

-----

1 point by jsgrahamus 4669 days ago | link

Good catch, Pauan. Thanks.

-----

3 points by Pauan 4686 days ago | link | parent | on: URLs separated by U+2002 become one

For reference, here are the whitespace characters as defined by Unicode:

http://en.wikipedia.org/wiki/Whitespace_character#Unicode

And here's some Arc stuff to match them:

  (mac unicode-range args
    `(list ,@(mappend (fn (x)
                        (if (acons x)
                            (accum a
                              (for i (coerce car.x 'int) (coerce cadr.x 'int)
                                (a:coerce i 'char)))
                            (list x)))
                      args)))

  (= unicode-whitespace (unicode-range (#\u0009 #\u000D) #\u0020 #\u0085 #\u00A0 #\u1680 #\u180E (#\u2000 #\u200A) (#\u2028 #\u2029) #\u202F #\u205F #\u3000))

  (def whitec (c)
    (some c unicode-whitespace))
Also, Racket has a "char-whitespace?" function which after very minor testing seems to do the same thing:

http://docs.racket-lang.org/reference/characters.html?q=whit...

-----

3 points by akkartik 4686 days ago | link

Thanks! I just updated anarki to use char-whitespace? and friends.

-----

3 points by Pauan 4686 days ago | link

I wonder if it'll cause any problems... in particular, Arc's "punc" is obviously not trying to be comprehensive, so maybe it was designed specifically for URL syntax?

-----

1 point by akkartik 4685 days ago | link

Hmm, it was always defined in arc.arc, so I think it's intended for more than urls. Besides, why would bang be part of URL syntax? And wouldn't it also need ampersand? The original version seemed to include the characters for regular english punctuation.

Update an hour later: but I see where you're coming from; punc is only used in one function -- urlend.

Update two hours later: I found one regression in urlend: https://github.com/nex3/arc/commit/671c5ec916. We also have unit tests for markdown now, so any further regressions need only be caught once more.

-----

2 points by Pauan 4685 days ago | link

I think you're right, but for future reference:

http://en.wikipedia.org/wiki/Percent_encoding#Types_of_URI_c...

http://en.wikipedia.org/wiki/URI_scheme#Examples

-----

5 points by Pauan 4686 days ago | link | parent | on: Remove all duplicates from a list

"As I see it, the dot (car.ls) is syntactic sugar for applying a one arg function? Pretty clever!"

Arc has the following ssyntax:

  foo.bar     -> (foo bar)
  foo!bar     -> (foo 'bar)
  
  (~foo 1)    -> (no (foo 1))
  (foo:bar 1) -> (foo (bar 1))
  (foo&bar 1) -> (and (foo 1) (bar 1))
To be honest, I only use the ".", "!", and ":" ssyntax. Also, "~", ":", and "&" work even when they're not in functional position:

  (map ~foo ...)
  (map foo:bar ...)
  (map foo&bar ...)
---

"Use of iso. Don't know why I used is.. Question I had, can '=' be use as a comparison operator?"

The only difference between "is" and "iso" is that "iso" recursively checks lists:

  (is  (list 1 2 3) (list 1 2 3))  ; nil
  (iso (list 1 2 3) (list 1 2 3))  ; t
"=" is always used for assignment, never for comparison. The only built-in equality function is "is", and "iso" is defined using "is".

---

"Also I get your use of 'else, but not exactly sure how it passes."

Like most Lisps, Arc has a datatype called "symbol", which is roughly equivalent to an "identifier" in other languages. Normally, if you use "else" it will refer to the variable "else", but if you use quote, it will be the symbol "else":

  else   ; variable
  'else  ; symbol
The rule for booleans in Arc is that the symbol "nil" is false, and everything else is true. The symbol "else" is not equal to the symbol "nil", therefore it is true.

The reason why akkartik used "else" is a personal style difference. It's idiomatic to just leave it off:

  (if 1    ; if
        2  ; then
      3    ; if
        4  ; then
      5)   ; else
---

"Now, if I understood well, there are two major forks, one you are working on, that would be wart, and anarki (mistaken already?)."

wart isn't a fork of Arc, it's a different language which has some similarities with Arc.

You can find a bunch of Arc info here:

https://sites.google.com/site/arclanguagewiki/

In particular, anarki is a true fork of Arc, which has diverged a lot, so don't expect much compatibility with existing Arc programs. But if you want all kinds of new features, I'd recommend it. I haven't used it myself though, so that's about all I can say about it.

Arc/Nu[1] is a fork of Arc I created, which should be mostly compatible with Arc 3.1, but cleans up the compiler and adds in a few new things. Naturally I recommend using this if you want something similar to Arc 3.1.

Then there's various other implementations of Arc, such as jarc and Rainbow, and an incomplete C implementation of Arc called Arcueid.

---

* [1]: https://github.com/Pauan/ar

-----

3 points by akkartik 4686 days ago | link

  (if 1    ; if
        2  ; then
      3    ; if
        4  ; then
      5)   ; else
Yeah, it's problematic how to indent the "5". The above way makes it look like a test rather than an action, and indenting it further makes it seem like part of the same block as 4.

You can tell that there's no idiomatic way to deal with this because the arc/HN codebase is schizophrenic, using both in different places.

-----

2 points by rocketnia 4686 days ago | link

Well, I always indent it in one of two ways, depending on how much room I have:

  (if 1  2
      3  4
         5)
  
  (if 1
    2
      3
    4
    5)
This style may have drawbacks, but I stubbornly use it anyway. :-p

  (list:if 1   ; misaligned condition
    (do a b c
      (d e f))
      (is x 0)  ; looks like part of the previous expr
    4
    5)
EDIT: Changed "I like it most of the time anyway" to "I stubbornly use it anyway." XD Somebody upvoted me before my edit....

-----

1 point by akkartik 4685 days ago | link

I understood what you meant, rocketnia ^_^

-----

2 points by Pauan 4686 days ago | link

For Nulan, my current thought is that multi-case "if" is too confusing with indentation, so the above would be written like this:

  if 1
    2
    if 3
      4
      5

-----

2 points by Pauan 4686 days ago | link | parent | on: Remove all duplicates from a list

"perhaps I'm trying too hard to avoid the afn :) I've never liked that idiom, perhaps because it's so hard to indent well."

That's why Arc/Nu provides alet and awith...

  (alet foo 1
    (self (+ foo 1))

  (awith (foo 1)
    (self (+ foo 1))
...which are equivalent to this:

  ((afn (foo)
     (self (+ foo 1)))
   1)

-----

2 points by Pauan 4698 days ago | link | parent | on: Rudimentary pattern-matching in def

"Only supported in def without :case. I'm basically not sure whether to support selective quoting or pattern matching. Suggestions?"

Well, I don't see any problem with that, since everything pattern matching can do, :case can do, and vice versa. So it makes sense to me to define one in terms of the other. Whether you define :case as a pattern, or patterns as :case, will depend on which strategy you think is better.

Oh, I see, wart supports only a single :case, right? If it supported multiple :case's, then you could support pattern matching and :case, right?

-----

2 points by akkartik 4698 days ago | link

Yes, this isn't currently legal:

  def (foo) :case a :case b  # second case is ignored
Is this what you meant?

---

I think I wasn't clear about my meaning. Consider the following definition:

  def (foo 'a b)
    ..
This can mean two things:

a) Selective-quoting: foo takes two args, the first isn't eval'd.

b) Pattern-matching: that definition of foo is dispatched to if the first arg is a.

So I wasn't sure which to use. Since I posted this I ended up using backquotes for pattern matching, so for case b) you'd end up saying:

  def (foo `a b)
    ..

-----

1 point by rocketnia 4696 days ago | link

"Since I posted this I ended up using backquotes for pattern matching"

I agree with Pauan. This is reasonable. ^_^ Well, beware of corner cases like trying to match literal uses of the symbol 'unquote.

-----

1 point by akkartik 4696 days ago | link

Wart knows knows no symbol unquote, only a literal comma :)

-----

1 point by Pauan 4697 days ago | link

"Is this what you meant?"

Yes.

---

"Since I posted this I ended up using backquotes for pattern matching"

That sounds reasonable. It's what I would have done in the past.

-----

2 points by Pauan 4723 days ago | link | parent | on: Multi-word commands

Well, I think you already know my answer, since I think Nulan's syntax system is superior.

But at least you're coming up with new ideas. I can't knock you for that.

---

"I'm using keyword args in the definition of multi-word macros to evoke pattern-matching, showing what parts of a call are 'constants'. (Perhaps I need to move to a pattern-matching rather than function-name-based lookup for calls?)"

Why not just use actual pattern matching for the function arguments, like Nulan does?

I would like to point out that you can still use function-name-based lookup for calls. Then it would only be the arguments of the function that are pattern matched. That's what Nulan does.

Then you could say this:

  mac (with 'nils vars ... body)
    `((fn ,vars ,@body))

  mac (with 'tmpfile f ... body)
    `(let ,f (tmpfile)
       (before (system:join "rm " ,f)
         ,@body))
Notice that I'm using "quote" directly in the argument list, and that I used plain-old "mac", rather than "mac2". This also lets you place the hard-coded symbol anywhere you want:

  mac (with vars 'nils ... body)
    `((fn ,vars ,@body))
Look, ma, no need for "mac3"!

This also lets you pattern match on keywords too, if you wanted to:

  mac (with :nils vars ... body)
    `((fn ,vars ,@body))

  mac (with :tmpfile f ... body)
    `(let ,f (tmpfile)
       (before (system:join "rm " ,f)
         ,@body))
Basically, I don't see the point of having a special "mac2" macro. I'd just use full-blown pattern matching. It's not that hard (after you've bashed your head on the wall for a while trying to understand it, okay maybe it is pretty hard), and it's much much cleaner.

In fact, depending on how you've implemented wart, you could even add in pattern matching in wart itself, so that this:

  mac (foo 'bar)
    ...
Would be macro-expanded to this:

  mac (foo $u) :case ($u = 'bar)
    ...
This is similar to how you already "extend" the functionality of existing stuff.

-----

1 point by akkartik 4723 days ago | link

"Why not just use actual pattern matching for the function arguments, like Nulan does? You can still use function-name-based lookup for calls. Then it would only be the arguments of the function that are pattern matched."

Yeah, the combination of permitting arbitrary things in function position and haskell-style pattern-matching allows us to emulate full-scale pattern matching. I hadn't noticed that before. I wonder if there's a cleaner way to integrate it completely with the operator-precedence parser.

-----

1 point by Pauan 4723 days ago | link

"I wonder if there's a cleaner way to integrate it completely with the operator-precedence parser."

I assume you're talking about Nulan since as far as I know, wart doesn't have a full-blown parser. In which case it works just fine in Nulan, because the parser runs before everything else (including macros).

-----

1 point by akkartik 4722 days ago | link

Yes, Nulan or something near it.

Patterns aren't first-class in haskell. Are they first-class in Nulan? If not, I was thinking aloud that Nulan's parser might be able to subsume patterns somehow.

-----

2 points by Pauan 4722 days ago | link

"Are they first-class in Nulan?"

They might be, once I make them user-customizable. At least, as "first-class" as macros.

---

"If not, I was thinking aloud that Nulan's parser might be able to subsume patterns somehow"

Well, the way I designed it, Nulan's parser is stand-alone: you can run it without any of the other Nulan stuff.

That's because the parser is really very simple. It's only concerned about lists, symbols, numbers, and strings. It has no knowledge of boxes, or macros, or anything like that.

This is by design. Although it would be possible to integrate the parser more closely into Nulan, I like the clean separation of concerns.

Patterns are currently executed during the macro expansion phase, so that's already long after the parser has run. In addition, while the parser operates everywhere, patterns can only be used in certain places.

How they work is pretty simple. Using the not-yet-created $pattern-rule macro:

  $pattern-rule foo -> {_ pat} val body
    'val + pat + body
Let's look at this program:

  (-> (foo 5) 10) 20
We're creating a function "-> (foo 5) 10" and then calling it with the argument 20.

Now, it will call the "foo" pattern with the following arguments: (foo 5), a unique variable, and 10. These represent the pattern, the argument to the function, and the body, respectively. Whatever the pattern returns is used as the body of the function.

After running the pattern, the end result is this:

  (-> u (u + 5 + 10)) 20
Let's look at another custom pattern:

  $pattern-rule and -> {_ @pat} val body
    pat.reduce
      -> x y
        pattern-match y val x
      body
This time we're calling the "pattern-match" function, which allows the pattern matching to be recursive.

Using this system, it's easy to do, for instance, list destructuring:

  $pattern-rule list -> {_ @pat} val body
    w/box i = -1
      pat.reduce
        -> x y
          pattern-match y ('val[,(++ i)]) x
        body
And now this program:

  -> (list a b c)
    prn a b c
Will get translated into this:

  -> u
    | box a = u.0
    | box b = u.1
    | box c = u.2
    | prn a b c
Though, the above "list" pattern doesn't support the @ splicing syntax, which is much more complicated to implement.

-----

1 point by akkartik 4723 days ago | link

Thanks. It sounds like the basic question is: is selective evaluation useful enough to merit using up quotes inside param lists? Because if we didn't have it, we'd be able to use quotes for pattern matching.

-----

1 point by Pauan 4723 days ago | link

I'm not sure what you mean. Wart already uses macro-like vaus so it already uses implicit evaluation rather than explicit like in Kernel. And what else could quote mean in an argument list other than to distinguish variables from literal symbols?

-----


"If macros are really going to give programmers the power to make the language syntax their own, then language-imposed irregularity is at cross purposes with that open-endedness. I think the parentheses help programmers realize that they should look only at the variable names to determine meaningful differences between things."

That's the beauty of Nulan's syntax system: it is almost completely customizable, feels very "Lispish", and plays very well with macros. You can now have your short syntax and the benefits of "code is data is code".

Even wart's system works pretty damn well, despite being much less powerful, because it has very simple rules for how to handle things.

I think the key to making syntax play well with Lisp is to make sure the syntax has a certain amount of simplicity and consistency, and is customizable. Basically, the syntax needs to follow the list structure. Beyond that, you can make it as crazy as you want.

-----

More