Arc Forumnew | comments | leaders | submit | almkglor's commentslogin
5 points by almkglor 6340 days ago | link | parent | on: `(a `(b ,@(c ,x)))

It's a bug. Probably comes from the fact that Arc's variables are prepended by _ (for Arc2) or __ (Anarki).

  arc> an-unknown-variable
  Error: "reference to undefined identifier: __an-unknown-variable"
                                             ^
                                             |
                                         This one
Scheme-side 'ac converts quasiquotes by scanning for escaped expressions and converting symbols in those expressions to __, but apparently there's a bug here

-----


> The source of this problem is not only in chaining itself: redefining standard functions will almost always lead to problems when distinct libraries redefine the same function.

But this should be supported. For instance, in C++, you can redefine the "standard function" operator+ :

  Foo something(Foo a, Foo b){
      return a + b;
  }
Or in Ruby, you can redefine the "standard function" each:

  a = Foo.new()
  a.each{ |i|
    puts i
  }
C++ does it by strict static typing, while Ruby does it by attaching to the object. Neither way feels very Arcish; what is a better Arc solution?

> Were you thinking about a multimethod dispatch such that of CLOS?

Possibly, if we can hack this into Arc somehow.

-----

3 points by stefano 6340 days ago | link

The CLOS solution is pretty similar to the Ruby solution: it checks the type passed to the function and dispatch to the correct function. The big difference is that CLOS looks at all the arguments, while Ruby only at the first argument (the object). The CLOS approach seems to me the best to solve the problem, but I think that applying it to functions as basic as car would kill performance without a really good optimizer.

-----

3 points by almkglor 6340 days ago | link

> The CLOS solution is pretty similar to the Ruby solution: it checks the type passed to the function and dispatch to the correct function. The big difference is that CLOS looks at all the arguments, while Ruby only at the first argument (the object)

IMO the difference is big enough for CLOS Way != Ruby Way. ^^

Still, I wonder - how does CLOS implement this? How about for variadic functions?

> but I think that applying it to functions as basic as car would kill performance without a really good optimizer.

Hmm. I've been trying to grok through dynamic dispatching speedup techniques - the Sun Self papers are pretty good, Sun's index doesn't have the papers themselves but you can ask Google for them.

-----

2 points by stefano 6339 days ago | link

> Still, I wonder - how does CLOS implement this?

I think that for every method with the same name an hash table indexed on the types of the argument is created, and when the method is called, a lookup is made on the table.

> Hmm. I've been trying to grok through dynamic dispatching speedup techniques - the Sun Self papers are pretty good, Sun's index doesn't have the papers themselves but you can ask Google for them.

I've found those papers in the past (a few weeks ago), but I've never found the will to read them, they are written for people already in the compilers' world and I'm still learning the basics about compilers. BTW, a good type inferencer + a good function inliner could solve the problem, but I wouldn't know even where to start to implement them :(.

-----

2 points by almkglor 6338 days ago | link

The gist of the papers are mostly this:

