Arc Forumnew | comments | leaders | submit | rocketnia's commentslogin
1 point by rocketnia 5450 days ago | link | parent | on: Module idea for PyArc

Oh yeah, this is an interesting issue. It's one reason I'm going with hygienic macros in Penknife, besides simple cleanliness. :-p The technique I use in Penknife, as it turns out, is an example of syntactic closures, but I didn't know it until well after I had an okay system in place. I found out when I read this from http://lambda-the-ultimate.org/node/4196:

"Other frameworks for lexically-scoped macros, notably syntactic closures (Bawden and Rees 1988) and explicit renaming (Clinger 1991), use a notion of lexical context that more directly maps to the programmer's view of binding scopes. Unfortunately, the more direct representation moves binding information into the expansion environment and, in the case of syntactic closures, tangling the representation of syntax and expansion environments."

I had exactly this entanglement happening in Penknife at one point. At the time a Penknife macro is defined, the local environment is captured, and later on, syntax generated by the macro is wrapped up in a way that refers back to that environment. At one point I was just putting the environment itself in the wrapper.

For your purposes, that might be just fine, especially if you're just going to eval the macro's result right when you get it. Then again...

PyArc's modules sound exactly like I want Penknife's (not yet existing) modules to be, and you seem to value the ability of the library users to configure the libraries to their own purposes, much like I do. I suspect, in our languages, programs will need to load the same library multiple times for different purposes, especially when it comes to diamond dependencies, where two individual libraries might depend on a common third library but configure it in different ways.

With Penknife's approach to extensible syntax, it turns out parsing is really slow, so I've organized the language so that it can parse a library in one pass and then run the resulting code over and over. ("Parse" basically means "compile" in Penknife, but Penknife does one or two additional things that could be called compilation, so I shy away from that word altogether.) That exposes a weakness of tangled syntactic closures: The compiled representation holds the original captured environment, rather than the one that should be used this time around.

So for Penknife I ended up storing the environment in the macro itself and complicating my environments so that variables were referred to based on what sequence of macro-unwrapping it took to get to them. Since the person using the macro obviously has the macro in their namespace, it comes together pretty nicely. I'm happy with the result, but it's not nearly as simple as (= env!foo 4), so I can't expect you to follow the same path. ^^

-----

1 point by Pauan 5450 days ago | link

In PyArc, right now, we just re-parse and re-eval it, so loading the same module twice is not a problem.

Okay, so, I fixed the issue with it thinking a variable was undefined when it wasn't. I then found out something very interesting:

  ; foo.arc
  
  (assign message* "hello")
  (mac ret () 'message*)
  
  ; bar.arc
  
  (import foo "foo.arc")
  (foo!ret) -> "hello"

  (= ret foo!ret)
  (ret) -> error: message* is undefined
So... without even trying to, I automagically made macros sorta-hygienic with modules. When you import a module, and then call it's macros using the foo!bar table convention, it works just fine. The macro will use it's defining environment, rather than the caller's environment. But... if you pull the macro into the current namespace, it dies, because it's now being eval'd in the caller's namespace.

I'm pretty okay with this. It's kind of a wart that macros don't work quite right in that situation, but the fact that they do work when using the table is pretty darn nice. This also theoretically gives the caller the choice of whether to eval the macro in the defined environment, or the caller environment. The only issue, I think, is that this behavior may be confusing, at least until you've been bitten by it once or twice; by then you should have it memorized. :P

Obviously this is just a simple test... the real test will be when it's released and we'll have more complicated macros. Then we'll see if there's still any major hygiene problems or not.

-----

1 point by rocketnia 5450 days ago | link | parent | on: Programmable ssyntax in Arc

If at some point you say this in Arc...

  (def foo ()
    (look-up-something-in syntax-rules*))
...then (foo) will always use the most recent version of 'syntax-rules* , regardless of how many times it's been reassigned since the definition of 'foo.

This is one reason it's challenging to do modules in Arc. You have to construct 'foo so that its references to 'look-up-something-in and 'syntax-rules refer to the meanings that exist at the time 'foo is defined (already done in Arc, but something to consider as you do things from scratch), and then you have to somehow make the same name have different meanings in different contexts (hard to do on top of official Arc, thanks to Racket's opaque namespaces, but very possible to do from scratch).

I think the least surprising thing to do would be to have your core functionality work the same way as 'foo does in Arc: It would look up the value of 'syntax-rules* , and that particular binding (meaning) would be a fundamental constant of the language. Ultimately, that's the same thing as hardcoding the name, but with an extra layer of indirection to account for modules.

-----

1 point by Pauan 5450 days ago | link

I'm not sure what you mean. Do you mean that it would always refer to the most recent version of syntax-rules* in the current module? Isn't that expected behavior? If you mean it would refer to the most recent version defined in any module, then that shouldn't be an issue, since they are/should be isolated.

My eval hack is only about updating the internal table rules when somebody changes ssyntax-rules* This avoids checking it too frequently, potentially making it run faster. So, the following should work fine:

  (= foo ssyntax-rules*)
  (= ssyntax-rules* nil)
  
  foo            -> previous ssyntax rules
  ssyntax-rules* -> nil

  (= ssyntax-rules* foo) -> works fine again
And yes, I plan to have lexical scope in PyArc. Each function has it's own environment, and a reference to the outer environment, so it can walk up the chain until it either finds the value or realizes it's undefined.

