I think you're right about it being frustrating to be pulled in multiple directions when choosing how to represent a data structure.
In Groovy, I'm pulled in one direction:
class Coord { int x, y }
...
new Coord( x: 10, y: 20 )
+ okay instantiation syntax
+ brief and readable access syntax: foo.x
As the project evolves, I can change the class definition to allow for a better toString() appearance, custom equals() behavior, more convenient instantiation, immutability, etc.
In Arc, I'm pulled in about six directions, which are difficult to refactor into each other:
'(coord 10 20)
+ brief instantiation syntax
+ brief write appearance: (coord 10 20)
+ allows (let (x y) cdr.foo ...)
- no way for different types' x fields to be accessed using the same
code without doing something like standardizing the field order
(obj type 'coord x 10 y 20)
+ brief and readable access syntax: do.foo!x (map !x foos)
+ easy to supply defaults via 'copy or 'deftem/'inst
[case _ type 'coord x 10 y 20]
+ immutability when you want it
+ brief and readable access syntax: do.foo!x (map !x foos)
- mutability much more verbose to specify and to perform
(annotate 'coord '(10 20))
+ easy to use alongside other Arc types in (case type.foo ...)
+ semantically clear write appearance: #(tagged coord (10 20))
+ allows (let (x y) rep.foo ...)
- no way for different types' x fields to be accessed using the same
code without doing something like standardizing the field order
(annotate 'coord (obj x 10 y 20))
+ easy to use alongside other Arc types in (case type.foo ...)
+ okay access syntax: rep.foo!x (map !x:rep foos)
(annotate 'coord [case _ x 10 y 20])
+ immutability when you want it
+ easy to use alongside other Arc types in (case type.foo ...)
+ okay access syntax: rep.foo!x (map !x:rep foos)
- mutability much more verbose to specify and to perform
(This doesn't take into account the '(10 20) and (obj x 10 y 20) forms, which for many of my purposes have the clear disadvantage of carrying no type information. For what it's worth, Groovy allows forms like those, too--[ 10, 20 ] and [ x: 10, y: 20 ]--so there's no contrast here.)
As the project goes on, I can write more Arc functions to achieve a certain base level of convenience for instantiation and field access, but they won't have names quite as convenient as "x". I can also define completely new writers, equality predicates, and conditional syntaxes, but I can't trust that the new utilities will be convenient to use with other programmers' datatypes.
In practice, I don't need immutability, and for some unknown reason I can't stand to use 'annotate and 'rep, so there are only two directions I really take among these. Having two to choose from is a little frustrating, but that's not quite as frustrating as the fact that both options lack utilities.
Hmm, that gives me an idea. Maybe what I miss most of all is the ability to tag a new datatype so that an existing utility can understand it. Maybe all I want after all is a simple inheritance system like the one at http://arclanguage.org/item?id=11981 and enough utilities like 'each and 'iso that are aware of it....
I rewrote the type system for arc a while ago, so that it would support inheritance and generally not get in the way, but unfortunately I haven't had the time to push it yet. If you're interested, I could try to get that up some time soon.
Well, I took a break from wondering what I wanted, and I did something about it instead, by cobbling together several snippets I'd already posted. So I'm going to push soon myself, and realistically I think I'll be more pleased with what I have than what you have. For instance, Mine is already well-integrated with my multival system, and it doesn't change any Arc internals, which would complicate Lathe's compatibility claims.
On the other hand, and at this moment it's really clear to me that implementing generic doppelgangers of arc.arc functions is a bummer when it comes to naming, and modifying the Arc internals to be more generic, like you've done (right?), could really make a difference. Maybe in places like that, your approach and my approach could form an especially potent combination.
I finally pushed this to Lathe. It's in the new arc/orc/ folder as two files, orc.orc and oiter.arc. The core is orc.arc, and oiter.arc is just a set of standard iteration utilities like 'oeach and 'opos which can be extended to support new datatypes.
The main feature of orc.arc is the 'ontype definition form, which makes it easy to define rules that dispatch on the type of the first argument. These rules are just like any other rules (as demonstrated in Lathe's arc/examples/multirule-demo.arc), but orc.arc also installs a preference rule that automatically prioritizes 'ontype rules based on an inheritance table.
It was easy to define 'ontype, so I think it should be easy enough to define variants of 'ontype that handle multiple dispatch or dispatching on things other than type (like value [0! = 1], dimension [max { 2 } = 2], or number of arguments [atan( 3, 4 ) = atan( 3/4 )]). If they all boil down to the same kinds of rules, it should also be possible to use multiple styles of dispatch for the same method, resolving any ambiguities with explicit preference rules. So even though 'ontype itself may be limited to single dispatch and dispatching on type, it's part of a system that isn't.
Still, I'm not particularly sure orc.arc is that helpful, 'cause I don't even know what I'd use it for. I think I'll only discover its shortcomings and its best applications once I try using it to help port some of my Groovy code to Arc.
And yes, I did modify arc's internals to be more generic. Basically, I replaced the vectors pg used for typing with lists and added the ability to have multiple types in the list at once. Since 'type returns the whole list, and 'coerce looks for conversion based on each element in order, we get a simple form of inheritance and polymorphism, and objects can be typed without losing the option of being treated like their parents.
Anyone have any ideas on how to allow macro indirection via namespaces without having first class macros?
Lathe's approach (where namespaces are friendly-name-to-unfriendly-global-name tables encapsulated by macros):
arc> (use-rels-as ut (+ lathe-dir* "utils.arc"))
#(tagged mac #<procedure: nspace-indirect>)
arc> (macex1 '(ut (xloop a list.7 b 0 a.b)))
(gs2012-xloop a list.7 b 0 a.b)
arc> (macex '(ut (xloop a list.7 b 0 a.b)))
((rfn next (a b) a.b) list.7 0)
arc> (ut:xloop a list.7 b 0 a.b)
7
Maybe we should try and design a set of very basic namespace handling tools, and then allow users to extend off of them.
Funny, that's part of what I had in mind as I made Lathe's module system. :-p Is there some aspect of Lathe's namespace handling that's inconsistent with what you have in mind? The point of the Lathe module system is mainly to keep the rest of the Lathe utilities safe from name conflicts, so I'll gladly swap it out if we can come up with a better approach.
It was just there for a brief pause between the macroexpansions of declare and undeclare.
If we want to change the behavior of other macros for a certain region of code, then that pattern might be useful. Since we seem to be talking about static type declarations, which I presume would be taken into account at macro-expansion time, I think the "between the macroexpansions" behavior is the whole point.
Thank you for the insight. It's probably the most lucid I've been all thread. It didn't seem deliberate to me, but it could have feasibly been written that way to control other macros' expansions. This also pushes computation to expansion time, which might clarify ylando's objections about "wasting run time". Except those still confuse me: macro expansion happens once, inside a function's body or outside of it.
arc> (mac m (expr)
(prn "macro m has expanded")
expr)
#(tagged mac #<procedure: m>)
arc> (def f (x)
(m (+ x 1)))
macro m has expanded
#<procedure: f>
arc> (f 1)
2
But the original point seems lost because declare's story keeps changing. So, ylando: why do we need "ignores"?
Try building a macro that change global value,
expand code (with macros) and then change the value back.
I think that this macro must use another macro to
change the value back; like the undeclare macro above.
The second macro expands into unnecessary code; so
if you put it inside a function this unnecessary code
will waste run time.
If we have "ignore" macro, we can write macros that do not
produce unnecessary code.
This introduces a redundant nil in the after block, and using after is a bit slower than just a do1. But we can't use do1 because this "do all the work at macro-expansion" approach is so touchy that it breaks:
arc> (load "macdebug.arc") ; see http://arclanguage.org/item?id=11806
nil
arc> (macwalk '(declare name prop a b c))
Expression --> (declare name prop a b c)
macwalk> :s
Macro Expansion ==>
(do1 (do a b c)
(undeclare name nil))
macwalk> :s
Macro Expansion ==>
(let gs2418 (do a b c)
(undeclare name nil)
gs2418)
macwalk> :s
Macro Expansion ==>
(with (gs2418 (do a b c))
(undeclare name nil)
gs2418)
macwalk> :s
Macro Expansion ==>
((fn (gs2418)
(undeclare name nil)
gs2418)
(do a b c))
macwalk> :s
Subexpression -->
(fn (gs2418)
(undeclare name nil)
gs2418)
macwalk> :s
Subexpression --> (undeclare name nil)
macwalk> :s
Value ==> nil
Value ==> gs2418
Value ==> (fn (gs2418) nil gs2418)
Subexpression --> (do a b c)
macwalk> :a
Value ==> (do a b c)
Value ==>
((fn (gs2418) nil gs2418) (do a b c))
((fn (gs2418) nil gs2418) (do a b c))
Note that we reach undeclare before the actual body is expanded!
We can hack it without after or do1 (or mutation, but I avoid that anyway).
This way, declare expands in the right order and we only undeclare once, since it'll expand into nil. The nil is "unnecessary", which seems to be why you want ignore, but it's a terribly pedantic point: ignore is already accomplished by dead code elimination (http://en.wikipedia.org/wiki/Dead_code_elimination). This isn't even a case of "sufficiently smart compilers" for vanilla Arc, since mzscheme already implements the standard optimizations: function inlining, dead code elimination, constant propagation/folding, etc. (see http://download.plt-scheme.org/doc/html/guide/performance.ht...) should all be able to clean up whatever ac.scm generates. E.g.,
(mac foo ()
`(prn ',metadata*!name))
(declare name bar (foo))
Final idea: if expansion-time computation can't be avoided, you can expand the macros manually, if only for the sake of your readers. As a bonus, it does away with the dead code.
The body can be distinguished from the attribute-value pairs thanks to either the fact that it doesn't begin with a symbol or the fact that there's nothing to pair it with.
It does save a single pair of parentheses every once in a while, but it takes half a page of comments to explain comprehensively. :-p Then again, it's meant for Arc macros in general, so some of the idiosyncracies might disappear if it's modified for a specific purpose like this one.
Do y'all like the x:a?
I thought the "x:a" was just an abbreviation for things like "w/html:a", "html:a", "tohtml:a", and "sml:a", substituting whatever you decided the macro name would be. What alternative are you thinking about?
> I thought the "x:a" was just an abbreviation for things like "w/html:a", "html:a", "tohtml:a", and "sml:a", substituting whatever you decided the macro name would be.
The "x:a" originally came from the example at http://en.wikipedia.org/wiki/SXML#Example, where I think it had another meaning - maybe something to do with XHTML... anyway, it's probably not important. You clarified that I wasn't missing something in the conversation (unless we both are ;).
As you demonstrate, your file does not contain the same thing as the original command you used. I think it would help to replace ".com" with ".scm".
By the way, to use preformatted text, indent each line with two spaces. Also, you can use asterisks for italics. There's actually an instruction page for this somewhere, but I can't find the link either, so no worries. :-p
Also, I don't mean to burst your bubble, but the main Arc page gives old installation instructions. You don't have to get an old MzScheme version as long as you get arc3.1.tar. http://arclanguage.org/item?id=10254
What do you like about having an object system, specifically? I wouldn't mind having one too, but I think object systems get complicated and arbitrary very quickly. I'd rather not have one monolithic abstraction where several highly-focused ones would do.
I do find myself switching to Groovy a lot thanks to its object system. I think what I mainly like is the ability to make an instance of FooSubclass and automatically have "foo in FooSuperclass" and "foo in FooInterface" be true.
Using Arc's built-in notion that the type of an object is the result of calling the 'type function on it (which is configurable via 'annotate), it's easy enough to achieve a form of inheritance that accomplishes what I just described:
; This is a table from type symbols to lists of their supertypes.
; Indirect supertypes are included in these lists, but no type is a
; member of its own list. There are no inheritance loops, either;
; inheritance here is a directed acyclic graph, and this is the
; transitive closure of that graph.
(= indirect-inheritance* (table))
(def inherits (subtype supertype)
(~~mem supertype (cons subtype indirect-inheritance*.subtype)))
(def fn-def-inherits (subtype . supertypes)
(each supertype supertypes
(when (~inherits subtype supertype)
(when (inherits supertype subtype)
(err "There was an inheritance loop."))
(let supers (cons supertype indirect-inheritance*.supertype)
(zap [union is supers _] indirect-inheritance*.subtype)
(each (k v) indirect-inheritance*
(when (mem subtype v)
(zap [union is supers _] indirect-inheritance*.k)))))))
(mac def-inherits (subtype . supertypes)
`(fn-def-inherits ',subtype ,@(map [do `',_] supertypes)))
(def isinstance (x test-type)
(inherits type.x test-type))
(def a- (test-type)
[isinstance _ test-type])
Come to think of it, I can't think of anything else object-oriented I miss in Arc. (OO is good for dispatch too, but I've already made an Arc dispatch library I like better.) I'll make sure to try this out the next time I program something substantial in Arc.
So, I've talked a lot about me, but my question to you still stands. What features are you looking for in an object system?
The most important feature of object oriented is name compressing.
Imagine a program with a lot of different databases.
Now you have to give names for functions that add element,
You will have to give them names like:
That sounds like dispatch to me. ^_^ You'd like to use one name but give it multiple meanings based on the types involved. This thread (http://arclanguage.org/item?id=11779) has a couple of approaches to dispatch from akkartik and myself.
Hmm, I've been having trouble for a while, trying to figure out my favorite way to have inheritance and multiple dispatch at the same time. (Single dispatch with inheritance seems more straightforward, but I'd rather not bother with it if I could use multiple dispatch instead.) If one method signature is (A, B) -> C, and another is (B, A) -> C, and A is a subtype of B, then what happens if you call the method with (a, a)? That could just cause an error, or the first argument could take precedence, but I'm not sure which approach I like better.
I think that multi dispatch is good idea for strong type languages.
If c++ encounters this problem it will solve it at
compile time (I think it will try to find the solution that
is best for the first variable then the second and so on).
For a dynamic language like arc multi dispatch
will have an efficiency cost. In my own opinion:
It is one of the "too smart to be useful" features of
common lisp. It is one of the reason people hate clos.
I have a question for you:
In what way a multi dispath is better then the alternatives?
You can dispatch only on the first argument
or hold a virtual table of functions for every object.
For a dynamic language like arc multi dispatch will have an efficiency cost.
I don't think the efficiency cost for dynamic dispatch on the first argument would be significantly different, and I'm not worried about either one. I think they're at worst as inefficient as a sequence of if statements, which is to say they would take constant time (given a constant number of methods to check and constant time for each check).
Constant time can still be painful in the right quantity, and making the decision at compile time would indeed help, but Arc doesn't have the necessary static type information to go on (as you know). So multiple dispatch and single dispatch alike have to suffer a bit.
It is one of the reasons people hate clos.
Well, I've heard nothing but good things about CLOS, but then I haven't heard that much. ^_^ Is there some good anti-CLOS reading material you can recommend? XD
In what way is multi dispatch better then the alternatives?
It's partly a matter of name compression. ^_-
; no dispatch
(collide-spaceship-with-spaceship s s)
(collide-spaceship-with-asteroid s a)
(collide-asteroid-with-spaceship a s)
(collide-asteroid-with-asteroid a a)
; single dispatch (on the first argument)
(collide-with-spaceship s s)
(collide-with-asteroid s a)
(collide-with-spaceship a s)
(collide-with-asteroid a a)
; double dispatch
(collide a s)
(collide s a)
(collide a s)
(collide a a)
Yeah, I stole Wikipedia's example here. Another example is a comparison function that needs to compare values of many different types. Yet another is a "write" function which has different behavior for each type of writer object and each type of object to be written.
When it comes right down to it, I just believe multiple dispatch ought to be a simpler concept than single dispatch, since it imposes fewer restrictions on the programmer. Unfortunately, it seems to replace one arbitrary restriction with a bunch of (what I see as) arbitrary design decisions. ^_^;
For every one of your examples,
we can use double dispatch design pattern;
For example:
(collide o1 o2) call
(collide-with-spaceship o2 o1) if o1 is a spaceship
or (collide-with-asteroid o2 o1) if o1 is an asteroid.
I think that you can even abstract this design pattern
with a macro (but I did not try it).
So you still did not convince me that multi dispatch is
a good feature.
At the risk of sounding like a broken record, I think Lathe's namespaces already embody lots of the ideas people are talking about here. :-p
In this case, Lathe provides two forms in its more-module-stuff.arc module, 'packing and 'pack-hiding, which work like 'packed but only put certain parts of the "my" namespace into the package object. That way, the internals don't get imported.
The 'packing and 'pack-hiding forms are in a separate module only because they aren't fundamental to Lathe, but in fact, I've never actually wanted to use them. Just having separate namespaces is enough for me, 'cause when I want to have unobtrusive definitions, I can just create a throwaway namespace to put them in.
The main point of this in Lathe is so that the form can clean up after itself using an 'after form. The return value capability is also nice.
An alternate namespace implementation might take this format so that it could search-and-replace names in its body at macro-expansion time. That was my original plan for Lathe's namespaces, but I soon realized a simplistic code walker wouldn't do, and I didn't really want to write a sophisticated code walker unless I had a whole new language in mind. Also, I doubt this approach would translate very well to the REPL.
1) I think you're missing the whole point of 'let.
arc> (= x 5)
5
arc>
(let x 3 ; "declare" a var with value 3
(= x (* x x))
(+ x 2))
11
arc> x
5
If you want to separate the declaration from the initial value... why? What happens if you use the variable in between those two times?
2) For what it's worth, my Lathe library provides a certain sort of namespaces, and I've been using those pretty happily. (http://arclanguage.org/item?id=11610) But of course I'd be happy with them, 'cause I'm their author. :-p
That said, I think 'symbol-macro-let would be nifty. I wonder if it could be even more useful to have some way to build a lambda whose free variables were treated in a custom way (as opposed to just being globals). Either one of these could be a basis for scoped importing of namespaces.
First off, two bugs: You use 'append rather than 'join, and 'shuffle_numbers will overwrite the global variable 'i since 'i isn't a lexical variable at (= i (- max min)) and (-- i). There might a third bug where you say (~is (caar lst) 'fn), but I'm not sure what your intention is there.
Next, there are some things you can simplify. In shuffle_members, you have "let temp 0 @", which isn't doing much, and you also have "@ arr" at the end, which is unnecessary. Unlike 'let, 'w/table automatically returns the value its variable has at the end of the body. In span_let, there's a place where you check [and (is (type _) 'cons) (is (car _) 'scope)], but FYI, that's exactly what [iscar _ 'scope] does. :) Also, I don't expect you to know this right away, but you can accomplish the decreasing loop using (down i (- max min) 1 ...); 'down is a backwards 'for.
Finally, it's really not a big deal, but these names would be more consistent with Arc:
binding-function-list*
span-let
shuffle-numbers
I think that the scope macro make the code more readable. What do you think?
I don't really think it makes it more readable, but that's only because it introduces new rules I'm not accustomed to. The "let temp 0" line is clearly supposed to wrap the whole area, but I had to open up Arc and check to see whether the 'for loop wrapped the 'loop loop or not. (Your indentation might have been a clue, but I suspected a bug.)
In total, you removed ((()())) and put in (scope @ @ @ @) and some extra rules to worry about. I think that's sort of a net loss. But hey, it could be the basis for a better idea. If it helps you read your code, then I think it's worth it already. ^_^