Use a "Polymorphic Inline Cache" (PIC). Basically if a piece of code could call several different methods, we figure out which one it is, then we create a copy of the calling function which does the type checking at the top and specialize all method calls to that type:

  (defm method ((t n my-type))
    (my-type::method n))
  (defm method ((t n your-type))
    (your-type::method n))
  (defm other-meth ((t n my-type))
    (my-type::other-meth n))
  (defm other-meth ((t n your-type))
    (your-type::other-meth n))

  (def general-function (n)
    (+ (method n) (other-meth n)))

  (general-function (your-type-creator 42))
  ===>
    (do
      (def general-function (n)
        (if (isa n 'your-type)
          (+ (your-type::method n) (your-type::other-meth n))
          (+ (method n) (other-meth n))))
      (general-function (your-type-creator 42)))
Everything else in the papers that go beyond the PIC is mostly about debugging and making the PIC lazy.

Edit:

Okay, I've been thinking. Basically the call* table is about specializing on 'apply, and we might think of 'defcall as:

  (defcall type (val . params)
    (code))
  ==>
  (defm apply ((t val type) . params)
    (code))
Could we possibly generalize this at the language level and make a PIC, say in arc2c/SNAP?

-----

1 point by stefano 6338 days ago | link

To do the optimization when we see

  (general-function (your-type-creator 42))
we need a type inferencer to discover the return type of your-type-creator. The cache should also be able to change. For example I could write:

(general-function (your-type-creator 42))

(set your-type-creator another-type-creator)

(general-function (your-type-creator 42))

Now the optimization doesn't work if the cache doesn't change. This seems a rare case, though, and it's useless to optimize rare cases.

-----

1 point by almkglor 6337 days ago | link

Actually we don't: general-function is actually defined this way:

  (with (PIC (table) ;init empty table
         num-calls 0
         orig-fn
         (fn (n)
           (+ (method n) (other-meth n))))
    (def general-function (n)
      ((aif
         ; don't infer type: just look at the runtime type
         (PIC:type n)
            it
         (is optimization-trigger-level** (++ num-calls))
            (do (= num-calls 0)
                (= (PIC:type n)
                   (optimize* orig-fn (type n))))
            orig-fn)
        n)))
Basically s/type inference/just look at it directly/

-----

1 point by eds 6339 days ago | link

> Still, I wonder - how does CLOS implement this? How about for variadic functions?

I don't think CLOS lets you check types on &rest, &optional, or &key parameters. So you couldn't use CLOS for the current behavior of '+.

Also note that CLOS only works on methods with "congruent lambda lists", that is, methods with the same number of required, optional, and keyword arguments. So you can't have

  (defmethod foo ((a type-a)) ...)
  (defmethod foo ((b type-b) &optional (c ...)) ...)

-----

1 point by almkglor 6338 days ago | link

ah, I see. So I suppose this greatly simplifies things then.

Hmm. This certainly seems easier to hack into arc. We could have each method start with a generic function whose lambda list we have to match.

As an aside, currently the base of Arc lambda lists are simply &rest parameters (i.e. optional parameters are converted into rest parameters with destructuring). Should we match on the plain rest parameters or should we properly support optionals?

-----


> The easiest solution to your problem might be a callable table whose keys are the types of arguments and whose values are different function implementation.

Yes. But there's a problem: how do you define the table for, say, the variadic function '+ ?

Your solution(s) are approximately what I had in mind, although I think I found some problems with it last night, and I just woke up and can't remember quite yet (brain is still booting or something).

-----

3 points by almkglor 6340 days ago | link

Okay, brain is up and running.

One way to do this is to use settable-fn.arc and have some sort of attachment for a table of argument signatures.

HOWEVER, we will need to formalize on what part of the argument signature to dispatch from.

For example we can consider nex3's 'defm syntax:

  (defm foo ((t arg type) (t arg2 type2))
     ...)
But then, what about optional parameters?

  (defm foo (arg (o arg something))
    ...)
Also: consider the case of 'coerce . It would be trivial to convert from our type to a target type:

  (defm coerce ((t arg our-type) target)
    (case target
      int    (our-type-to-int arg)
      string (our-type-to-string arg)
      ...))
However, how about when we want to convert to our type? How do we express (coerce (string something) 'our-type) ?

-----

3 points by almkglor 6337 days ago | link

Okay, here's the solution I've been thinking of.

  (def coerce (obj typ . rest)
    (apply <base>coerce obj (annotate typ nil) rest))

  (defm <base>coerce ((t obj my-type) (t _ string))
    (convert-my-type-to-string obj))

                                     ; edit: corrected type
  (defm <base>coerce ((t obj string) (t _ my-type))
    (convert-string-to-my-type obj))
Also, for variadic functions:

  (def + rest
    (reduce <base>+ rest))

  (defm <base>+ ((t x string) (t y string))
    (join x y))
Further, optional parameters are simply ignored and considered as part of the rest parameter (i.e. they can't be typed). Basically, the typesystem matches on the first N parameters, where N is how many type parameters you care to define.

Why the first N parameters? So that we can protect against optional parameters defaulting to values that are inappropriate for other types, such as the 'scanner example I gave above.

-----


LOL. Using the arc-side (require "ssyntaxes.arc") solves this, but also completely suppresses #<eof> in all cases.

Edit: Hmm. Should probably mean some fixing in the scheme-side to fix up the ssyntax and ssexpand functions.

-----

1 point by almkglor 6347 days ago | link | parent | on: Tagged unions on Anarki

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 6347 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 6347 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 6347 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) No, you should implement in the math in the underlying machine instructions, which are guaranteed to be as precise and as fast as the manufacturer can make it. The underlying machine instructions are fortunately possible to access in standard C libraries, and the standard C library functions are wrapped by mzscheme, which we then import in arc.

2) It should be, and it isn't.

  (defmemo fac (n)
    ((afn (n a)
       (if (> n 1)
           (self (- n 1) (* a n))
           a))
     n 1))
3) Yes, arc-on-mzscheme handles this automagically. arc2c does not (I think it'll overflow)

-----

3 points by kens 6347 days ago | link

Implementing numerically stable and accurate transcendental functions is rather difficult. If you're going down that road, please don't just use Taylor series, but look up good algorithms that others have implemented. One source is http://developer.intel.com/technology/itj/q41999/pdf/transen...

That said, I don't see much value in re-implementing math libraries in Arc, given that Arc is almost certainly going to be running on a platform that already has good native math libraries.

-----

1 point by shader 6347 days ago | link

I figured that being close to machine instructions was a good thing, but I thought that we should do that via some other method, not necessarily scheme, which may or may not remain the base of arc in the future.

That being said, if you think that pulling from scheme is a good idea, why don't we just pull all of the other math functions from there as well?

-----

2 points by almkglor 6347 days ago | link

> That being said, if you think that pulling from scheme is a good idea, why don't we just pull all of the other math functions from there as well?

Yes. Yes it is. http://arclanguage.com/item?id=7288

That's what I said ^^

-----

1 point by shader 6346 days ago | link

Ok, I added that tail optimized version to math.arc.

Do you want to have a separate math libs for the scheme functions and native implementations? You already suggested the possibility.

-----

2 points by almkglor 6346 days ago | link

Err, "native implementations" being?

Actually I think it might be better if we had a spec which says "A Good Arc Implementation (TM) includes the following functions when you (require "lib/math.arc"): ...." Then the programmer doesn't even have to care about "scheme functions" or "java functions" or "c functions" or "machine language functions" or "SKI functions" - the implementation imports it by whatever means it wants.

Maybe also spec that the implementation can reserve the plain '$ for implementation-specific stuff.

-----

1 point by almkglor 6348 days ago | link | parent | on: Tagged unions on Anarki

Looks good, although probably needs more examples, maybe some actual use cases.

On the implementation side... I notice you use (apply list 'do ...). I suspect you can probably make it a very big `(do ...) form, although you may have felt that it was getting bit complicated I guess ^^;.

-----

1 point by absz 6348 days ago | link

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 6347 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 6347 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 6347 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 6347 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.

-----


(def foo ...) is a macro that means:

  (= foo (fn ...))
This means that:

    (def accumulate (combiner null-value term a next b)
        (def iter (a result)
    ...
'iter here is the global iter.

What you want to do is:

    (def accumulate (combiner null-value term a next b)
        (let iter (fn (a result)
            ....

-----

3 points by ambition 6348 days ago | link

I looked into this some more, and it looks like this is a difference between arc and scheme. I just wanted to put this out there for anyone else looking at Scheme materials through an Arc lens.

Arc:

    arc> (def fn2() (def i() (prn "fn2-i")) (i))
    #<procedure: fn2>
    arc> (fn2)
    fn2-i
    "fn2-i"
    arc> (def fn1() (def i() (prn-"fn1-i")) (fn2) (i))
    #<procedure: fn1>
    arc> (fn1)
    *** redefining i
    *** redefining i
    fn2-i
    fn2-i
    "fn2-i"
    
Scheme:

    > (define (fn2) (define (i) (print "fn2-i")) (i))
    > (fn2)
       "fn2-i"> (define (fn1) (define (i) (print "fn1-i")) (fn2) (i)) 
    > (fn1)
       "fn2-i""fn1-i">

-----

1 point by ambition 6348 days ago | link

Thanks!

-----


The series of "create your own collection":

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

-----

6 points by almkglor 6348 days ago | link | parent | on: Web pages on demand

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

-----

1 point by mekazu 6348 days ago | link

thanks

-----

More