Arc Forumnew | comments | leaders | submitlogin
3 points by nlavine 5961 days ago | link | parent

It seems to me that Arc provides a solution to this problem in the fact that you can call any data object as a function, and define what it means to do so. What we're really getting at, though, is a theoretical issue with what a function is.

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. A slightly cooler implementation could provide arbitrary predicates by having a list of (predicate, implementation) pairs. When it was called, it would iterate down until it found a predicate that matched the arguments, and then call that implementation. You could make either of these pretty trivially.

However, this gets at the idea of what a function really is. A theoretical "pure lambda" is something you can apply to other objects and maybe get a result from. I think we need these in the language, to support primitive types and a foreign function interface.

When you talk about chaining, however, you're thinking of something like a linked list of these things, and you want to manipulate the structure of the list. That's already getting away from the pure lambda idea. My ideas get farther away, and at the risk of looking foolish, I'm going to claim that anything that solves your problem also includes a way to get inside a function object and mess with its internals. The question then is, what should a function be? What should you be able to do to one? Here's a short list of what we've talked about:

  ; f is an extended function object.
  ; the following should work.
  (apply f ...) ; call f on some args
  (redef f (argtyps) ...) ; redefine the behavior of f on some argument types, or add a new implementation if one isn't already there.
  (extended-function
     argtyps imp
     ...) ; takes pairs of argtypes and implementations, and makes an extended function object.
You also need the primitives still, so you'll want this:

  ; f is some sort of object.
  (callable? f) ; see if f is any sort of callable
  (primitive-procedure? f) ; see if f is a pure lambda. this might be a bad name for this function.
There would be primitive constructors for assembly code and other languages.

  (asm ...) ; takes asm, returns a primitive-procedure
  (from-shared-object file name) ; loads a primitive from a library. this should probably be renamed too
  (from-c-header header name impfile) ; loads a primitive, but deals with types nicely by reading a C header file that defines this.
Is there anything else?


3 points by almkglor 5961 days ago | link

> 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 5961 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 5957 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.

-----