There's a bug with it right now, but I should be able to fix it. The environments get created properly, but when doing the actual lookup, it thinks the value is undefined when it isn't. That's pretty normal, since the interpreter is still missing lots of functionality.

-----

1 point by rocketnia 5450 days ago | link

"I'm not sure what you mean. Do you mean that it would always refer to the most recent version of syntax-rules * in the current module? Isn't that expected behavior? If you mean it would refer to the most recent version defined in any module, then that shouldn't be an issue, since they are/should be isolated."

The core is a module, right? At least in concept? And shouldn't you be able to modify something in another module when you need to, to fix bugs etc.? (Not that you'd be able to fix bugs in the core, necessarily. :-p )

---

"There's a bug with it right now, but I should be able to fix it. The environments get created properly, but when doing the actual lookup, it thinks the value is undefined when it isn't. That's pretty normal, since the interpreter is still missing lots of functionality."

It sounds almost like you shallowly copy the outer scope when you create a local scope, which means you miss out on later definitions made in the outer scope. Is this right? It's a pretty wild guess, and even if it is right, then I'm interested to know how you're shallowly copying arbitrary environment types. :-p

(EDIT: Oh, you've fixed this already. I'm just late to the game.)

-----

1 point by Pauan 5450 days ago | link

Yes and yes. But there's supposed to be a clean separation between modules. If you assign to a variable, it assigns it in your module. If you want to assign to a different module, you need to be explicit, like so:

  (import foo "foo.arc")
  (= foo!bar 'qux)
So in order to assign to the core module, you would need something like built-ins* to access it:

  built-ins*!ssyntax-rules*  ; modify ssyntax globally
Assuming I choose the name built-ins* of course.

"then I'm interested to know how you're shallowly copying arbitrary environment types. :-p"

Environments are just wrappers around Python's dict type, so I can use the copy() method to make a shallow copy. I might need to make my own custom copy function later, though. Arc tables are just annotated dicts, by the way. But Python lets me modify how classes behave, so for instance I should be able to get cons cells to work in a dict, even though they're mutable (and thus not normally allowed).

-----

1 point by rocketnia 5450 days ago | link

"If you want to assign to a different module, you need to be explicit, like so:"

Ah, I don't know why I didn't see that. ^_^

---

"Environments are just wrappers around Python's dict type, so I can use the copy() method to make a shallow copy. I might need to make my own custom copy function later, though."

I was harking back to "What is an environment? Anything that supports get/set on key/value pairs." Would it also need to support a copy function?

-----

1 point by Pauan 5450 days ago | link

Hm... it shouldn't, no. Only global_env is copied, so Arc types wouldn't need to worry about copying. In the hypothetical case that I would need to copy them, I could coerce them into a table and then do a copy on that. This could all be done transparently, so Arc code wouldn't need to do anything.

global_env is treated specially because it's the base for all other modules: it contains the built-in functions that modules cannot create (at least not in Arc), and also the core functions in arc.arc for convenience.

There are other strategies I could use as well, like special-casing (assign) or similar, but I figured a shallow copy would be easiest.

-----

1 point by Pauan 5449 days ago | link

"...and then you have to somehow make the same name have different meanings in different contexts (hard to do on top of official Arc, thanks to Racket's opaque namespaces, but very possible to do from scratch)."

I'm curious, how are Racket's namespaces opaque?

-----

1 point by rocketnia 5449 days ago | link

That's a good question. It's probably unfair to Racket to just call the namespaces opaque without an explanation. In fact, I'm not sure there isn't a way to do what I want to with Racket namespaces, and even if there weren't, there might be a way to configure Racket code to use a completely different kind of name resolution.

With a Racket namespace, you can get and set the values of variables, you can reset variables to their undefined state, and you can get the list of variables, so it's very much like a hash table. But suppose I want to have two namespaces which share some variable bindings. I may have been able to get and set the values of variables, but it turns out I can't manipulate the bindings they're (in my mind) stored in. I can't create two namespaces which share a binding.

Fortunately, Racket namespaces can inherit from modules, which sounds pretty much exactly what I want, and there's also the "racket/package" module, which may or may not act as an alternative (for all I know). However, you can't just create modules or packages dynamically; you have to define them with particular names and then get them back out of those names somehow. I haven't quite been able to get anything nifty to work, maybe because of some issue with phase levels.

-----

1 point by Pauan 5449 days ago | link

So... you want two different namespaces that inherit from the same namespace? Easy in PyArc (or Python):

  (= shared (new-namespace))
  (new-namespace shared)
  (new-namespace shared)

-----

1 point by rocketnia 5449 days ago | link

I did say "very possible to do from scratch." PyArc's behavior doesn't help in pg's Arc. ^^;

-----

1 point by Pauan 5449 days ago | link

Yeah, that's true. It's one of the (few?) benefits of writing the interpreter from scratch, rather than piggybacking on an existing implementation.

-----

2 points by rocketnia 5449 days ago | link

The less an interpreter piggybacks, the less will be left over to implement when porting the interpreter to another platform. That benefit's enough by itself, IMO. ^_^

But then I tend to care more about the work I'll need to do in the future than the work I do today. When my priorities are the other way around, like for fun-size DSLs that'll hopefully never need porting, being able to piggyback is nice.

-----

1 point by rocketnia 5450 days ago | link | parent | on: Programmable ssyntax in Arc

It's not just a matter of performance. That's the kind of semantic difference that'll make it really hard for people with existing Arc code to use their code on your interpreter; in a sense, it won't actually "work" in the sense of being a working Arc implementation. Are you concerned about that?

No need to be; it could very well become a language that's better than Arc (and if modules are in the core, that's definitely a possibility ^_- ).

