Now, the thing I like about implicit has-a relationships is that they guarantee less code than making it explicit, in that they're the same amount of code sans type declarations and checks. For example, the above would be simply
(def dump-out (port l)
(map [wrie _ port] l))
No typeclass declarations and no type checking. This also has the advantage of being identical to the code you'd write if you were completely unaware of a scanner abstraction.
Also, on a more philosophical level, I feel like type checking is just out of place in a dynamically-typed language like Arc.
...which ends up being the optimizing compiler's clue:
(def dump-out (port l)
; a completely optional declaration
; for:
; (1) an non-optimizing interpreter, so that
; you have some chance of checking errors, and
; (2) the optimizing compiler, so that it can
; do some shortcuts.
(type-declaration l 'scanner)
(map [write _ port] l))
^^
You can then trivially redefine type-declaration as:
(= type-declaration
(annotate 'mac nilfn))
To disable type-checking.
Oh and yeah: type checking is plenty in-place, if you're building a library. If the library's interface functions simply passed the object to library-internal code without checking, the error will pop up as coming from some code deep in your library, where your user might be less inclined to trace (because it might be your bug, not theirs). At least if you do the check on the interface functions themselves you have proof that the bug is in the user's code.
But that macro, and the whole plumbing behind declaring and checking type classes, just don't have to exist if you make it implicit. It's a lot of added complexity for no gain in power. All you gain is a little type safety - and if you need type safety, Arc (and Lisp in general) is not your language.
I wonder how much type-checking major Lisp libraries really do have. In the dynamically-typed-language libraries I've looked at, there are more or less no type checks. In the Ruby community, they're specifically discouraged, because they limit the power of duck typing.
Yes, but who says it has to be used? In the majority of code, there won't be type checking, but we do want to say something like this:
This function accepts a list of ordinal types
which we can simply put in-code as:
(type-declaration a 'Scanner)
(type-declaration (car a) 'Ordinal)
The point is that the function doesn't specifically accept a list of strings or numbers - it accepts a traversable sequence of anything that can be ordered. The type-declaration thing is just a notation to express that to someone else.
I totally agree. One of the things I like most about Ruby and its type system is that it puts so much emphasis on has-a relationships. This is pretty much what I'm going for with stuff like redef, defm, and my formulation of settable-fn... even though they actually use is-a to check, the idea is that only certain basic functions - analogous to methods like #each in Ruby, or the instance functions of Haskell type classes[1] - need to use is-a to check, and all other functions assume that the basic functions work.
[1] I'm not that familiar with Haskell terminology, so this might be wrong.
[1] This is about correct; Haskell dispatches off the is-a ness of the type. However, code you write will generally ask for the has-a relationship:
{- merge used for mergesort -}
merge :: Ord a => [a] -> [a] -> [a]
{- The "Ord a" above asserts that the type "a"
should be an "Ord"inal, i.e. it should support
< <= > >= == methods
-}
merge [] [] = []
merge a [] = a
merge [] b = b
merge a:as b:bs
| a < b = a:(merge as b:bs)
| Otherwise = b:(merge a:as bs)
{- Of course, I haven't programmed Haskell in a while,
so the above code might be wrong. -}
If we had has-a relationships, we might say something like this in Arc:
(def merge (a b)
(unless (and (has-a (car a) 'Ord)
(has-a (car b) 'Ord)
(is (type a) (type b)) )
(err "Type error, needs Ord"))
(if
(no a)
b
(no b)
a
; now we're assured < works
(< (car a) (car b))
(cons (car a) (merge (cdr a) b))
(cons (car b) (merge a (cdr b))) ))
Hmm. LOL. I can just imagine a macro to do that type checking for you:
(type-check (list a b)
(Ord a) ((a . as) (a . as)))
It's beginning to look lot like Haskell ^^. Heck. arc.arc is congruous to Haskell.Prelude
Yeah, I've seen several ways to express has-a relationships. Statically typed languages like Haskell tend to express them as is-a relationships (e.g. Int is-a Ord), since that meshes well with type safety. Languages that embrace duck typing, like Ruby, tend to make has-a relationships implicit; just use the method (or in Arc's case, function) and assume that the receiver will react properly. This meshes well with the late-binding, screw-static-verification philosophies of these languages, which Arc generally shares, and the message-passing object model, which Arc does not.
This is where I assume Arc will end up building its type system, if it ever does embrace a single type philosophy[1]. Even if it doesn't have a message-passing model, its dynamism makes the implicit has-a model a good fit.
The only language I've seen that embraces explicit checks for has-a relationships, as opposed to expressing them as is-a or making them implicit, is Javascript. This is mostly because you're dealing with objects that have an inherently variable and ill-defined interface that you don't know a lot about at compile-time. I think most JS developers view it as an annoying necessity, though, so I'm inclined to believe it's the least pleasant way of dealing with has-a (despite being the most explicit).
[1] Speaking of which, I really think it should. This is sort of implicit in taking part in all these discussions about typing, I suppose, but I wanted to mention it anyway. Any sufficiently powerful, has-a-based type system won't be a constraint on the language, but will rather allow powerful abstractions like Ruby's #each and almkglor's scanner.
We could do something akin to Ruby: declare that "?", specifically, is allowed the end of an identifier. This seems to be as good a use as any, and it could still be used as a prefix or infix syntactic character.
And I completely concur (great word choice)--I'm working in a text editor right now (TextMate, not Emacs), and it's perfectly suitable. Forcing use of an IDE, or biasing towards it, would turn me away from the language.
The more I think about these scanners, the cooler they seem. It's a very neat abstraction, and a great way to integrate laziness into Lisp in a very Lisp-y manner.
It would be neat too if function application would destructure using Arc-side 'car and 'cdr (Scheme side '_car '_cdr, or '__car '__cdr on Arc-wiki now that we have FFI). Basically the requirement for 'apply would be a scanner, not a 'cons; after all, 'apply does not mutate the applied list.
However as is it's possible to use p-m: or kennytilton's dsb (which should really be ds-b: and put on the arc-wiki.git) for destructuring scanners.
Also the syntax for creating scanners should really be neater. Possibly this might help:
(mac scanner args
(let (a d) nil
( (afn ((opt val . args))
(if
(iso opt ''car)
(= a val)
(iso opt ''cdr)
(= d val)
(err:tostring:pr "Unknown option - " opt))
(if args (self args)))
args)
(w/uniq (a-v d-v a-valid d-valid)
`(let (,a-v ,d-v ,a-valid ,d-valid) nil
(make-scanner
'car (fn ()
(if ,a-valid ,a-v
(= ,a-valid t
,a-v ,a)))
'cdr (fn ()
(if ,d-valid ,d-v
(= ,d-valid t
,d-v ,d))))))))
'inf-iter can then be defined somewhat more neatly as:
(def inf-iter (f c)
(scanner
'car c
'cdr (inf-iter f (f c))))
All the above untested of course, including the grandparent post ^^ I'm sitting bored in the office ^^
Yeah, agreed. In general, I'd like to see ac.scm calling out to Arc functions as much as possible, to maximize the power of in-Arc tinkering via redef/defm, like scanner.
I believe this ordering is designed to allow map to take multiple lists to map over. As cooldude127 pointed out, you can't really do varargs if you put the function at the end.
I dislike this idea, mostly because the syntax is pretty trivial anyway using the bracket currying suggested elsewhere. [map car] isn't much shorter than $car, and it's much more flexible and leaves $ (or whatever symbol might end up being used) open for some other meaning. It's also more explicit, although I'm not sure that's worth much.
That's not necessarily true; as I pointed out further down (http://arclanguage.org/item?id=4704), you can get the effect you want. On the other hand, you do take a speed hit. I'm inclined towards supporting both, but swapping the args is very easy to write and may not belong in the core.
Yeah, that's exactly the sort of thing I was thinking.
As for old functions, I'd really like to have a better way than "errsafe" for determining whether or not a variable is bound. But yeah, I agree this should work for not-yet-defined functions.
Crick, my code must be completely unclear. Oh well. The paper describes snakes; my implementation makes use of "downsnakes" and "rightsnakes" as references to the edit graph, with a "downsnake" being a single downward move followed by a snake and a "rightsnake" similarly defined.
The paper describes a graph traversal method which I eventually realized was suspiciously like Pascal's Triangle (tilt your head to the left 45 degrees and squint ^^); hence the reference to "start the triangle". Basically instead of + as in pascal's triangle I have (minN (rightsnake left) (downsnake right)), while the leftmost and rightmost nodes are generated via downsnake and rightsnake, respectively, instead of the identity function as in Pascal's.