Arc Forumnew | comments | leaders | submit | absz's commentslogin

That depends on whether you're using the default arc2.tar or if you're using Anarki (the git wiki with community changes---you should use it, it's better). The answer to the second question (define a new type) is yes either way:

  (annotate my-type my-obj)
creates a new object with type my-type and representation my-obj. You can get the type with (type obj) and the representation with (rep obj). Types, however, have no associated behaviour.

The answer to the first question is no in arc2.tar (it's handled by the interpreter directly), but yes on Anarki:

  (defcall type-name (self arg)
    (do-something self arg))
defcall is like def, except instead of a function name you provide a type name. Then, whenever an object of that type is called in function position, its representation is passed in as the first argument (in that example, self), and the other arguments are passed to the rest of the parameter list. For instance, take a "shouter" type which wraps an output port and, when called, prints to that port in all uppercase:

  arc> (defcall shouter (self str)
         (w/stdout self (prn:upcase str))
         str)
  #<procedure>
  arc> (= yell (annotate 'shouter (stderr)))
  #3(tagged shouter #<output-port:stderr>)
  arc> (yell "Hello, world!")
  HELLO, WORLD!
  "Hello, world!"
And that's how you create objects of new types and make them callable.

-----

2 points by rincewind 6153 days ago | link

Can you define a setter for my-type annotated objects? Like this:

  (= tab (annotate 'my-table (table)))
  (defcall my-table (self arg)
     self.arg)

  ;what i'd like to do:
  (defset 'my-table ((self arg) value)
      (= self.arg upcase.value))

  (= (tab 'test) "foo bar baz")

  (tab 'test)
  ;should return ("FOO BAR BAZ")
If not, it should not be too hard to implement, but I would rather not put redundant effort into it.

-----

1 point by almkglor 6152 days ago | link

An alternative is to use the "lib/settable-fn.arc" or "lib/settable-fn2.arc" frameworks for this.

When using settable-fn.arc:

  (def create-my-type ()
    (add-attachments
      'key (fn () (generate-keys))
               ; note the order: value before key(s)
      '=   (fn (v k)  (assign-value-v-to-key-k v k))
      (annotate 'my-type
        (fn (k)
          (lookup-in-k k)))))
lib/settable-fn2.arc:

  (def create-my-type ()
    (add-attachments
      'key (fn () (generate-keys))
      '=   (fn (v k)  (assign-value-v-to-key-k v k))
      'type 'my-type
      (fn (k)
        (lookup-in-k k))))
Aside from 'keys and '=, it allows you to overload 'len

-----

2 points by almkglor 6152 days ago | link

The "Create your own collection" series should help a bit - these collections all use lib/settable-fn.arc, and with minimal modification should be useable with lib/settable-fn2.arc

http://arclanguage.com/item?id=3595 Suggest PG: Settable function objects

http://arclanguage.com/item?id=3698 Create your own collection in Arc: settable functions now implemented on arc-wiki.git

http://arclanguage.com/item?id=3762 Create your own collection: use directories as if they were tables with file-table

http://arclanguage.com/item?id=3858 Create your own collection: bidirectional tables

http://arclanguage.com/item?id=5254 Create your own collection: cached-table

http://arclanguage.com/item?id=7365 Create your own collection: proto-table, when you want prototyping semantics in your object system

-----

1 point by absz 6152 days ago | link

Yes, you can; it involves redefining sref. When you write (= (obj key) value), it becomes (sref obj value key). So in this case, you would write

  (redef sref (x v k)
    (if (isa x 'my-table)
      (let y (rep x)
        (annotate 'my-table (= y.k (upcase v))))
      (old x v k)))
Or, if you're using nex3's defm,

  (defm sref ((t x my-table) v k)
    (let y (rep x)
      (annotate 'my-table (= y.k (upcase v)))))
If you do this a lot, you could probably wrap a macro around it to eliminate some of the boilerplate.

-----

1 point by stefano 6153 days ago | link

'= is a macro, and therefore it needs to know how to assign to the variable at compile time, but the type information is known only at run time, but it should be possible to get something like:

  (= (my-table tab 'test) "foo bar baz")
to work. It's a little more verbose and if you changed the name of the type from my-table to something else you would have to change every assignment.

-----

1 point by chris_l 6154 days ago | link

Thanks, that's just what I was looking for!

-----

1 point by absz 6154 days ago | link | parent | on: Python-like modules

Speaking of "real gensyms," is there a reason uniq isn't simply defined (in ac.scm) to be

  (xdef 'uniq gensym)
? mzscheme's gensym has all the right properties:

  > (gensym)
  g26
  > (define g (gensym))
  > g
  g27
  > (equal? g 'g27)
  #f
  > (gensym 'prefix)
  prefix28
  > (gensym "string")
  string29
Does something break because they aren't "real" symbols? Or can we just (on Anarki) go ahead and make this change?

-----

2 points by conanite 6154 days ago | link

speaking of gensyms, is there a reason uniq isn't defined in arc? one less axiom? (on a related note, I don't know why sig is defined in ac.scm, it doesn't seem to be otherwise referenced there)

-----

1 point by absz 6153 days ago | link

Because if/when we have real gensyms, where if we have

  arc> (= gs (uniq))
  gs2003
then (is gs 'gs2003) returns nil, instead of t (which it currently does), we won't be able to define uniq in Arc. It's another datatype (the "uninterned symbol"), and thus it needs an axiom. The axiom could be something besides uniq (string->uninterned-symbol, for instance, or usym) if (isnt (usym "foo") (usym "foo")).

-----

1 point by conanite 6153 days ago | link

Aha. So uniq in its current form is really 'kinda-uniq. How important is it that gensyms aren't equal to each other? I mean, if "everyone knows" /gs[0-9]+/ is "reserved" for gensyms, then all we need do is not make symbols matching that pattern. Thus is the language minimaler.

I'm just being lazy. It can't be that difficult to implement ...

-----

2 points by absz 6153 days ago | link

For now, it's not: see http://arclanguage.org/item?id=7529 . Thanks to mzscheme, it's a one-line change :)

And does that really make the language more minimal? If we leave uniq as it is, we could move it to arc.arc, but we have the axiom that symbols of the form /gs\d+/ are forbidden; if we change uniq, uniq is an axiom, but we don't have to worry about formats.

-----

2 points by stefano 6153 days ago | link

Even if Arc were not based on mzscheme the change would be minimal: it takes exactly the same operations as creating a normal symbol, with the difference that it doesn't have to be interned.

-----

2 points by conanite 6152 days ago | link

"doesn't have to be", or must not be interned? I'm thinking the latter, if the point is to guarantee that no other symbol shall be equal to a gensym ...

-----

3 points by stefano 6152 days ago | link

You're right: it must not be interned.

-----

2 points by almkglor 6154 days ago | link

I don't think anything would break; care to try?

-----

1 point by absz 6153 days ago | link

Alright, done and on the git.

-----

1 point by absz 6155 days ago | link | parent | on: Help w/ continuations

Actually, arc just compiles everything down to Scheme; Scheme, of course, has full continuation support, and thus so does Arc.

That's a nice explanation of continuations, but one comment: I think (if I remember what I read correctly) that writing ccc in terms of mark-the-current-spot (or perhaps get/cc, or gcc :P) is actually impossible, but I could be wrong. The other advantage of ccc is that every flow control construct can be simulated using it (including, but not limited to, C's return).

-----

2 points by tokipin 6155 days ago | link

> I think (if I remember what I read correctly) that writing ccc in terms of mark-the-current-spot (or perhaps get/cc, or gcc :P) is actually impossible, but I could be wrong.

that's the short-term conclusion i came to when i was fidding with it. i didn't exactly prove it was the case though so i was being safe

-----


Hmmm, interesting. The "chaining" aspect reminds me of a pattern that people (e.g. almkglor: http://arclanguage.org/item?id=6955) have found with givens:

  (givens x (+ 1 5)
          y (* x 7)
    (- y 88))
although of course here you have to explicitly name your variables.

-----

4 points by absz 6157 days ago | link | parent | on: Objects in Arc

The real problem with storing the functions in the object, I think, is that you have different copies of the function for every object. There are two problems with this: (a) it wastes space, and (b) if you redefine the function, you have to recreate all your objects. That's the advantage of storing methods externally.

Also, speaking of "if it's an idiom, [it] probably needs a macro," I often find myself writing (obj name1 name1 name2 name2 name3 name3 ...), like you have above. So here's my nobj macro to solve that problem; for the same effect as above, just write (nobj name1 name2 name3 ...):

  (mac nobj args
    " Creates a table from the list of variables passed to it; each variable's
      name is keyed to its value.  E.g. if x = 10 and y = -10, then (nobj x y)
      results in #hash((y . -10) (x . 10)).
      See also [[obj]] [[table]] "
    `(obj ,@(flat:map [list _ _] args)))

-----

3 points by tokipin 6157 days ago | link

well, we can closure the methods with the constructor function so they aren't duplicated, at the expense of explicitly requiring the object to be passed to them, or some kind of dispatch mechanism. we can also implement simple inheritance:

  (def inherits (obj1 obj2)
       (fn (key)
           (or (obj1 key) (obj2 key))))
with (apply or ...) we could have a long inheritance chain, but here i have just two objects so the pattern is clear. we can then define objects like so:

  (let _proto (obj
                full (fn (self) (string self!first " " self!last))
                until (fn (self year) (- year self!age)))
  
    (def person (first last age)
           (inherits (obj
                       _proto _proto
                       first first
                       last last
                       age age)
  
                     _proto))
  
  )

  arc> (= p (person "joe" "momma" 18))
  #<procedure>
  arc> (p!full p)
  "joe momma"
  arc> (p!until p 22)
  4
  
  arc> (= q (person "axe" "murderer" 10))
  #<procedure>
  arc> (q!full q)
  "axe murderer"
  arc> (q!until q 22)
  12
because the inheritance is dynamically dispatched or whatitbe, we can alter the methods with the intended effects:

  arc> (q!backwards q)
  Error: "Function call on inappropriate object nil (#<procedure>)"
  arc> (= ((q '_proto) 'backwards) (fn (self) (string self!last " " self!first)))
  #<procedure:gs2439>
  arc> (q!backwards q)
  "murderer axe"
  arc> (p!backwards p)
  "momma joe"

  arc> (= ((q '_proto) 'until) (fn (self age) "a long time"))
  #<procedure:gs2451>
  arc> (q!until q 22)
  "a long time"
  arc> (p!until p 18)
  "a long time"
note the 'inherits' function is agnostic. we can inherit from arbitrary objects and functions or what have you, just that in this case it was used to inherit from a hidden prototype

anarki supposedly has user definable syntatic sugaries, so the (q!blah q) pattern could be sugarized

-----

2 points by almkglor 6156 days ago | link

http://arclanguage.org/item?id=7365

-----

4 points by conanite 6155 days ago | link

Does it really store a new copy of the function each time? I thought it would only store the closure, and apparently closures are cheap ( http://arclanguage.org/item?id=7342 ) (sorry almkglor, I end up quoting you all the time).

And as for redefining functions: once your api is stable there's probably less need to redefine functions, and if necessary you can still

(= p!until (fn () ...))

(although this way you don't have access to "private" variables in the lexical scope of the original. I'm completely with EliAndrewC in preferring (p!until ...) over (person-until p ...)

And thanks for nobj, it's awesome. It makes my macro look like Visual Basic. Slowly, I learn ...

-----

3 points by almkglor 6155 days ago | link

Yes, a good implementation should store just the closed variables and a reference to the code - there shouldn't be any code duplication.

It thus depends on how many local variables are being closed over. Note that in some implementations (although not in arc2c, and by inference not in SNAP) a closure is just two pointers: a reference to an environment and a reference to the code. Of course each 'let form and function would create a new environment though, and this style is not so often used because lookup of closed variables can require indirection.

-----

2 points by absz 6155 days ago | link

You're welcome---I'm glad to have been of assistance.

As for storing copies, I would have said that it would store extra copies because of the different variable it closes over, but almkglor points out that you can separate code and environment, so the question is what mzscheme does.

The thing about redefinition is that in, say, Ruby, you can do

  class String
    def foo
      code_goes_here
    end
  end
And every string will have that new foo method. Here, you can only redefine the methods of one object.

For me, the real syntax question is whether we want (until p age) (which probably means that we are using the CLOS model of generic functions) or we want (p!until age) (which probably means that we are using the Smalltalk model of message passing). I sort of like the former syntax, but I also sort of prefer the Smalltalk model. What do you think?

-----

1 point by almkglor 6155 days ago | link

Note that redefinition using (p!until age) syntax is still possible in Arc using 'defcall and if you predeclare the private variables.

For instance, consider this:

  (deftype foo (x)
    (private y z)
    (meth niaw ()
      (do-something x y z))
    (meth arf (something)
      (do-something-else something x y z)))
  =>
  (let methods
       (table
         ; lambda lifted!
         'niaw
         (fn (x y z)
             (do-something x y z))
         'arf
         (fn (x y z something)
             (do-something-else something x y z)))
    (def foo-replace-method (s f)
      (= (methods s) f))
    ; so external code can determine the local variables
    (def foo-get-private-variables ()
      '(x y z))
    (def foo (x)
      (with (y nil z nil)
        (let invoker
             (fn (f rest)
               (apply f x y z rest))
        (fn (which-method)
          (aif
            (methods which-method)
               (fn rest (invoker it rest)))))))
Then a method redefining macro can be:

  (def lastcons (l)
    (if (cdr l)
        (lastcons:cdr l)
        l))
  (mac redef-meth (type meth params . body)
    (givens replacer (sym:string type "-replace-method")
            privates (eval:list:sym:string type "-get-private-variables")
            _ (= (cdr:lastcons privates) params)
      `(,replacer ,meth (fn ,privates ,@body))))
Note that foo-replace-method and foo-get-private-variables could be placed in a central global table or two instead.

-----

5 points by almkglor 6157 days ago | link

  (mac nobj args
    " Creates a table from the list of variables passed to it; each variable's
      name is keyed to its value.  E.g. if x = 10 and y = -10, then (nobj x y)
      results in #hash((y . -10) (x . 10)).
      See also [[obj]] [[table]] "
    `(obj ,@(mappend [list _ _] args)))
^^

-----

1 point by absz 6157 days ago | link

Even better, then :) And I definitely need to remember that this exists.

-----

3 points by almkglor 6157 days ago | link

^^ Of course you don't: just tell Arc about what you do remember, and it'll tell you more about related things you might want to look at too:

  arc> (help map)
  (from "arc.arc")
  [fn]  (map f . seqs)
   Applies the elements of the sequences to the given function.
      Returns a sequence containing the results of the function.
      See also [[each]] [[mapeach]] [[map1]] [[mappend]] [[andmap]]
      [[ormap]] [[reduce]]

-----

6 points by absz 6157 days ago | link

I know, but when was the last time you thought you needed help with map of all things? :)

Actually, aha! I added this to the bottom of ~/.arcshrc

  (let func (random-elt:keys help*)
    (prn "Documentation for " func " " (helpstr func)))
Now whenever I start arc, it will print, e.g.,

  Documentation for saferead (from "arc.arc")
  [fn]  (saferead arg)
   Reads an expression, blocking any errors. 
  
  Use (quit) to quit, (tl) to return here after an interrupt.
  arc> 
And thus hopefully I will learn something :)

-----

3 points by absz 6158 days ago | link | parent | on: Objects in Arc

I'm not sure what I think of the coding style, but there is a problem with your new macro. What would happen, for instance, if you had

  (let this nil
    (new push [push _ this] ; Anonymous function with an argument named _
         pop  (fn () (pop this))))
When we expand new, we get

  (let this nil
    (let this (table)
      (= (this 'push) [push _ this])
      (= (this 'pop)  (fn () (pop this)))
      this))
Now, your stack no longer works---it's modifying the wrong this! Moral of the story: never let names be bound in macros. The solution is to use a unique name, generated by the function uniq. The macro w/uniq lets you get as many uniqs as you need. To rewrite new using this, we would write

  (mac obj args
    (w/uniq g
      `(let ,g (table)
         ,@(map (fn ((k v)) `(= (,g ',k) ,v))
                (pair args))
         ,g)))
Notice how instead of this, you have ,g (which must be unquoted because g holds the name we want). And also notice how I've changed some names---that's because this is the obj macro from vanilla Arc. So moral of the story number two: use built-in Arc functions (the caveat, of course, is that the documentation is, shall we say, subpar, so good luck).

Your examples will all work, thus, if you change new to obj. As for the style... I agree that person-foo is ugly, but p!foo doesn't really feel right either. One solution is to switch to Anarki (http://arclanguage.org/item?id=4951) and use a combination of annotate, defcall, and nex3's defm macro (http://arclanguage.org/item?id=4644). Here's how this might work:

  (def person (first last age)
    ; (annotate 'type object) creates an object of type 'type.
    ; The object's type is obtained by (type o), and the object's
    ; representation (the second argument) is obtained by (rep o).
    (annotate 'person
              (obj first first
                   last  last
                   age   age)))
  
  ; When an object of type 'person is used as a "function", e.g.
  ; p!first, this will be called (where self is p and field is 'first).
  ; Insert error checking if you want p!nonfield not to return nil.
  (defcall person (self field)
    self.field)
  
  ; See http://arclanguage.org/item?id=4644 for documentation of defm.
  (defm full ((t self person))
    (string ((rep self) 'first) " " ((rep self) 'last))
  
  (defm until ((t self person) year)
    (- year ((rep self) 'age))
  
  ; Examples
  (= p (person "Eli" "Courtwright" 25))
  (prn p!first)
  (prn:full p)
  (prn:until p 30)
This is a bit cumbersome, so you could define abstracting macros. Some of these already exist, for instance <plug>my tagged union system on Anarki (http://arclanguage.org/item?id=7364)</plug>, which would turn this code into

  (vtype person (first string?)
                (last  string?)
                (age   (andf int? [>= _ 0])))
  
  ; And the same defms and examples from above.
Of course, this provides you with all sorts of nifty features; see the link for details. And I recall seeing other proposed object systems too, but I can't help you with those.

-----

2 points by EliAndrewC 6157 days ago | link

I actually used "obj" as a reference when writing "new", which is why they were so similar. I decided not to use a gensym because I figured that "this" would effectively be a reserved word with a common meaning and so that I could use "this" in my methods. However, your point about clashes is well taken.

As for "defm", it looks pretty nice. I guess I wouldn't mind calling "(until p 30)" instead of "(p!until 30)" so long as I don't have to call "person-until" just to prevent naming clashes.

I really dislike having to call "((rep self) 'first)" instead of "self!first", but as you point out, I might be able to define some macros to make this less verbose.

I guess for now I'll start playing around with Anarki. Hopefully the more knowledgable Lispers out there will settle on an object system that's general enough to be useful and concise enough to by enjoyable.

-----

3 points by absz 6157 days ago | link

Ah, I see -- new is an auto-binding obj. That makes sense if you're including methods (which perhaps you shouldn't). It's just that usually when people do that it is a mistake :)

Actually, that was a mistake on my part; you can in fact write

  (defm full ((t self person))
    (string self!first " " self!last)
  
  (defm until ((t self person) year)
    (- year self!age)
because the defcall allows you to put objects of type person in that position. And now the only redundancy left is having to write (t self person) all the time, which you may or may not want to abstract.

The other missing feature is the inability to say (= p!age 42); for that, you need to override sref:

  ; Insert error checking so that you can't
  ; write (= p!species 'banana).
  (defm sref ((t self person) value key)
    (= ((rep self) key) value))
Again, <plug>the tagged unions do all of this for you (except for defining your own methods, which you have to do with defm and vcase/tcase)</plug>. Of course, they don't have inheritance.

-----

1 point by EliAndrewC 6157 days ago | link

That sounds excellent, and it makes me a lot more excited about using objects in Arc. I don't care that much about having to write (t self person) a lot, since it seems like an acceptably low amount of boilerplate.

However, out of curiosity, why do you have to write "t" instead of just saying "(self person)"? I read through the code for defm and argmap, but my Lisp reading skills aren't strong enough to decipher it.

-----

2 points by absz 6157 days ago | link

Because parameter lists in Arc support destructuring. What that means is that anywhere you can write variable to bind a name to a value (such as in (let variable 10 (prn variable))), you can also write (v1 v2) to bind a list's first element to v1 and second element to v2. And (o v default) denotes optional parameters. Perhaps some examples would be clearer:

  (with (i 1 v 5 x 10)
    (let a            (list i v)   (prn "a is (1 5)."))
    (let (b c)        (list i v)   (prn "b is 1 and c is 5."))
    (let (d . e)      (list i v x) (prn "d is 1 and e is (5 10)."))
    (let (f (o g))    (list i)     (prn "f is 1 and g is nil."))
    (let (h (o j 42)) (list i)     (prn "h is 1 and j is 42."))
    (let (k (o m))    (list i v)   (prn "k is 1 and m is 5."))
    (let (n (o p 42)) (list i v)   (prn "n is 1 and p is 5.")))
defm adds a (t var type) syntax; if you left out the t, you would have ordinary destructuring bind.

-----

1 point by absz 6167 days ago | link | parent | on: Tagged unions on Anarki

See http://arclanguage.org/item?id=7395: is this like what you were talking about?

-----

1 point by rkts 6165 days ago | link

Yep. I wouldn't call it elegant, but it works.

I feel like there ought to be a better way to do this--something that's less complex but handles more cases. In particular I'd like to be able to alternate OO and pattern-matching styles on the same datatypes. However, I haven't been able to come up with a solution that really satisfies me.

One other thing: wouldn't it be better to raise an error when an invariant is violated, instead of returning nil?

-----

1 point by absz 6165 days ago | link

You can use OO structure-access semantics with tagged unions: (tu 'slot-name) and (= (tu 'slot-name) new-value). That's not the interesting part of OO, of course :)

I thought about returning an error, but Arc seems to be a little calmer about such things: (is (car nil) (cdr nil) nil), for instance. Of course, (car 'sym) errors. ::shrug:: I'm not sure about this, and it might change.

-----

3 points by rkts 6165 days ago | link

The purpose of a type system is to help find errors by failing early instead of letting bugs propagate through a system and show up in mysterious ways. Right now, you're actually making debugging harder because you're just wrapping bugs inside other bugs. Instead of figuring out why his binary tree turned into a cheeseburger, a programmer now has to determine (a) why his binary tree is nil (or why mutating it seems to have no effect), (b) which invariant he violated, and (c) what error caused the invariant to be violated.

Regarding (car nil) and (cdr nil), these return nil because it's sometimes convenient. For instance, here's a function that collects every other item in a list:

  (def foo (xs)
    (if xs
      (cons car.xs (foo cddr.xs))))
If (cdr nil) raised an error, we would have to check the cdr before we could recur:

  (def foo (xs)
    (if xs
      (cons car.xs (if cdr.xs (foo cddr.xs)))))
Not a huge difference, obviously, but a difference nevertheless. I can't see any comparable reason to return nil in your case.

-----

1 point by absz 6165 days ago | link

Fair enough, you've convinced me.

EDIT: Creation, reading (defcall), and writing (sref) all give errors if invariants are violated/nonexistent keys are accessed. Is there anything I missed?

-----

1 point by absz 6167 days ago | link | parent | on: Tagged unions on Anarki

Update: I (think I)[1] added rkts's suggestion of "solitary" tagged unions/variants, which act very similarly to plain structures except with the availability of a vcase macro (very much like tcase). They are defined with

  (vtype name (arg1 pred1) arg2 ... (argN predN))
, where an argument without a predicate is implicitly (arg object) (you can't do this in tcase without extra parentheses, though). vcase works similarly to tcase:

  (vcase var
    name1 (expr1)
    name2 (expr2)
    ...
    nameN (exprN)
          (optional-error-condition))
Note that unlike in tcase, the names aren't connected, but are merely defined by whichever vcase (or a tcase with a variant named the same thing as the union, which is what a vcase type is).

This required fixing a bug where you couldn't have a variant with the same name as its tagged union (because you then had (annotate NAME (annotate NAME ...)), and that is equivalent to (annotate NAME ...)). This means that you can do that now, but that *(~isa (rep tu) 'variant-type)&.

[1]: That is, I added something which I think is what rkts suggested; rkts will probably disabuse me of this notion rather quickly if I got something wrong :)

-----

1 point by almkglor 6167 days ago | link

IIRC (annotate 'foo (annotate 'foo obj)) is different from (annotate 'foo obj); each annotate adds another layer of (rep ...) to peel off.

http://arclanguage.com/item?id=3692

-----

2 points by absz 6167 days ago | link

Yes, but. That only holds if in (annotate t1 (annotate t2 data)), we have (isnt t1 t2) (there was a thread about this somewhere, but I can't find it). Try it!

  arc> (annotate 'foo (annotate 'bar obj))
  #3(tagged foo #3(tagged bar #3(tagged mac #<procedure>)))
  arc> (annotate 'foo obj)
  #3(tagged foo #3(tagged mac #<procedure>))
  arc> (annotate 'foo (annotate 'foo obj))
  #3(tagged foo #3(tagged mac #<procedure>))
I was surprised too; that's why this was a bug I had to fix. I suppose this behaviour makes sense, but it does break the second of the two identities

  (is (type (annotate t r)) t)
  (is (rep  (annotate t r)) r)
for certain values of r. Ah well.

-----

1 point by almkglor 6167 days ago | link

Huh. Have you tried it on PG's ArcN? It might have been me hacking this onto Anarki (I have multiple personalities. No, don't believe what I just said, that wasn't me. LOL). Don't have access to an arc right now, sorry ^_^

-----

2 points by almkglor 6166 days ago | link

Ah, it seems you're right ^^ From canonical arc:

  (define (ar-tag type rep)
    (cond ((eqv? (ar-type rep) type) rep)
          (#t (vector 'tagged type rep))))

  (xdef 'annotate ar-tag)

-----

1 point by absz 6167 days ago | link | parent | on: Tagged unions on Anarki

I remember that discussion; in fact, I had a response, http://arclanguage.org/item?id=3518, in which I proposed this (and I might have been working on this at the time).

The clearest advantage of distinct types is in things like binary trees:

  (tunion binary-tree
    branch (left  binary-tree?)
           (right binary-tree?)
    leaf   (value object))
If you just had

  (vtype branch left right)
  (vtype leaf   value)
you can't quickly tell what's a binary tree and what isn't in the same way, and you lose the invariant that a branch's left and right are both binary trees. At the same time, something more like vtype would remove

  (tunion my-type
    my-type (foo pred)
            (bar int?))
. Actually, if you had something like

  (mac vtype (type . arguments)
    `(tunion ,type ,type ,@arguments))
you could do this:

  (vtype variant1 (x pred))
  (vtype variant2 (a int?) (b object))
  
  (tcase var
    (variant1
      variant1 (do-something var))
    (variant2
      variant2 (do-something-else var))
    else
      (fail))
And you should be able to write a macro that will convert something nicer to that form, e.g.

  (vcase var
    variant1 (do-something var)
    variant2 (do-something-else var)
             (fail))
. I'll work on that and push it to Anarki.

I'm not convinced on the auto-binding; I implemented it that way because the resulting syntax was nice. It might make sense to have an option to work without auto-binding of names, but maybe it won't actually be an issue in practice. car and cdr are a bit atypical because (I hope) you won't be redefining a list type :)

-----

1 point by absz 6167 days ago | link | parent | on: Tagged unions on Anarki

For use cases: see Haskell code? :P I wrote this because I really liked them in other languages and in EOPL, and thought they might come in handy. If I come across somewhere in my Arc code where I use them (I haven't written too much new Arc recently) I'll report out.

I also wrote most of this a while ago (I think I started in March), and my coding has improved quite a bit since then, so the implementation could probably get cleaned up... :)

-----

2 points by almkglor 6167 days ago | link

> For use cases: see Haskell code? :P

LOL. The 'maybe type looks awfully familiar... ^^

> If I come across somewhere in my Arc code where I use them (I haven't written too much new Arc recently) I'll report out.

Please do ^^. As an aside, it may be possible to transform the AST data structures in arc2c to tagged unions; we could transform the (if (alam ast) ... (alit ast) ...) to tcase forms. Care to try? It strikes me as a possible demonstration of your code, and may be useful to shake out bugs.

-----

1 point by absz 6167 days ago | link

I like maybe :) Also, it's simple and good for testing (e.g. it has a zero-ary type).

The arc2c thing sounds sensible---I have to fix a bug in tagged-union.arc first (you can't currently have a constructor with the same name as the datatype), but then I'll try to work on it.

-----

2 points by almkglor 6167 days ago | link

Well, in this case the data type would be ast, and the constructors would be lam, lit, quote, etc.

-----

2 points by absz 6167 days ago | link

Right---there was something similar in EOPL, and I have something similar in a toy Lisp interpreter (in Haskell) I'm working on.

-----

More