Arc Forumnew | comments | leaders | submitlogin
Two useful language extension ideas
3 points by c-a-smallwood 3351 days ago | 20 comments
While working on my MTL project, I've tried to come up with some features that would make working with expressions easier. Two ideas that I came up with have stuck, and I feel that they could be quite useful if implemented in arc. The cool thing about them is they could theoretically be written in any lisp-based language.

The first idea is an old one: A type system. My influence on this comes from Common Lisp's "deftype". When I first learned about "deftype", I hated it. At first, I figured it was like "typedef" in C (kind of), but I learned it was really a fancy predicate system. It's kind of like your "Type" is given a name, and its qualities are typically given in the form of a combination of predicates and/or a symbol naming a previously defined type. My approach to implementing this would go along the lines of the following:

1. Create a global table named "tags". This will be used for containing the tagged predicate functions.

2. Create a macro that takes a "name" and a "body" of code. In arc style, I feel like using the syntax for anonymous functions would extend well here.

Example:

(deftag posnum (and (is (type _) 'num) (> _ 0)))

Expansion:

(= (tags 'posnum) (fn (_) (and (is (type _) 'num) (> _ 0))))

You could then either rewrite the "isa" macro to support the "tags" table, or you could simply deal with using more parenthesis.

Examples:

((tags 'posnum) 0) -> nil

((tags 'posnum) 1) -> t

The second idea is also not new: Pattern matching. From time to time, I tinker with the Rust language, and my influence mainly comes from this, but I know it doesn't originate from Rust. It would be a combination of Arc-style "if", C-style "switch" and Rust-style "match". It would support multiple clauses just like "if", but I can't figure out how to format code on the forum, so it looks unreadable and I've just made single-clause examples.

Examples:

; Checks if x is a sym

(match x sym (prn "x is a symbol."))

; Checks if x is a cons and binds car to "a" and cdr to "d".

(match x (cons a d) (prn "x is a cons. car: " a ", cdr: " d))

Of course it could be changed, and extended. For instance adding the ability to pattern match a closure or macro to its arguments, body and environment, or adding the wildcard "_" symbol to ignore matching particular parts of an expression.

Examples:

; Ignores body & env of closure

(match x (fn args _ _)(prn "x is a closure. args: " args))

; Ignores all closure values

(match x fn (prn "x is a closure."))

; Specifically check if x equals "Hello"

(match x "Hello" (prn "x is Hello"))

I feel like the "match" idea could be extremely helpful in writing macros that deal with manipulating complex expressions.

Any comments or criticism?



3 points by Pauan 3351 days ago | link

You can indent a code block by 2 spaces to format it as code.

---

Your idea of types is similar to Predicate Dispatch[1]. I believe it's also similar to Racket's contracts[2].

This is an extremely powerful type system in the sense that you can express literally anything with it. However, I believe that a more restrictive type system can actually be better, because it provides more guarantees (especially at compile-time).

---

I don't think it's possible to pattern match on a function's arguments, body, and closure, simply because Racket doesn't expose those things.

I do approve of pattern matching, though.

---

* [1]: http://en.wikipedia.org/wiki/Predicate_dispatch

* [2]: http://docs.racket-lang.org/guide/contracts.html?q=contract

-----

2 points by akkartik 3351 days ago | link

Anarki does have pattern matching: https://github.com/arclanguage/anarki/blob/master/lib/defpat... (by user almkglor). It hasn't been used in years to my knowledge, though. If you try it out I'd love to hear experiences and will try to help fix any bugs you find. I like the idea in theory and tried to use it in my wart language (https://github.com/akkartik/wart/blob/master/061patmatch.tes...) but didn't make much headway so far.

(More info: http://arclanguage.org/item?id=2556)

I'm having more trouble understanding deftype. (I'd never come across it so far in my common lisp experience.) I couldn't tell what the benefit would be of your ((tags 'posnum) 0) examples over just a simple predicate. Won't it only be useful if arc actually got optional typing like common lisp?

(Also, I assume you're familiar with arc's approach to types using annotate and rep?)

-----

2 points by c-a-smallwood 3351 days ago | link

Try to think of it as an add-on, not a replacement. If you were to rewrite "isa" to first check if the type you're checking for is the builtin expression type or the tag, and if it's not, lookup the symbol in the "tags" table. If the key is not bound, it returns nil. If the key is bound, apply the predicate bound to the key to the value supplied to "isa".

Example:

(deftag posnum (and (isa _ 'num) (> _ 0)))

(isa 1 'posnum) => t

(isa 0 'posnum) => nil

(isa 1 'blah) => nil

-----

2 points by akkartik 3350 days ago | link

Ah, I see. I understand now why Pauan brought up predicate dispatch.

Incidentally, anarki does predicate dispatch through defextend:

  (defextend some (test seq) (isa seq 'string)
    (let f testify.test
      (recstring f:seq seq)))
(https://github.com/arclanguage/anarki/blob/97b3464256/arc.ar...)

Would this be easier to express using the tags table?

-----

2 points by c-a-smallwood 3350 days ago | link

In my opinion defextend is fine the way it is, it has its similarities to CLOS's defmethod, which I would avidly abuse in my earlier lisp days.

The positive thing about adding a tagged predicate dispatch table, if implemented into "isa", would be the illusion of any object being a particular "type", provided it satisfies the predicate. In reality, the true type of the object would be cons or sym or whatever its value is.

One simple idea: The int/num difference could theoretically be moved into arc codespace via deftag. Primarily leave the number type "num" internally, but deduce if a number is an "int" via the tags table.

Example:

  (deftag int
    (and (isa _ 'num)
         (is _ (round _))))
  
  (isa 1.5 'int) => nil
  (isa 1 'int) => t
One cool thing you could do is define tags for something like an object descriptor, or a class. Store whatever you need in cons cells, along with maybe a symbol at the head named "class:" or something, with "slots:" or "methods:" in that class. This could easily be checked if a particular cons resembles a "[class]" structured cons via a predicate, right? So deftag a predicate for "class". Then check if your object is a class-structed cons via "isa".

Example:

  (deftag class
    (and (alist _)
         (is len._ 3)
         (is car._ 'class:)
         (alist cadr._)
         (>= (len cadr._) 1)
         (is (car cadr._) 'slots:)
         (alist (car:cddr _))
         (>= (len:car:cddr _) 1)
         (is (caar:cddr _) 'methods)))

  (= foo '(class:
            (slots: bar)
            (methods: baz)))

  (isa foo 'class) => t
  (isa foo 'cons) => t
  (type foo) => cons
  (alist foo) => t

-----

2 points by c-a-smallwood 3349 days ago | link

I've got a simple, functional version that could easily be transcribed in anarki. Here it is in MTL:

https://github.com/camden-smallwood/mtl/blob/master/core.l#L...

-----

2 points by akkartik 3349 days ago | link

tag and isa already mean something in anarki. Any suggestions on how we integrate the existing type support with deftag? Feel free to send a pull request!

-----

2 points by c-a-smallwood 3348 days ago | link

In truth, it's been about 5-6 years since I've collaborated on anyone's code that isn't my own. I'd love to submit a pull request :) I'm installing Racket & Anarki on this windows machine this morning to see about getting a working version. Do you think "deftype" would be ok for the macro name? Any other suggestions? Maybe something in the spirit of original Arc, like "pred" for predicate or something?

-----

2 points by akkartik 3348 days ago | link

I think you're in the best position to judge :) Maybe play with a little example program and see what looks best in its context? If it doesn't seem to matter just pick one. We can always change it later.

I have to admit, I'm not sure how long it's been since somebody tried installing anarki on windows. Let me know when you run into problems. I'm actually amazed that I was able to get mtl running on linux so easily. Are you using cygwin?

-----

2 points by c-a-smallwood 3348 days ago | link

No, I can't stand Cygwin. I actually wrote the original 1200 lines of the interpreter on Fedora. My goal was to try to keep it completely portable C code. As of right now, on my machine, I create a Win32 Console app in Visual Studio to compile MTL on Windows, and it works just fine. Initially I had some segfaults, but it was mainly due to the lack of exception checks and null checks... So as far as I can tell, it's pretty much cross platform at the moment.

In the few minutes I've used anarki, it seems to work just fine :) I just run it in the command-line Racket app by running:

  (load "C:/anarki/boot.scm")
  (tl)

-----

2 points by c-a-smallwood 3348 days ago | link

Pull request submitted. I had to fix a small bug that I didn't check for before... It works now :)

-----

2 points by akkartik 3348 days ago | link

Playing with it some more, I have a few ideas/tweaks :)

1. Maybe we should rename deftype to def-isa since it only overrides isa, not type or coerce? That would also fit with how we use defcall to extend coercion to function, defcoerce to extend coerce, defset to extend assignment, etc.

2. I'm thinking of renaming the types table to type-predicates.

What do you think?

-----

2 points by c-a-smallwood 3348 days ago | link

I like both ideas, they flow well with arc and anarki's naming scheme. Something like the following:

  (def-isa list (alist _))

-----

2 points by c-a-smallwood 3348 days ago | link

At the same time, I kind of feel like having a dash makes it deviate a little... And "defisa" just doesn't look right in my opinion. Do you think having a slight variance would be ok?

-----

1 point by akkartik 3348 days ago | link

Just submitted and then saw your comment: https://github.com/arclanguage/anarki/commit/4cfe8c36e5.

Feel free to change further or to revert if it doesn't look right. Nothing is set in stone.

-----

2 points by akkartik 3348 days ago | link

Thanks! I see it now. Amazing how much difference it makes to see it working in a known context.

I figured out why '=' wasn't working; we were redefining it further down the file. I fixed it by moving deftype to later, and now we can go back to '='. https://github.com/arclanguage/anarki/commit/2c12a9e4f8. Does that seem reasonable?

-----

2 points by c-a-smallwood 3348 days ago | link

Works great!

-----

1 point by akkartik 3349 days ago | link

I'm curious how your square bracket syntax works. In particular, why does this work?

  mtl> ([_.car] '(1 2 3))
  1
But not this?

  mtl> ([0._] '(1 2 3))
  Exception: Symbol is not bound: 0

-----

2 points by c-a-smallwood 3349 days ago | link

Essentially the syntax is the same, but with one difference: If the body of the bracket function ONLY consists of a dotted expression, the body of the function IS that expression. Example:

  Anarki:
  [car._] => (fn (_) ((car _))) ; note extra parens
  [car _] => (fn (_) (car _))

  MTL:
  [_.car] => (fn (_) (car _))
  [car _] => (fn (_) (car _))
I mainly took this step to take advantage of having support for multiple "dots", Example:

  x.car.type => (type (car x))
It's just more readable in my opinion. In regards to the second part of your post, it's because of a bug in my number reader, which I'll fix soon. Thanks for pointing it out!

I plan to add n-expression support into bracket functions, too. It would make the following work:

  ([_(1)] '(1 2 3)) => 2

-----

2 points by akkartik 3349 days ago | link

Nice!

I've criticized sweet-exprs in the past as trying to do too much (https://news.ycombinator.com/item?id=8507385). But the original subset you're implementing here is very useful.

(I'm still opposed to neoteric expressions, because parens before function is The One True Way, especially in the presence of infix: http://arclanguage.org/item?id=16924. But I won't agitate for it again since n-exprs are optional :)

BTW, in searching for the above link I ran across http://arclanguage.org/item?id=18261, which you might find interesting if you hadn't already seen it.

-----