-----

1 point by Pauan 5450 days ago | link

I'm not terribly concerned about existing code. I would like existing code to work, but it's not a huge priority as in "it has to work no matter what." I've even toyed around with the idea of changing the default ssyntax, which would definitely break code that relies on Arc's current ssyntax.

I'm curious, though, what's the semantic difference? In PyArc, when eval sees a function, it calls apply on it. Apply then calls eval on all of the function's arguments, and then calls the function. How does MzArc differ significantly in that regard?

-----

1 point by rocketnia 5450 days ago | link

In MzArc (RacketArc?), 'eval is only called once per top-level command (unless it's called explicitly). If you define a function that uses a macro and then redefine the macro, it's too late; the function's code has already been expanded.

I rely on this behavior in Lathe, where I reduce the surface area of my libraries by defining only a few global variables to be macros that act as namespaces, and then replacing those global variables with their old values when I'm done. In an Arc that expands macros at run time, like old versions of Jarc, these namespace macros just don't work at all, since the variables' meanings change before they're used.

I also rely on it in my Penknife reimplementation. I mentioned (http://arclanguage.org/item?id=14004) I was using a DSL to help me in my pursuit to define a function to crank out new Penknife core environments from scratch. The DSL is based on a macro 'pkcb, short for "Penknife core binding," such that pkcb.binding-get expands to something like gs1234-core-binding-get and also pushes the name "binding-get" onto a global list of dependencies.

There's also a global list of Arc expressions to execute as part of the core constructor function. Adding to the list of expressions invalidates the core constructor function.

Whenever you call the core constructor, if it's invalid, it redefines itself (actually, it redefines a helper function) by evaluating code that has a big (with ...) inside to bind all those 'pkcb variables. But actually, I actually don't have a clue what 'pkcb variables to put in the (with ...) at that point. Fortunately, thanks to Arc's macro semantics, the act of defining triggers the 'pkcb macro forms, and it builds a list telling me exactly what I need to know. Then I define the core constructor again using that list in the (with ...).

I do all this just to save a little hash-table lookup, which I could have done with (or= pkcb!binding-get (pk-make-binding)). :-p If that's not important to you, I'm not surprised. It's hardly important to me, especially since it's a hash table of constant size; I mainly just like the thought of constructing the bindings eagerly and ditching the hash table altogether when I'm done.

So that example falls flat, and since you're taking care of namespaces, my other example falls flat too. I've at least established the semantic difference, I hope.

-----

1 point by Pauan 5450 days ago | link

Actually, PyArc expands macros at read-time and run-time, as needed. You can even use (apply) on macros.

If that's what aw meant by "Arc compiler", then I misunderstood. I took it to mean "compiling Arc code into a different language, such as assembly/C/whatever"

Given what aw said, though, eval doesn't invoke the Arc compiler... once read-time is over, the data structure is in memory so eval can just use that; it doesn't need to do any more parsing.

So, I'm not sure what aw meant by "eval invoking the Arc compiler."

-----

1 point by rocketnia 5450 days ago | link

Actually, PyArc expands macros at read-time and run-time, as needed.

Ah, okay. The semantic differences may not exist then. ^_^

In ac.scm, the eponymous function 'ac takes care of turning an Arc expression to a Racket expression. Arc's 'eval invokes 'ac and then call's Racket's 'eval on the result, so it is actually a matter of compiling Arc into another language. As for PyArc, in some sense you are compiling the code into your own internal representation format, so it may still make sense to call it a compile phase. The word compile is just too general. XD

-----

1 point by Pauan 5450 days ago | link

Then in that case, I think my previous answer is correct: PyArc does not have an Arc compiler, only an interpreter.

What you referred to as compile phase I refer to as read-time, since I think that's more specific. In any case, everything passes through read/eval, which is actually exposed to Arc, unlike MzArc where eval is just a wrapper.

Thus, implementing an additional check in eval could add up, if I'm not careful. So I think I'll add a hook to (assign) instead, that way it can be really fast.

-----

1 point by rocketnia 5450 days ago | link

"What you referred to as compile phase I refer to as read-time, since I think that's more specific."

I don't know about calling it read-time. Arc being homoiconic and all, there's a phase where a value is read into memory from a character stream, and there's a phase where a value is converted into an executable format, expanding all its macro forms (and in traditional Arcs, expanding ssyntax).

Read time is kinda taken, unless (read) really does expand macros. If compile time isn't specific enough for the other phase--and I agree it isn't--how about calling it expansion time?

---

"In any case, everything passes through read/eval, which is actually exposed to Arc, unlike MzArc where eval is just a wrapper."

I don't know what consequences to expect from that, but I like it. It seems very appropriate for an interpreter. ^^

---

"Thus, implementing an additional check in eval could add up, if I'm not careful."

I see what you mean.

-----

1 point by Pauan 5450 days ago | link

Read actually does expand macros. At least for now. In other words, it does the "read characters into memory" and "convert into executable format" phases at the same time.

Calling it expansion time isn't really correct because it doesn't always expand; sometimes it just takes data like 10 or (foo bar) and converts it into the equivalent data structure.

---

"I don't know what consequences to expect from that, but I like it. It seems very appropriate for an interpreter. ^^"

Well, it means you can overwrite them in Arc, possibly to implement defcall or similar, as opposed to in MzArc where they're hidden.

-----

1 point by shader 5450 days ago | link

Maybe we need an official jargon file for arc, given that we're having a lot of confusion over things like "compile time", what to call pg's original arc, etc.

I realize that there was an attempt at starting a wiki, but it requires permission to edit and is somewhat different from most wikis I'm familiar with. Maybe a more open data platform would be more accessible, such as wikia? I would quickly set one up there if anyone else is interested (and aw isn't too offended ;)

-----

2 points by aw 5450 days ago | link

Well, http://sites.google.com/site/arclanguagewiki/ is up, running, works, is available for editing for anyone who want to edit it, and I've been planning to continue to contribute to it myself. So... certainly anyone can post material anywhere they want, including a different wiki platform if they like it better, and if it's something useful to Arc we'll include links to it just as we include links to other useful material, whether published in a wiki or a blog post or whatever... but I personally would be looking for a more definite reason to post somewhere else myself.

Is there something specific you don't like about Google Sites?

Some of the things I was looking for:

- no markdown. I despise markdown syntax (when writing about code) with the myriad special syntax that I can never figure out how to escape.

- a way to plug in code examples from my upcoming "pastebin for examples" site, which I hope I'll be able to create a Google gadget to do.

- a clean layout without a lot of junk gunking up the page

- simple and easy to use

-----

1 point by Pauan 5449 days ago | link

I propose pgArc, personally. Short, simple, and unambiguous.

-----

2 points by shader 5448 days ago | link

Traditionally we've used "vanilla" to refer to the main branch, but then as a community we don't really care much for tradition anyway :P

-----

1 point by aw 5450 days ago | link

What's the semantic difference?

-----

1 point by rocketnia 5450 days ago | link | parent | on: Programmable ssyntax in Arc

With this system, ssyntax can be scoped to each module

You're going to have modules? Do tell. Something like CommonJS modules, maybe? ^_^

---

1. Needs to be able to define prefix, infix, and suffix versions for the same character.

What do you intend to do with "a!!!b"?

Also, what do you plan to do about precedence? Currently a.b has higher precedence than a:b, but many of us prefer for a:b.c to mean (a (b c)) rather than (compose a (b c)) (http://arclanguage.org/item?id=13172, http://arclanguage.org/item?id=3174), so in Penknife (http://arclanguage.org/item?id=13071) I've made an everything-is-left-associative-with-the-same-precedence approach. But by doing that I also sacrifice prefix notation, and I have postfix notation only as a degenerate case of infix (with an empty right side).

---

2. Needs to have separate ssyntax per module, so ssyntax defined in one module doesn't interfere with another module.

That should be easy enough if you already have a plan for modules. Modules just need to keep both an everyday variable namespace and an ssyntax namespace. In fact, I think ssyntax could be implemented with regular globals, something like Penknife's approach.

---

This could be as simple as having a ssyntax-argument-rules global, in addition to ssyntax-rules* but I'm not sure if I want to go that route. Ideas?*

Oh, right, I think that's the typical Arc way to set up an extensible framework. Consider the 'setter, 'templates, 'hooks, and 'savers* tables, as well as Anarki's 'defined-variables, 'vtables, and 'pickles* tables, all defined in arc.arc. (On the other hand, consider that macros aren't kept in a 'macros* table.)

It would be nice for an Arc module system to take that into account, somehow constructing module-local versions of each framework for all the framework entries people want in the scope of that module. But that may not be a good solution either; some frameworks, such as the 'defgeneric framework ('vtables* and 'pickles), or the 'coerce table in Anarki's ac.scm, are specifically supposed to be used by one codebase and configured by another. If someone defines a new kind of function, for instance, they register a coercion from that type to 'fn, and they can automatically pass it to almost any utility that expects a function; if those utilities are in modules protected from that registry change, that defeats the purpose. In a sense, customizability and modules play against each other. I'm a huge fan of both those things, so I'd be very interested in a solution. XD

-----

1 point by Pauan 5450 days ago | link

> "You're going to have modules? Do tell. Something like CommonJS modules, maybe? ^_^"

Yes, but not like CommonJS. I was going to save the explanation for when it's released, but I suppose I might as well tell my plan now.

Here you go: http://arclanguage.org/item?id=13977

> "What do you intend to do with "a!!!b"?"

It would be treated as 3 infix calls. So if ! expands to (l 'r) then it would be ((a '!) 'b) That may not be a good idea. By the way, a!!!!b would be ((a '!) '!) b

As for precedence, that's why I made it an alist rather than a table: it's sorted, so I can compare it in a top-to-bottom fashion. In other words, precedence is determined by the item's index within the alist.

> "That should be easy enough if you already have a plan for modules."

Yes, but I made it a requirement anyways, in case somebody had a crazy idea for custom ssyntax that was incompatible with modules.

-----

1 point by Pauan 5450 days ago | link

"If someone defines a new kind of function, for instance, they register a coercion from that type to 'fn, and they can automatically pass it to almost any utility that expects a function; if those utilities are in modules protected from that registry change, that defeats the purpose."

That's an excellent point, and I hadn't considered that. As I mentioned in the module thread, it uses (new-namespace) to create a clean slate, right? I could provide a way to add stuff to (new-namespace); in essence changing what the built-ins are. Sounds both dangerous, yet extremely malleable and congruent with Arc's spirit.

-----

1 point by rocketnia 5451 days ago | link | parent | on: www to non-www redirect in svr.arc

I believe it means responding to a request to "www.example.com" by sending a 301 redirect to the corresponding URL at "example.com".

Recently, I've been discovering that the www is a good idea in certain ways. Cookies are sent to servers based on the domain name, so having that www there is a good way to make sure that you'll be able to have a subdomain which isn't sent those cookies (say, because it's served in a way you don't fully trust, or because it's a static file subdomain which shouldn't have to wait for the browser to send it superfluous cookies). I'm kinda not sure how it all works right now, but it's got me paranoid. But I digress; whether the redirection should be done is not the question here. ^_^

-----

2 points by akkartik 5451 days ago | link

Ah, I see. I tend to put that kind of stuff in the reverse proxy (I would never hook arc up directly to port 80). Here's the relevant part of readwarp's nginx config:

  server {
    listen   80 default;
    server_name  readwarp.com alias www.readwarp.com;
    ..
  }
Apache has similar syntax, but I am happy to report I have forgotten what it is.

-----

1 point by markkat 5451 days ago | link

Yes, rocketnia is right. That's what I'm getting at. I haven't used a reverse proxy yet. But I am at least using the setuid line in svr.arc now. ;)

-----


"Seems nil doesn't like being shadowed."

That's because ((fn (nil) nil) 10) is supposed to be the same as ((fn (()) nil) 10), which destructures the first 0 elements from 10, treating it as a degenerate cons list.

I actually use this behavior in practice, 'cause it's useful to have at least some parameter syntax that doesn't bind any variables, rather than just using a conventional name like "ignored" or "_". Most of the time I use it in a much-too-big 'withs block, where I abuse one of the bindings for sanity-checking or other side effects. ^^ I wouldn't worry about it too much, though.

On the other hand, nil's behavior makes it more verbose to determine whether something's usable as a local variable; you have to check (and x (isa x 'sym) (~ssyntax x)). In Lathe, I define 'anormalsym to do just that. (It might be more accurate to change it to (and (isa x 'sym) (~in x nil t 'o) (~ssyntax x)).)

Speaking of (~ssyntax x), ssyntax is usable for local variable names, but naturally, you can only get the values from the Racket side:

  arc> (let a.b 4 a.b)
  Error: "reference to undefined identifier: _a"
  arc> (let a.b 4 ($ a.b))
  4
That could be a bug.

Personally, I'd like for ssyntax-burdened names in parameter lists to use user-defined parameter syntaxes, like custom forms of destructuring and whatnot. And then, just for axiomatic simplicity's sake, I'd like things like 'o to be implemented using the same system, using ssyntax-burdened names like ".o".

-----

1 point by Pauan 5451 days ago | link

"That's because ((fn (nil) nil) 10) is supposed to be the same as ((fn (()) nil) 10)"

nil is such a weird value... it's false, a symbol, and an empty list all at once. I actually had to do some hacky stuff to get () and '() and nil to behave properly.

"Speaking of (~ssyntax x), ssyntax is usable for local variable names, but naturally, you can only get the values from the Racket side:"

I plan to implement ssyntax at the reader level in my interpreter, so that shouldn't be an issue.

-----

1 point by evanrmurphy 5451 days ago | link

"nil is such a weird value... it's false, a symbol, and an empty list all at once."

Perhaps nil should serve as the base case for every type, rather than just some of them. In Arc, it already does this for booleans, lists and strings (""), but it could be extended to support tables (#hash()), numbers (0), symbols (||) etc. Then it would have more consistency as a concept.

-----

2 points by shader 5451 days ago | link

I've run into issues with nil the symbol vs nil the value. It's caused some issues when I wanted to use arc's sym type to represent variable names in a class compiler project I'm working on. If the variable's name is nil, all kinds of things stop working, and I've had to resort to calling some scheme functions directly to get proper handling of symbols.

I wish that 'nil and nil could be kept somewhat separate, with the first being just a symbol, and the second being equivalent to the base value for all data structures. Otherwise the symbol type is broken and inconsistent, since you cannot accurately (or at least completely) represent code as data.

-----

2 points by Pauan 5450 days ago | link

My interpreter treats 'nil and nil as separate. Not sure if that's a good idea, but I'd rather wait and see if it causes problems before changing it:

  (is 'nil nil) -> nil
This does raise one interesting question, though... obviously 'nil is a sym, but if (is nil ()) is t, then shouldn't (type nil) return 'cons?

-----

2 points by waterhouse 5449 days ago | link

() is not a cons cell, and while (car ()) is well-defined (to be nil), you can't set the car or cdr of (). It is definitely not a cons.

On the other hand, (listp nil) should be true. In fact, its precise definition should probably be (and is, or is equivalent to) this:

  (def listp (x)
    (or (is x nil)
        (and (acons x)
             (listp (cdr x)))))

-----

1 point by Pauan 5448 days ago | link

However, I can easily define nil so that it's type is 'cons, but it would throw an error if you try to assign to the car or cdr. That may break code that assumes that 'cons != nil though.

Actually, I could represent nil as an actual 'cons cell, so that assigning to the car or cdr would work. Crazy? Probably. Especially since nil is a singleton and you can't create more nil's, so it would be a global change.

Right now, nil does have a type of 'sym, but it seems weird to treat it mostly like an empty cons cell, but not have it's type be 'cons. So I figured I could play around with it and try giving it a type of 'cons and see how badly it breaks stuff.

-----

2 points by evanrmurphy 5448 days ago | link

"Actually, I could represent nil as an actual 'cons cell, so that assigning to the car or cdr would work. Crazy?"

That's a bit crazy. :)

PicoLisp does something reminiscent. Every one of its data structures (numbers, symbols, nil and conses) is implemented using the low-level cons cell structure (i.e. a pair of machine words). [1] They talk about nil's representation fulfilling its dual nature as both a symbol whose value is nil and a list whose car and cdr are nil; both the symbol predicate and the list predicate return true when applied to nil:

  : (sym? NIL)
  -> T
  : (lst? NIL)   
  -> T
I'm not sure that they let nil's car and cdr be assignable though, because "NIL is a special symbol which exists exactly once in the whole system." [2]

--

[1] http://software-lab.de/doc/ref.html#vm

[2] http://software-lab.de/doc/ref.html#nilSym

--

Update: Oops, I just noticed a lot of this comment could be considered redundant with the grandparent comment by waterhouse. Sorry for that.

-----

1 point by rocketnia 5451 days ago | link

My code has a lot of [string:or _ "nil"] to handle that kind of stuff. ^_^ I've just kinda accepted it ever since http://arclanguage.org/item?id=10793.

I do 'string lists into strings a lot, but that would continue to work if 'nil and '() were separate values.

-----

1 point by aw 5451 days ago | link

If the variable's name is nil, all kinds of things stop working

Can you give an example? Are you trying to use nil as a variable name in Arc, or simply to have a data structure representing variables where some of those variables are named nil?

-----

1 point by shader 5448 days ago | link

I'm creating a compiler for a class, and I've been representing variable names in the ast with symbols. I could use strings instead, and may end up doing so, but symbols seemed a more elegant solution.

-----

1 point by Pauan 5450 days ago | link

So nil would be an ubertype that basically just means "empty"? One issue with that is (is 0 nil) would be t... I think it's useful to not always treat 0 as the same as nil.

Not sure about empty tables, though... maybe it would be fine to treat those as nil. We already treat an empty list as nil, after all.

Actually, what you're suggesting is very similar to how languages like JavaScript and Python do it, with 0 null undefined NaN "" etc. being falsy. So in JS, you can do this:

  var foo = 0;
  if (foo) {} // will not be evaluated

  foo = "";
  if (foo) {} // will not be evaluated

  foo = NaN;
  if (foo) {} // will not be evaluated
On the other hand, both those languages support actual Booleans, and they treat an empty array as true.

-----


Yeah, t should be rebindable, right? :)

-----

2 points by aw 5451 days ago | link

http://awwx.ws/localt0

-----

1 point by rocketnia 5451 days ago | link

I mean making 't an everyday global variable, like fallintothis does at http://arclanguage.org/item?id=11711.

As far as prior work goes, I think this topic is the last time "can't rebind t" came up: http://arclanguage.org/item?id=13080 It's mainly just me linking back to those other two pages, but it's also a pretty good summary of my own opinion of the issues involved.

-----

2 points by aw 5451 days ago | link

In my runtime project, nil and t are ordinary global variables... if someone turns out to want the rebind protection feature, I'll add it as a compiler extension (which other people could then choose to apply, or not, as they wished).

-----

1 point by Pauan 5451 days ago | link

What I would do is make it print a warning, but still allow it. Something like, "hey, you! it's a bad idea to rebind nil; you'll probably break everything! use (= nil ()) to fix the mess you probably made"

-----

1 point by aw 5451 days ago | link

Printing a warning is a good idea. Whether rebinding nil could be fixed with "(= nil ())" is an interesting question, you might (or might not, I haven't tried it) find that rebinding nil breaks Arc so badly that = no longer works... :-)

I see a potential for a contest here: how to permanently break Arc (with "permanently" meaning you can't recover by typing something at the REPL), in the most interesting way, using the fewest characters. (Non-interesting being, for example, going into an infinite loop so that you don't return to the REPL).

-----

1 point by Pauan 5451 days ago | link

Quite possibly. It should work in my interpreter, though. Actually, () and nil are two different values, but they're special-cased to be eq to each other. It was the only way I found to make one print as "nil" and the other print as "()" From an external point of view, though, they should seem the same.

Also, if = doesn't work, assign probably would:

  (assign nil '())
I just tested it in my interpreter:

  (assign nil 'foo) -> foo
  nil               -> foo
  'nil              -> nil
  ()                -> ()
  '()               -> nil
  (assign nil '())  -> nil
  (is nil ())       -> t
  
So yes, it should be possible to recover, even after overwriting nil (at least in my interpreter. I don't know about MzScheme)

-----

1 point by Pauan 5451 days ago | link

Exactly! nil too. :P

-----

2 points by shader 5451 days ago | link

Hmm... the issue with that is that you might start having to quote nil or t whenever you want to actually mean nil or t, instead of just typing them in normally.

I do wish there was an easier way to tell whether or not a value was provided as nil, or was left empty and defaults to nil. Maybe doing destructuring on rest args would help solve that problem in most cases?

-----

1 point by Pauan 5450 days ago | link

"I do wish there was an easier way to tell whether or not a value was provided as nil, or was left empty and defaults to nil."

I too have sometimes wished for that in JavaScript, but let me tell you a little story. I was writing a syntax highlighter, and got it working fine in Chrome and Firefox 3.5, but there was a bug in Firefox 3.0.

You see, I was using this bit of code here:

  output.push(text.slice(curr.index[1], next && next.index[0]));
If `next` doesn't exist, it will pass the value `undefined` to the `slice` method. In JS, if you don't pass an argument, it defaults to `undefined`, so this is supposed to behave like as if I hadn't passed in the argument at all.

But in Firefox 3.0, the slice method behaves differently depending on whether you pass it `undefined`, or don't pass it any arguments. So, I had to use this instead:

  if (!next) {
      output.push(text.slice(curr.index[1]));
  } else {
      output.push(text.slice(curr.index[1], next.index[0]));
  }
This was (thankfully) fixed in 3.5. The moral of the story: most of the time it doesn't matter whether the caller passed nil, or didn't pass anything. You can treat the two situations as the same.

Consider this hypothetical example in Arc:

  (your-func 5 (and x y z))
If x, y, or z are non-nil, it will be passed in as usual. On the other hand, if any of them are nil, it will be like as if you had used (your-func 5 nil).

By behaving differently when nil is passed in vs. not passing in an argument, you might cause the above example to break. Or perhaps it would work, but the behavior would be subtly different... introducing bugs.

By having different behavior depending on whether an argument is passed or not, you force callers to do this, instead:

  (iflet val (and x y z)
    (your-func 5 val)
    (your-func 5))
Note the redundancy. In fact, this is even more important in Arc (compared to JavaScript) because you can use any expression, such as (if), a macro call, etc.

So... let me ask: what situations do you really need to know whether the caller actually passed in nil, or didn't pass anything at all?

-----

1 point by rocketnia 5450 days ago | link

Great point. In fact, I don't check whether an optional argument was passed very often, and the times I do, I usually expect to regret it at some point, for exactly that reason. ^_^

-----

1 point by rocketnia 5451 days ago | link

"I do wish there was an easier way to tell whether or not a value was provided as nil"

I share this sentiment. One thing we could do is have a space of hidden-from-the-programmer variables which tell you whether other variables have been bound. They can be accessed using a macro:

  (= given-prefix* (string (uniq) "-given-"))
  (mac given (var)
    ; NOTE: I don't think this will work properly for nil, but nil is
    ; never a local variable name anyway.
    (sym:+ given-prefix* var))
The implementation of argument lists would need to be aware of 'given-prefix* and bind the prefixed variables at the same time as the regular ones.

---

"Maybe doing destructuring on rest args would help solve that problem in most cases?"

What do you mean by that?

-----

2 points by shader 5451 days ago | link

Well, if you use a rest arg for all optional values, and then use some form of destructuring bind on that list to extract your optional arguments, then you can tell whether or not they were passed in or merely defaulted to nil by just searching the arg list.

  (def test args
    (if (assoc 'c args)
          (pr "c was passed")
        (pr "c was not passed")))

-----

1 point by rocketnia 5451 days ago | link

I still don't follow. We can already manage the argument list manually, but in most of the suggestions here, we can only do it if we don't destructure it in the signature (unless we use more complicated kinds of destructuring).

  ; Current options:
  
  (def test args
    (let ((o c)) args
      (pr:if (len> args 0)
        "c was passed"
        "c was not passed")))
  
  (let missing list.nil  ; This is just something unique.
    (def test ((o c missing))
      (pr:if (is c missing)
        "c was not passed"
        "c was passed")))
  
  
  ; Some hypothetical options and non-options:
  
  (def test (& (c))
    (pr "no way to tell if c was passed"))
  
  (let missing list.nil
    (def test (& (c))
      (pr "still no way to tell if c was passed")))
  
  (def test (& args)
    (let ((o c)) args
      (pr:if (len> args 0)
        "c was passed"
        "c was not passed")))
  
  (def test (& (&both args (c)))  ; Destructure twice.
    (pr:if (len> args 0)
      "c was passed"
      "c was not passed"))
  
  (def test ((o c nil c-passed))
    (pr:if c-passed
      "c was passed"
      "c was not passed"))
  
  (def test ((o c))
    (pr:if given.c
      "c was passed"
      "c was not passed"))
  
  (def test (c)  ; Parameter lists are just destructuring.
    (pr:if given.c
      "c was passed"
      "c was not passed"))
  
  (def test (&both args (c))
    (pr:if (len> args 0)
      "c was passed"
      "c was not passed"))

-----

1 point by Pauan 5450 days ago | link

Brilliant! In fact, you could write a macro that would do that for you:

  (mac defreq (name args . body)
    `(w/uniq gen
       (def ,name ,(map (fn (x) `(o ,x gen)) args)
         ,@(map (fn (x) `(if (is ,x gen) (err:string "parameter " ',x " is required"))) args)
         ,@body)))

  (defreq foo (x y) (+ x y))
  (foo)     -> x is required
  (foo 1)   -> y is required
  (foo 1 2) -> 3
It probably breaks with rest arguments, but I think you could get those working too.

-----

1 point by Pauan 5450 days ago | link

Or this version, which is even better:

  (mac defreq (name vars . body)
    (if (isa vars 'cons)
          (let exp (len vars)
            `(def ,name args
               (let giv (len args)
                 (if (< giv ,exp)
                       (err:string "expected " ,exp " arguments (" giv " given)")
                     (apply (fn ,vars ,@body) args)))))
        `(def ,name ,vars ,@body)))


  (defreq foo (x y) (+ x y))
  (foo)     -> error: expected 2 arguments (0 given)
  (foo 1)   -> error: expected 2 arguments (1 given)
  (foo 1 2) -> 3
  
  (defreq foo args args)
  (foo)     -> ()
  (foo 1)   -> (1)
  (foo 1 2) -> (1 2)
  
It fails on functions that take required and rest args, though:

  (defreq foo (x y . args) (list x y args)) -> error
Err... right, you were talking about detecting if an argument was nil or not given... but I realized that the same technique could be used to write a version of def that implements required arguments even in a language where every argument is optional.

-----

1 point by Pauan 5451 days ago | link

Only if you actually rebind them. It's like using `quote` as a variable name: you can do it, but most people won't because that's silly. I just think it's nice to allow it, on the off chance it's actually useful. It just feels weird to arbitrarily say "you can't rebind nil and t" but allow rebinding of everything else.

-----


This is a bit off-topic, but technically, Racket's syntax reader treats (a b c) and (a . (b c)) differently. It wraps just about every node of the parse tree in a syntax object, but the exceptions are the nodes which, from a certain point of view, aren't even part of the parse tree: The list tails which aren't written using their own parentheses. In particular, the (b c) part of (a b c) isn't wrapped, but the (b c) part of (a . (b c)) is.

  Welcome to Racket v5.1.
  > (define abc (read-syntax #f (open-input-string "(a b c)")))
  > (define a.bc (read-syntax #f (open-input-string "(a . (b c))")))
  > (define abc.null (read-syntax #f (open-input-string
                                       "(a b c . ())")))
  > abc
  #<syntax::1 (a b c)>
  > a.bc
  #<syntax::1 (a b c)>
  > abc.null
  #<syntax::1 (a b c)>
  > (syntax-e abc)
  '(#<syntax::2 a> #<syntax::4 b> #<syntax::6 c>)
  > (syntax-e a.bc)
  '(#<syntax::2 a> . #<syntax::6 (b c)>)
  > (syntax-e abc.null)
  '(#<syntax::2 a> #<syntax::4 b> #<syntax::6 c> . #<syntax::10 ()>)

-----

2 points by rocketnia 5452 days ago | link | parent | on: Readc, readline, and newlines

Scheme's 'char-ready? is probably close to what you're looking for. It returns #f if the runtime can guarantee that the stream would have blocked if it were read from at that time, and otherwise it returns #t.

  ; This exploits an Arc 3.1 bug to drop to Racket (as seen at
  ; http://arclanguage.org/item?id=11838). It's unnecessary on Anarki.
  (mac $ (racket-expr)
    `(cdr `(nil . ,,racket-expr)))
  
  (def discard-input ((o str (stdin)))
    " Reads all the characters from the stream, giving up partway if it
      can determine the stream would block. The return value is `nil'. "
    (while:and $.char-ready?.str readc.str))
  
  
  arc>
    (do (pr "enter something> ")
        (discard-input)
        (prn "The first character was " (tostring:write:readc) ".")
        (discard-input))
  enter something> something
  The first character was #\s.
  nil
  arc>

-----

2 points by rocketnia 5455 days ago | link | parent | on: Some Racket utilities, at Lathe

I threw together these Racket utilities just to learn about Racket macros. Plenty of other utility libraries exist, and you should probably look for those instead if you're interested. :-p

These utilities aren't extensible (kind of a must for me, nowadays). Furthermore, they're not built to have especially nice error messages. ^_^;

What they are is slightly more convenient, of course. A lot of them are inspired by Arc or my Penknife draft, but they're not comprehensive takes on Arc or Penknife by any means. Mostly, they save parentheses.

  ; before
  (begin 1 2 3)
  
  ; after
  (du 1 2 3)  ; "do" was taken (yeah, silly example)
  
  
  ; before
  (cond [(plan-a)  4]
        [(plan-b)  5]
        [else      6])
  
  ; after
  (ifs (plan-a)  4
       (plan-b)  5
                 6)
  
  
  ; before
  (let* ([a 1] [b 2])
    (display "hi")
    (let ([c 3])
      (+ a b c)))
  
  ; after
  (w- a 1 b 2
    (display "hi")
      c 3
    (+ a b c))
  
  
  ; before
  (foo (bar 3 (baz (qux 1 2 3))))
  
  ; after
  (: foo : bar 3 : baz : qux 1 2 3)
  
  
  ; before
  (lambda (a b) (string<? (car a) (car b)))
  
  ; after
  (: abfn : string<? (car a) (car b))
  
  
  ; before
  (let next ([a 1] [b 2])
    (next b a))  ; loop forever
  
  ; after
  (nextlet a 1 b 2
    (next b a))

-----

More