Arc Forumnew | comments | leaders | submit | akkartik's commentslogin
3 points by akkartik 3304 days ago | link | parent | on: A keyword param implementation

Just a quick note before I take a deeper look this weekend: I copied the ':arg' syntax from Common Lisp. Racket tried to follow that as well, though they've added their own tweaks to it. Smalltalk and Objective-C have a trailing colon. Needless to say, you shouldn't feel constrained by any of these approaches.

-----

3 points by akkartik 3305 days ago | link | parent | on: Keyword args

As it happens, keyword args were one of my earliest bunny trails, and I obsessed over them for probably five years.

Here's my earliest post about keyword args: http://arclanguage.org/item?id=10692. (Follow-up much later: http://arclanguage.org/item?id=13083)

That got me working on a fork of Arc with keyword args support: https://github.com/akkartik/arc. Original discussion about it: http://arclanguage.org/item?id=12657.

That eventually spiraled out into a whole language with easy keyword args at its core: http://akkartik.name/post/wart. Milestones along the way: http://arclanguage.org/item?id=12814; http://arclanguage.org/item?id=12943; http://arclanguage.org/item?id=15180.

Supporting keyword arguments also led me to param aliases: http://arclanguage.org/item?id=15254. Then to Haskell's as-params: http://arclanguage.org/item?id=16970. Then to multi-word functions, kinda inspired by Objective-C's keyword arguments: http://arclanguage.org/item?id=17233.

Finally -- 3.25 years after I started Wart -- I found a foundational bug in it: http://arclanguage.org/item?id=18371. I eventually fixed it, but the fact that it lay unnoticed for so long killed my motivation to continue supporting keyword arguments. At some point along the way it's become a higher priority to make code testable, and if you have thorough tests then how well some tiny slice of code reads seems a lot lower priority given how complex I found it to create a 'closure' of mechanisms that work seamlessly together and don't have any special-cases: http://akkartik.name/post/readable-bad. So these days I try to ignore keyword args lest my madness return :)

-----

2 points by akkartik 3306 days ago | link | parent | on: Labels macro in arc

I could swear we discussed this before, and that I had picked out a solution to my satisfaction, but I can't for the life of me recall it. Hopefully someone else will. Yours is certainly a contender.

It's not clear how your macro works, though. When I expand the above call to labels I get this:

  (with (g20467 (fn nil nil)
         g20468 (fn nil nil))
    (with (collatz (fn a (apply g20467 a))
           worker  (fn a (apply g20468 a)))
      (with (collatz (fn (n) (if (even n) (/ n 2) (+ (* n 3) 1)))
             worker  (fn (n seq)
                       (if (is n 1)
                         (cons n seq)
                         (worker (collatz n) (cons n seq)))))
        (do (= g20467 collatz) (= g20468 worker))
        (rev (worker n 'nil)))))
Is there a reason you need two nested with forms defining 'collatz and 'worker?

-----

3 points by mpr 3306 days ago | link

I think the double assignment of the names given to the labels macro is needed in the case that the functions call each other. Take this example:

    (labels ((even (n)
               (if (is n 0)
                 'even
                 (odd (- n 1))))
             (odd (n)
               (if (is n 0)
                 'odd
                 (even (- n 1)))))
      (even 5))
The macroexpansion of this labels call looks like this

    ((fn (g3947 g3948)
      (with (even (fn a (apply g3947 a))
             odd  (fn a (apply g3948 a)))
        (with (even (fn (n) (if (is n 0) (quote even) (odd (- n 1))))
               odd  (fn (n) (if (is n 0) (quote odd) (even (- n 1)))))
          (do
            (= g3947 even)
            (= g3948 odd))
          (even 5))))
     (fn nil nil) (fn nil nil))
If I add a prn to see what we get, we can run the code and see that it works as expected.

    (prn ((fn (g3947 g3948)
            (with (even (fn a (apply g3947 a))
                   odd  (fn a (apply g3948 a)))
              (with (even (fn (n) (if (is n 0) (quote even) (odd (- n 1))))
                     odd  (fn (n) (if (is n 0) (quote odd) (even (- n 1)))))
                (do
                  (= g3947 even)
                  (= g3948 odd))
                (even 5))))
          (fn nil nil) (fn nil nil)))

    ;; odd
Now I will remove the outer with and run the same code, which returns nil.

    (prn ((fn (g3947 g3948)
            (with (even (fn (n) (if (is n 0) (quote even) (odd (- n 1))))
                   odd  (fn (n) (if (is n 0) (quote odd) (even (- n 1)))))
              (do
                (= g3947 even)
                (= g3948 odd))
              (even 5)))
          (fn nil nil) (fn nil nil)))

    ;; nil
I can't explain this in greater detail; I haven't traced the full execution. But given the evidence I believe mutual recursion makes double assignment necessary.

-----

4 points by akkartik 3306 days ago | link

You could macroexpand to this, though:

  (with (even nil odd nil)
    (= even (fn (n)
              (if (is n 0)
                'even
                (odd (- n 1))))
       odd  (fn (n)
              (if (is n 0)
                'odd
                (even (- n 1)))))
    (even 5))

-----

3 points by mpr 3305 days ago | link

Yes, this simpler macroexpansion is all that's needed, apparently. When I first ported the code above I didn't have an appreciation for what labels needed to do; I just ported it. Thanks for making me think about it a little harder. Here is an implementation that will macroexpand to only one with. I've included a sample macroexpansion, as well as the results of running two functions. One of them, (collatz-seq), uses only simple recursion. The other, (parity), uses mutual recursion.

    (mac labels (fns . forms)
      (with (fnames (map car fns)
             fbodies (map (fn (f) `(fn ,@(cdr f))) fns))
        `(with ,(mappend (fn (name) `(,name nil)) fnames)
           (= ,@(mappend (fn (f) `(,(car f) ,@(cdr f))) 
                         (zip fnames fbodies)))
           ,@forms)))

    (def collatz-seq (n)
      (labels ((collatz (n)
                 (if (even n)
                   (/ n 2)
                   (+ (* n 3) 1)))
               (worker (n seq)
                 (if (is n 1)
                   (cons n seq)
                   (worker (collatz n) (cons n seq)))))
              (rev (worker n '()))))

    (def parity (n)
      (labels ((even (n)
                 (if (is n 0)
                   'even
                   (odd (- n 1))))
               (odd (n)
                 (if (is n 0)
                   'odd
                   (even (- n 1)))))
              (even n)))

    (prn (macex1 '(labels ((even (n)
                             (if (is n 0)
                               'even
                               (odd (- n 1))))
                           (odd (n)
                             (if (is n 0)
                               'odd
                               (even (- n 1)))))
                          (even n))))

    (prn (parity 17))
    (prn (parity 24))

    (prn (collatz-seq 21))

    ;; ---------------------------------------------

    ;; (with (even nil odd nil)
    ;;   (= even (fn (n)
    ;;             (if (is n 0)
    ;;               (quote even)
    ;;               (odd (- n 1))))
    ;;      odd (fn (n)
    ;;            (if (is n 0)
    ;;              (quote odd)
    ;;              (even (- n 1)))))
    ;;   (even n))
    ;; 
    ;; odd
    ;; even
    ;; (21 64 32 16 8 4 2 1)

-----

3 points by akkartik 3305 days ago | link

That's very interesting analysis. I'm curious to see where you got the original Common Lisp version from. Is there some reason CL requires two withs?

-----

3 points by mpr 3305 days ago | link

Got the CL version from here http://www.pipeline.com/~hbaker1/MetaCircular.html. Someone on ##lisp IRC linked me when I was asking about how to write labels. Since I'm curious too, I'll do a similar analysis on CL and post the results.

-----

3 points by mpr 3305 days ago | link

Good point. Then I don't see any need for the double with.

-----

4 points by akkartik 3305 days ago | link

By the way, I was sad that you stopped submitting Tcl links after the one :) Don't be discouraged that there was no discussion on it. Sometimes I can't think of anything to say at the moment, but I still enjoy the link.

-----

4 points by mpr 3305 days ago | link

Hah! Yes, I was a little discouraged. Thanks for letting me know! I have some stuff in mind, expect to see more soon :)

-----

3 points by akkartik 3305 days ago | link

Since I'm on a roll here with my speculatin': also don't correlate how a post is received with how long it takes to get comments. I just didn't see your Y-combinator post for the 8 days before I responded. Which is a complete outlier for me on this Forum and hopefully not a harbinger of things to come.. :/

-----

3 points by akkartik 3319 days ago | link | parent | on: The History and Spirit of C and C++

This may not seem relevant to Lisp, but bear with the talk. It has some very interesting insights on the tension between high- and low-level languages, the interplay between providing features and keeping the implementation tractable, that are relevant to any programming language designer.

-----

4 points by akkartik 3320 days ago | link | parent | on: Implicit importing

"Defining a function with the same name as an old one overrides the old one's behavior. So yeah, you either hope nothing collides or you prefix it somehow. This is less of an issue in Arc than it is in Emacs both because fewer functions come in the box, and because fewer libraries are pulled in. But certainly collisions can/will happen."

Just to add to this, and at the risk of boring the veterans here to death by repeating myself :)

Reading between the lines of the implementation, I think the Arc way of dealing with the possibility of collisions is to not worry about them until they happen. When they happen Arc will show you a warning:

  *** redefining foo
And it's up to the programmer to manually decide how to fix the collision.

This makes a lot more sense than adding prefixes because the names chosen are in the context of the application being built rather than some one-size-fits-all name chosen by a library. That makes them likely to be better names in the context of the application.

Avoiding prefixes also keeps most names from becoming unnecessarily long when they don't need to be.

It would be tedious to have to do this if you have a lot of collisions. But that's a good source of pressure to keep the codebase small and minimal and not pull in a lot of libraries, as people are wont to do in Ruby or Node. Any sort of namespacing mechanism encourages programmers to take less responsibility for their code.

-----

4 points by digitalis_ 3320 days ago | link

[There doesn't seem to be a reply button under your last reply -- we've possibly hit a maximum depth -- so I'm replying here. Sorry!]

I'm talking about "a namespace mechanism" here, without actually knowing how one in Arc would work.

Just thinking about it for a moment, I think you'd set the prefix

  (= namespace-prefix 'awesome-package)
and then there'd be some symbol (in the textual sense) that you'd put in front of all the library functions; maybe a slash.

  (def /awesome-function (...)
    (...))
Or, instead of setting the namespace with a variable, you'd have

  (w/namespace awesome-package
    ...
    (def /awesome-function (...)
      (...))
    ...)
[I think the latter's probably better -- it's clearer where the namespace ends.]

In the end, you have to have some way of avoiding conflicts -- and all of these boil down to tacking something on the front (math.sqrt in python, for instance); I (personally) would rather have a better way of doing this than manually typing out `awesome-package` at the start of every function/macro/variable/whatever.

(Though we may eventually have to "agree to disagree" on this, I think the discussion's worth it!)

-----

4 points by akkartik 3319 days ago | link

Yup!

The trick for replying deep in discussions: click on the 'link' of the comment you want to reply to. news.arc hides links to replies below some depth for some period of time to discourage flame wars. You can see the code for this at https://github.com/arclanguage/anarki/blob/15481f843d/lib/ne...

-----

2 points by digitalis_ 3319 days ago | link

Thank you for the enlightenment! (I promise not to abuse this power.)

-----

4 points by digitalis_ 3320 days ago | link

Interesting...

I think, given that Arc is supposed to be "a language for good programmers" [1], that it's silly to impose restrictions like withholding a namespacing mechanism to encourage a certain type of programming.

Though I like keeping codebases minimal, I also like freedom.

[1] http://paulgraham.com/design.html

-----

3 points by akkartik 3320 days ago | link

To my mind it is namespaces that add restrictions. Arc's warning about redefining names can be ignored, but a namespace mechanism is usually more insistent about violations.

I try not to use words like 'freedom' in these discussions because navigating by them is a subtle business. While having a feature can sometimes provide freedom, in general I tend to assume that features cost freedom. Like the saying that your possessions own you. A namespace mechanism is more code to write, more places for bugs to be hiding in, more errors for the user to run into, more places for one programmer to hide things where they can trip another programmer up. All these issues are places for degrees of freedom to be used up rather than created.

So yes, we are absolutely in agreement that Arc is for good/experienced programmers. But there are multiple visions out there for how you go about helping experienced programmers. My vision is admittedly unconventional, a minority view. It's possible that Arc just doesn't have namespaces because the authors haven't gotten around to implementing them yet. But somehow I doubt that.

-----

2 points by Pauan 3303 days ago | link

Actually, because Node.js has a proper module/namespace system, people are instead encouraged to create a lot of micro libraries (e.g. less than 200 lines of code).

So rather than importing a single big library in your application, you would instead import a lot of micro libraries (each of which is versioned separately, and might have their own dependencies).

There are pros and cons to big libraries, and pros and cons to little libraries. I think languages should have good support for both.

-----

1 point by akkartik 3303 days ago | link

Yes, I love the trend towards micro libraries because they encourage people to pull in precisely the functionality they need. That aspect of Node is not part of the problem.

-----

2 points by Oscar-Belletti 3319 days ago | link

I agree that prefixes are not a good solution. And namespaces is more code to write both in app's code and in the arc implementation code.

However, if you use two libraries which make a collision, what do you do?

-----

3 points by akkartik 3319 days ago | link

In principle I don't have a fully general solution yet :/ It's something I'm working on.

What I would like to happen is that my application only contains dependencies it really needs, and that each dependency includes no superfluous/dead interfaces or code. Under these circumstances I would like to live in a world where I can go in and modify the libraries to have different names, with the difference in names making sense in the context of the application. Then I would bundle the application with all its libraries included.

Of course this doesn't scale to large libraries, because managing a fork today involves an amount of work that ranges from non-trivial to intractable. But this would be my ideal.

Past writings on this subject: http://akkartik.name/post/libraries; http://akkartik.name/post/libraries2. My current project which tries to make fork-management tractable: https://github.com/akkartik/mu

Bear in mind that it's only a hard problem for collisions in the interface of the two libraries. Functions that are used only internally can be wrapped inside closures so they're only accessible to the library that cares about them.

-----

3 points by rocketnia 3319 days ago | link

I've been noticing continuities between social code distribution, modularity, and variable scope. A guiding example is code verification:

  Unrecorded reasoning, existing mainly in our minds.
  -->
  Codebases dedicated to proofs or tests.
  -->
  Proofs or tests located in the codebase they apply to.
  -->
  A type/contract declaring a module interface.
  -->
  A type/contract annotation for a function definition.
  -->
  A type/contract annotation for an individual expression.
  -->
  A type/contract annotation for an individual built-in operator, but at
  this point it becomes implicit in the operator itself, and we just
  have structured programming, enjoying properties by construction.
Verification is a simplified version of a build process; it's just a build with a yes or no answer. So the design of a build system has similar continuity:

  Unrecorded how-to knowledge, existing mainly in our minds.
  -->
  Codebases or how-to guides dedicated to curated builds (e.g. distros).
  -->
  Build scripts and docs located in the codebase they apply to.
  -->
  Macroexpansion-time glue code, importing compiler extensions by name.
  -->
  Load-time glue code, importing runtime extensions by name.
  -->
  Service-startup-time glue code, obtaining dependency-injected fields
  by name.
  -->
  An expression, taking free variables from its lexical scope by name.
  (This is a build at "evaluation of this particular expression" time.)
There might be some rough parts in here. I might be taking things for granted that I don't want to, like taking for granted that we want unambiguous named references from one module to another. My point with this continuity is to note that if I don't want named imports, then maybe I don't want named local variables either; maybe tweaks to one design should apply to the other.

And this means that even local syntactic concerns extrapolate to social decisions about how we expect to deal with our unrecorded knowledge. Every design decision has a lot to go by. :)

---

Another exciting part is that I think nested quasiquotation shows us a more general theory of lexical locality. If we're dealing with syntax as text, then locations in that text have an order, and we can isolate code snippets at intervals along that order (and mark them with parentheses). Intervals are partially ordered by containment, so we can isolate code snippets at meta-intervals between an outer interval and multiple nonoverlapping inner intervals (and mark them with parentheses with nonoverlapping parentheses-shaped holes: quasiquotations).

That "nonoverlapping" part seems awkward, but I think there's a simple concept somewhere in here.

With this concept of intervals, I'm considering higher degrees of lexical structure past quasiquotation, and I'm considering what kind of parentheses or quasiquotations would exist for non-textual syntaxes.

A module system deals with a non-textual syntax: The syntax of a bundle of modules. If the modules have no order to them, then we don't even have parentheses to work with, let alone quasiquotation. But they can have an order to them. We can impose one from outside:

  Module A precedes module B.
And anything we can impose from outside, we might want to add as a module:

  Module A says, "..."
  Module B says, "..."
  Module C says, "Module A precedes module B."
This is prone to contradictions and ambiguities. If we can say how to resolve these ambiguities from the outside, we should be able to do so as a module:

  Module A says, "..."
  Module B says, "..."
  Module C says, "Module A precedes module B."
  Module D says, "Module B precedes module A."
  Module E says, "If module C and module D disagree, listen to module C."
  Module F says, "If module C and module D disagree, listen to module D."
  Module G says, "If module E and module F disagree, listen to module E."
This should lead to a very complete system of closed-system extensibility: For any given set of modules, if the set's self-proclaimed ordering between A and B is currently unambiguous, then we might as well listen to it! If we don't like it, we can add more contradictions and disambiguations until we do, right up to and including "Ignore all those other modules and do it like this." :)

With this ability to disambiguate when things go wrong, we can model lexical scope:

  Module A says, "Export foo = (import bar from system {B, C})."
  Module B says, "Export foo = 2."
  Module C says, "Export bar = foo + foo."
  
  Result: foo = 4.
While both A and B have an export named "foo," this conflict is disambiguated by the fact that module A is treating {B, C} as a local scope. I intend this to mean that bar isn't at the top level either.

If we really want access to bar at the top level, we can refer to it again, and we can even be sloppy about it and make up for our sloppiness with disambiguations:

  Module A says, "Export foo = (import bar from system {B, C})."
  Module B says, "Export foo = 2."
  Module C says, "Export bar = foo + foo."
  
  Module D says, "Export all imports from system {B, C}."
  Module E says, "If A and D export the same variable, listen to A."
  
  Result: foo = 4; bar = 4.
If we want, we can have the top-level bar see the version of foo exported by A, even though the version of bar used by A still uses the foo from B:

  Module A says, "Export foo = (import bar from system {B, C})."
  Module B says, "Export foo = 2."
  Module C says, "Export bar = foo + foo."
  
  Module D says, "Export all imports from system {A, B, C}."
  Module E says, "Export all imports from system {C, F}."
  Module F says, "Export foo = (import foo from system {A, B, C})."
  Module G says, "If D and E export the same variable, listen to D."
  
  Result: foo = 4; bar = 8.
Not easy enough to extend? Define some structure. Write modules that assign folksonomic tags to other modules or themselves, and then refer to the system of all modules with a given tag. Write modules that act as parentheses, and write modules that determine enough of an order to decide which modules those parentheses contain. Here's an example of the latter:

  Module A says, "Export foo = (import bar from range R1)."
  Module B says, "Export interval R1, and begin it here."
  Module C says, "Export foo = 2."
  Module D says, "Export bar = foo + foo."
  Module E says, "End interval."
  Module F says, "These modules are in order: B, C, D, E."
The flexibility is obviously really open-ended here, and it's going to be a challenge to make this a well-defined idea. :-p

-----

2 points by Oscar-Belletti 3319 days ago | link

>What I would like to happen is that my application only contains dependencies it really needs, and that each dependency includes no superfluous/dead interfaces or code.

Do you want to avoid the situation, wich happens in c, where when you need the sqrt you have to include the whole math file? I totally agree.

>Under these circumstances I would like to live in a world where I can go in and modify the libraries to have different names, with the difference in names making sense in the context of the application

This looks right to me. Perhaps it could be something like python's

    from library import function as good_name_for_your_project
>Then I would bundle the application with all its libraries included.

I'm not sure making a (even not full) copy of a library is a good idea because it would lead the user to have many copies of the same libraries. On my windows machine I ended having 4 versions of python! I think that common parts should be in common.

-----

3 points by akkartik 3319 days ago | link

> Do you want to avoid the situation.. where when you need the sqrt you have to include the whole math file? I totally agree.

Yes, definitely. In the Javascript world it's called tree-shaking: https://medium.com/@Rich_Harris/tree-shaking-versus-dead-cod...

> from library import function as good_name_for_your_project

What's happening here is that you're a) adding a feature in Python to support 'from..as', b) including an external library and c) continuing to keep around an old name that you don't really care about. You're essentially preserving the old name just because other people who your application doesn't care about use it.

Imagine a world where maintaining forks was tractable. Would this still be a good idea? Why not just do a search and replace and maintain a private fork, eliminating all this complexity in your private stack? Just delete 'from..as' from your private Python! :o)

> I'm not sure making a copy of a library is a good idea because it would lead the user to have many copies of the same libraries.

Yes, this is a fundamental difference in outlook/ideology. I think that copying isn't always bad. We culturally tend to emphasize the issues with copying a lot more than the costs of avoiding duplication.

A degenerate example is to observe that there are tons of 'e's in the novel I'm reading and try to deduplicate them. That is of course obviously farcical, but it at least serves to illustrate that there's a trade-off, and that always DRY'ing your code isn't obviously a good idea. Another example is to observe that the internet has many copies of the same libraries running at any given time. You can argue that they're on different machines, but then imagine a 'machine' consisting of multiple cores and private caches and non-uniform memory access and RAID-partitioned disks. Changing latency costs can make it reasonable to maintain multiple copies of some immutable data in a single 'machine'. Now consider that development is yet another cost that is open to variation. If (automatically) creating copies of something eases development, it's at least worth considering. For example, optimizing compilers can sometimes specialize a function differently for different callsites. That's duplication often inside a single binary, and it makes sense in some contexts.

The npm eco-system promiscuously duplicates dependencies inside the node_modules/ directory, so that is at least some evidence that the approach I'm suggesting isn't too insane :)

-----

2 points by Oscar-Belletti 3319 days ago | link

Ok, this maybe could be the way to go. Adapting little libraries isn't a problem, and it probably makes your program better. This defeats collisions, useless code and is ok for autoloading. But this approach will work only if our libraries will be small enough. For now this is ok.

Duplicating libraries isn't a problem: disk space for ease of development is an exchange which is getting more and more convenient.

For autoloading: the interpreter/compiler could load all .arc files in current directory (or current-directory/lib), or scan them for function definitions (without loading them) and making elisp autoload automatically for every function. I prefer the first option.

-----

2 points by digitalis_ 3319 days ago | link

One possibility for this bundling is that Arc looks first where it would expect a library to be (in an equivalent of npm_modules), then looks for it in the usual place (/usr/lib or wherever).

Or, if it all needs to be bundled, you could have symlinks for the libraries you don't change.

What do you think?

-----

2 points by digitalis_ 3319 days ago | link

Thing is...this is about as verbose as you can get!

If a name's already good, you're not going to change it; if it's bad, you should push that change upstream! (If the name's bad, it's likely that the original author didn't put much time into choosing the name, so I think it would be fairly straightforward to get that merged.)

[As much as I love this idea of implicit importing, I'm sure the explicit side -- which'll let you change whatever names you like -- will need to be there as well. So we can all chill.]

-----

3 points by rocketnia 3319 days ago | link

Quality of a name is relative to a purpose. The more public we go, the more meanings compete for a single name, making us resort to jargon. If a language really only uses homogenous intensional equality, being able to call it = is a relief. If someone wants to build a side-by-side comparison of several versions of an extension, they might prefer for some of the names to be different in every version while others stay the same.

But it's not just names per se. In that side-by-side comparison, they might also want to merge and branch parts of the code whose assumed invariants have now changed; invariants can act as Schelling points, like invisible names. Modifying code is something we do sometimes, and I think akkartik wants to see how much simplicity we'll get if everyone who wants a simpler system has the tooling support to modify the code and make it simpler themselves.

Personally, I find it fascinating how to design a language for multiple people to edit the code at the same time, a use case that can singlehandedly justify information hiding, modules, and versioning. But I think existing module systems enforce information hiding even more than they have to, so that in the cases where people do need to invade that hidden information, they face unnecessary difficulties. I think a good module system will support akkartik's way of pursuing simplicity.

But... my module system ideas aren't finished. At a high level:

- You can invade implementation details you already know. You can prove this by having their entire code as a first-class value with the expected hash.

- You can invade implementation details if you can authorize yourself as their author.

-----

2 points by akkartik 3319 days ago | link

"If a name's already good, you're not going to change it; if it's bad, you should push that change upstream! (If the name's bad, it's likely that the original author didn't put much time into choosing the name, so I think it would be fairly straightforward to get that merged.)"

Not necessarily. 'Good' and 'bad' are not absolute, they are extremely contextual. A name that is good for a general-use library might be sub-optimal for your application, or vice versa. Subjective taste is also a thing. So while you should certainly send out a pull request for the change, our model of the world shouldn't rely on the change actually getting pushed.

In general it is amazing to me how often a blindingly obvious Pull Request gets rejected or just sits in the queue, untouched. There's lots of different kinds of people out there. Which is why I tend to think more like a barbarian[1] about collaboration: think of other people as islands with whom you might collaborate if the stars align. But don't rely on the collaboration. Be self-sufficient.

[1] http://www.ribbonfarm.com/2011/03/10/the-return-of-the-barba...

---

"As much as I love this idea of implicit importing, I'm sure the explicit side -- which'll let you change whatever names you like -- will need to be there as well."

I actually interpreted your original post that kicked off this thread as implicit loading since Arc has no notion of modules or import. So the question of changing names did not arise. That seemed like a tangent to the original question.

These seem like separate questions:

1. Should Arc know how to react with implicit symbols?

2. Should Arc provide namespaces?

One the one hand, you can have implicit loading without needing a module/namespace system. On the other hand, I don't see how you can have implicit loading in the presence of namespaces. Without the "from..as" construct how would your system know which library to load a symbol from, if there's a collision?

Summary: even if you have namespaces, you're still going to be doing your own collision-detection if you want implicit loading. What's the point of a module system then?

-----


HN discussion: https://news.ycombinator.com/item?id=919187

-----

3 points by akkartik 3321 days ago | link | parent | on: Implicit importing

Yes, I could get behind this, with one proviso: all the code the system knows about should be under the project. Kinda like npm's node_modules/ directory. With that setup your proposal feels like a ctags for the compiler. Yes, definitely worth doing.

-----

3 points by digitalis_ 3320 days ago | link

Just to clarify: when you say 'all the code the system knows about', do you mean user code and the standard libraries, or just the user code?

From what I can see, you have to install (npm's equivalent of) the standard libraries you want in to the node_modules dir. (So it's not much of a win over the boilerplate import code.)

However, I'm interested in how you'd have implicit importing of user code -- at first glance, all the code under it seems a decent way to go.

-----

2 points by akkartik 3322 days ago | link | parent | on: Y combinator in arc

Interesting idea; can you think of any other uses for it?

FYI, Anarki uses _1, _2, etc. to support anonymous functions with more than one argument. It might get confusing to have both. So which would you choose?

-----

3 points by mpr 3321 days ago | link

Considering it for about a minute, I say bracket functions of multiple args is better to have than nested bracket functions. The multiple args case applies more often I would guess. And the nested case is hard to parse, so taking away the power of the multi arg case to have the nested case doesn't seem worth it.

Edit: that is, hard for humans to parse

-----

2 points by zck 3321 days ago | link

I agree; the nested case is a bit difficult for humans to parse.

I wonder if there's a way to make (fn (x) ...) even more lightweight.

What if, to do the same thing, we had a new anonymous function syntax?

    {x ...}
It would work somewhat like this:

    arc> (let my-fn {arg (string "I got " arg)}
              (my-fn "this thing"))
    "I got this thing"
This is somewhat analogous to "let"

    (let x ...)
It could also allow for multiple arguments, like this:

    {(x y} ...)
Allowing for multiple arguments prevents destructuring-bind style function calls, though, which is a downside. I guess you could use other kinds of brackets for that?

    {{x y} ...};; this would be non-destructuring-bind
    {(x y) ...};; this is destructuring-bind
    {{(x y} ...);;; this is the same as #2; it would destructuring-bind the first argument, a list.

Heck, we could even build it into the existing anonymous function syntax:

    [{x y} ...]
Would be the same as

    (fn (x y) ...)
But I'm not sure that's a big enough win.

-----

3 points by mpr 3321 days ago | link

As per akkartik's comment, we already have multi-argument anonymous functions in anarki:

    ([prn _1 _2] "hello " "there")
But yeah maybe having full lambda list capabilities in the anonymous function would be cool.

    ([((a b) c) (prn c b a)] (list "is" "name ") "my ")
So if the first argument to the bracket function is a list, it acts as a lambda list.

But this interferes with the arc special syntax of having lists act as functions on their indices. Because the following is already valid arc code:

    (let x (list 1 2 3)
      (x 2))
    ;; > 3
In that case having different delimiters might be the way to go. So your brace notation would be used for anonymous functions with full lambda list capability.

    (let x (list 1 2)
      ({((a b) c) (prn c b a)} x 3))
Edit: trying some other formatting...

    (with (f {((a b) c)
               (prn c b a)}
           x (list 1 2))
      (f x 3))

-----

3 points by zck 3320 days ago | link

I was thinking that if you have an even simpler way to bind specific variables in the function call, you can nest these anonymous functions, and not have the arguments shadow each other.

> In that case having different delimiters might be the way to go. So your brace notation would be used for anonymous functions with full lambda list capability.

Yeah, I think you're right. The square-brace anonymous function could have optionally a curly-braced first argument. If that argument isn't there, it works as the square-braced anonymous function currently does. If that argument is there, it's the arguments to that function. Since curly braces aren't used anywhere else in Arc, it can only be parsed in that one way.

But perhaps this is all a lot of work to avoid writing `fn (arg1 arg2)`. Other than golfing, I don't know if you really want to nest functions this way. And this seems like a waste of the only paired characters Arc doesn't use.

-----

3 points by rocketnia 3320 days ago | link

"And this seems like a waste of the only paired characters Arc doesn't use."

I don't think even the [] syntax really pulls its weight. Between (foo [bar baz _]) and (foo:fn_:bar baz _), the latter is already more convenient in some ways, and one advantage is that we can define variations of (fn_ ...) under different names without feeling like these variations are second-class.

(Arc calls it "make-br-fn" rather than "fn_", but I wanted the example to look good. :-p )

-----

2 points by zck 3320 days ago | link

Interesting.

Personally, I've never gotten comfortable with the colon intrasymbol syntax. Even in your example, I'm having trouble parsing it right now. I really don't like how it makes "bar" look like part of the first part, not the second.

-----


Found via the discussion at https://news.ycombinator.com/item?id=12112800. I'm still trying to make sense of it.

They key quote that got me interested in it: "..we believe it is important to clear up the longstanding confusion about the source of LISP's metalinguistic power -- contrary to folklore it is unrelated to the fact that LISP programs are represented by lists."

-----


malisper, I've told you before about my troubles building Clamp[1], and I haven't learned anything new in the interim. Everytime I try to do anything with asdf I just feel stupid. Including a reference to http://common-lisp.net/project/asdf/asdf/Quick-start-summary... in your Readme[2] is not nearly enough. Could you describe, just for your own system:

a) what version of asdf (and sbcl and OS and processor...) you have tried and seen it working on,

b) which directory you put Clamp in when you clone it,

c) which directory you run sbcl from, and

d) what commands you use to load clamp after starting sbcl?

It would only take a few sentences/minutes, and it'll help immeasurably to get people unfamiliar with Common Lisp started with Clamp. Telling people how to get up and running (without needing to understand the dependencies you rely on) is the bare minimum prerequisite necessary to contemplate replacing Arc-on-Racket with Clamp-on-Common-Lisp.

It seems not a month passes without some new project I would find most interesting to try out, except it doesn't have a Readme, or it has a Readme with tons of prose except for the one primary thing I care about in the beginning: what are the precise concrete commands that have been known to have it do something, at least on some one computer? It's a super easy way to stand out among the crowd, and it only takes a few sentences compared to the thousands of lines of code you've already written.

[1] http://www.arclanguage.org/item?id=18895

[2] http://www.arclanguage.org/item?id=18894

-----

3 points by malisper 3367 days ago | link

You should only need Clamp to be in ~/common-lisp, and asdf >3.1.2. With that everything else should take care of itself. I think, but I'm not sure, that you can install the latest version of asdf through Quicklisp.

If you want the particular details, I am running sbcl 1.2.11 on Ubuntu 14.04 with Clamp in ~/common-lisp and asdf version 3.1.3. To load Clamp, after using 'M-x Slime' in Emacs to start Slime, I type '(require :clamp)' or '(require :clamp-experimental)' to load it.

-----

1 point by akkartik 3366 days ago | link

Still no luck, sorry. I'm on Ubuntu 14.04 as well, and I'm running sbcl 1.1.14.debian. I did the following:

  $ sudo apt-get install emacs
  $ emacs --version
  GNU Emacs 24.3.1

  $ cat .emacs
  ; from http://melpa.org/#/getting-started
  (require 'package) ;; You might already have this line
  (add-to-list 'package-archives
               '("melpa" . "https://melpa.org/packages/"))
  (when (< emacs-major-version 24)
    ;; For important compatibility libraries like cl-lib
    (add-to-list 'package-archives '("gnu" . "http://elpa.gnu.org/packages/")))
  (package-initialize) ;; You might already have this line

  ; from https://www.common-lisp.net/project/slime/doc/html/Installation.html
  (setq inferior-lisp-program "/usr/bin/sbcl")

  $ pwd
  /home/akkartik
  $ ls common-lisp
  Clamp/

  $ emacs
  M-x package-install RET slime RET
  # some errors
  M-x slime
  # seemed to work
  * (require :clamp)
  Don't know how to REQUIRE CLAMP.
     [Condition of type SB-INT:EXTENSION-FAILURE]
I think I might be missing some step in between that you implicitly have..

-----

3 points by malisper 3366 days ago | link

What asdf version are you running?

-----

1 point by akkartik 3366 days ago | link

Ah yes, I meant to ask: how would I answer that?

-----

3 points by malisper 3366 days ago | link

(asdf:asdf-version)

-----

2 points by akkartik 3366 days ago | link

3.0.3

-----

3 points by malisper 3366 days ago | link

That's why you have been having issues. All you need to do is download a more recent version and load it in your .sbclrc file.

-----

4 points by akkartik 3366 days ago | link

Interesting! This is precisely what you need to record in the Readme!! "You need asdf 3.1 or higher." I assume you mean 3.1, right? According to http://www.cliki.net/ASDF the current version is 3.1. Is that really such a big difference?

I didn't think this was a problem since https://common-lisp.net/project/asdf/#downloads suggested just using the version that comes with my CL. Should I upgrade my sbcl as well?

Everytime I consider installing asdf manually I'm stymied by that last link. It provides an asdf.lisp but doesn't say what to do with it. Do I just load it? I just tried that without luck. I also tried various incantations from these tabs I have open: https://www.common-lisp.net/project/asdf-install/tutorial/in...; https://www.common-lisp.net/project/asdf-install/tutorial/se....

Common Lisp's crap documentation is culturally recursively turtles all the way down, just like all the good things about it.. :/ This is why I'm rooting for you to break out of this cycle of suck and improve things. Would you consider starting with a pristine Ubuntu 14.04 without sbcl or anything on it, and saving all the commands you type in until you get to a working Clamp? That would be wonderful to include in the Readme.

Another option might be to eschew asdf since the default versions on the most common Ubuntu release interact so poorly. Just dump all notion of dependencies and directly include all the code you need in the appropriate relative paths. Give me a single "sbcl --load" command to rule them all, just like Arc 3.1's classic "racket -f as.scm".

(It's not just Common Lisp, by the way. I have yet to meet a language-specific package manager that didn't suck. Pip, RubyGems, CPAN, it's all utter garbage. NPM hasn't started to suck yet, but I'm constantly looking for signs of incipient over-complexification.)

Edit: I'd be happy to provide a temporary Ubuntu server on Linode or something for this work.

-----

3 points by malisper 3366 days ago | link

> Is that really such a big difference?

No. It's just that asdf >=3.1.2 will check the ~/common-lisp directory by default. Prior to 3.1.2, you had to manually specify the directories you wanted asdf to check.

> https://common-lisp.net/project/asdf/#downloads suggested just using the version that comes with my CL. Should I upgrade my sbcl as well?

It looks like the version of sbcl you are using is old enough that the asdf version it comes with is older than 3.1.2. From that link, there is https://common-lisp.net/project/asdf/asdf.lisp Copy paste that into a file and add `(load "/foo/bar/asdf.lisp")` to your .sbclrc. I believe that Quicklisp comes with a more recent version of asdf, so you can also try setting that up and seeing if that configures asdf properly.

> I also tried various incantations from these tabs I have open

Those are for asdf-install which IS NOT asdf: http://www.cliki.net/asdf-install

asdf-install was the previous version of Quicklisp, but you should now be using Quicklisp instead.

I should spend the time to make Clamp a Quicklisp package so that you would be able to install it with just `(ql:quickload :clamp)` instead of needing to deal with asdf.

-----

1 point by akkartik 3365 days ago | link

That would be great, thanks.

> Those are for asdf-install which IS NOT asdf: http://www.cliki.net/asdf-install

O_O

-----

1 point by akkartik 3365 days ago | link

Ah, I FINALLY managed to get Clamp loaded. Here are the steps I followed from my home directory on Ubuntu 14.04:

  $ sudo apt-get install sbcl
  $ wget https://beta.quicklisp.org/quicklisp.lisp  # following instructions at https://quicklisp.org
  $ sbcl --load quicklisp.lisp
  * (quicklisp-quickstart:install)
  * (ql:add-to-init-file)
  * (quit)
  $ cd quicklisp/local-projects
  $ git clone https://github.com/malisper/Clamp
Now, from any directory:

  $ sbcl
  * (ql:quickload :clamp)
  * (in-package :clamp)
  * (use-syntax :clamp)
Now it's ready!

  * (map [+ _ 1] '(1 2 3))
  (2 3 4)
Woohoo! I'll send you a pull request. Is there some way we can provide an "Arc repl" that's already in the right package and uses the right syntax? I tried this, but it didn't work:

  $ cat quicklisp/local-projects/Clamp/init.lisp
  (ql:quickload :clamp)
  (in-package :clamp)
  (use-syntax :clamp)
  $ sbcl --load quicklisp/local-projects/Clamp/init.lisp
  ; Loading "clamp"
  * (map [+ _ 1] '(1 2 3))
  The variable [+ is unbound.

-----

3 points by malisper 3364 days ago | link

Reader macros work on a per file basis so I don't think there is a way to set the proper syntax from a separate file.

You might want to consider trying to setup Slime, as it provides some really amazing Smalltalk-esque features. I detailed most of them in my blog series, Debugging Lisp[0]. Some of the ones I covered in the post are: recompilation of code at runtime, restarting of a stack frame at runtime, an object inspector, interactive restarts (restarts are a better form of try/catch), various forms of function lookup (e.g. list all functions who call function X). I haven't yet covered it, but I eventually want to, is slime-macrostep[1]. It lets you interactively expand parts of a macro step by step.

[0] http://malisper.me/2015/07/07/debugging-lisp-part-1-recompil...

[1] http://kvardek-du.kerno.org/2016/02/slime-macrostep.html

-----

1 point by akkartik 3362 days ago | link

I'm trying to work through http://malisper.me/2015/07/07/debugging-lisp-part-1-recompil... but I got this error when I tried to restart from any of the stack frames:

  Cannot restart frame: #<SB-DI::COMPILED-FRAME EVAL>

-----

1 point by akkartik 3364 days ago | link

Ah, I figured out a way to run it with just:

  $ clamp
https://github.com/malisper/Clamp/pull/5

-----

1 point by akkartik 3364 days ago | link

Yes, I'll certainly try to add Slime back now that I have the foundation working well together.

-----

1 point by akkartik 3362 days ago | link

I've gotten Slime working, but so far I'm still doing the same things I would do in an interactive session:

  $ emacs
  M-x slime RET
  # in the slime buffer
  * (ql:quickload :clamp)
  * (in-package :clamp)
  * (use-syntax :clamp)
Is that what do you typically do?

Also, when I try to C-x C-e an arc expression in some random buffer after the above steps, it doesn't seem to remember the above commands anymore. The only thing that works is running commands at the repl.

-----

4 points by malisper 3362 days ago | link

> I've gotten Slime working, but so far I'm still doing the same things I would do in an interactive session:

Yes, that's what I typically do. If you wanted to, you could add those instructions to your .sbclrc, and that should load Clamp on start up.

> Also, when I try to C-x C-e an arc expression in some random buffer after the above steps, it doesn't seem to remember the above commands anymore.

You need to have slime-mode enable in the buffer you are editing. You can add the following code to your .emacs:

    (require 'slime)
    (add-hook 'lisp-mode-hook (lambda () (slime-mode t)))
and then whenever you open a file that ends in .lisp, it will enable slime-mode.

If you are going to get into programming Lisp with Emacs, you should look into Evil (vim bindings for Emacs), paredit (smart paren editing), ac-slime (autocomplete for slime), show-paren-mode (shows matching parens), and undo-tree (a better version of undo/redo). Although I've never used it, you might want to look at Spacemacs which is supposed to supply sane defaults for Emacs.

-----

3 points by zck 3362 days ago | link

>If you are going to get into programming Lisp with Emacs, you should look into Evil (vim bindings for Emacs), paredit (smart paren editing), ac-slime (autocomplete for slime), show-paren-mode (shows matching parens), and undo-tree (a better version of undo/redo).

Yes, customizing Emacs is really useful for making it better to use. To help with that, here are some of my config's settings for things you've mentioned. My show-paren-mode settings are here (https://bitbucket.org/zck/.emacs.d/src/default/init.el?filev...).

Instead of paredit, I use smartparens. They do similar things, but when I looked at the two, I thought smartparens was better, although I can't remember why right now. My config is here (https://bitbucket.org/zck/.emacs.d/src/default/init.el?filev...).

I should similarly check out the other things you've mentioned (except Evil, 'cause I don't like modal editing).

-----

1 point by akkartik 3362 days ago | link

Ah, that hook was what I needed. Thanks! I'll check out all your tips.

-----

1 point by akkartik 3365 days ago | link

Draft changes to Readme: https://github.com/malisper/Clamp/pull/3/commits/d4e89140b9

Everyone please try out the instructions and/or suggest improvements to the prose.

-----

2 points by highCs 3367 days ago | link

> replacing Arc-on-Racket with Clamp-on-Common-Lisp

We need a compiler for that btw (ie. the arc macro Im talking about nearby?) because 1) language designers need a compiler and 2) right now, it is missing parts of Arc; specifically:

  ('(1 2 3) 0)
  (mylist 0)
  ("Yoopi" 0)
  (+ "Hello" " World")
  top level ssyntax
  What case are missing for destructuring?
  What am I forgetting? Do we have 'for'?

-----

2 points by highCs 3367 days ago | link

Put clamp inside quicklisp/local-projects/

Run your lisp wherever you want and then:

  (asdf:load-system :clamp)
  (defpackage #:mypkg (:use clamp))
  (in-package #:mypkg)
(tested with ccl)

> Telling people how to get up and running

Second that. Some intructions in the README to get clamp up and running on linux would be a must. OSX and windows would be nice to have too.

-----

3 points by akkartik 3367 days ago | link

Doesn't work with sbcl, I'll try it with ccl later today:

  $ uname -a
  Linux akkartik 3.5.0-17-generic #28-Ubuntu SMP Tue Oct 9 19:32:08 UTC 2012 i686 i686 i686 GNU/Linux

  $ pwd
  /home/akkartik

  $ sbcl --version
  SBCL 1.1.14.debian

  $ ls quicklisp/local-projects
  Clamp/  system-index.txt

  $ rlwrap sbcl
  This is SBCL 1.1.14.debian, an implementation of ANSI Common Lisp.
  More information about SBCL is available at <http://www.sbcl.org/>.

  SBCL is free software, provided as is, with absolutely no warranty.
  It is mostly in the public domain; some portions are provided under
  BSD-style licenses.  See the CREDITS and COPYING files in the
  distribution for more information.
  * (require :asdf)

  ("ASDF")
  * (asdf:load-system :clamp)

  debugger invoked on a ASDF/FIND-SYSTEM:MISSING-COMPONENT in thread
  #<THREAD "main thread" RUNNING {ABFC8D1}>:
    Component :CLAMP not found

  Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.

  restarts (invokable by number or by possibly-abbreviated name):
    0: [ABORT] Exit debugger, returning to top level.

  ((:METHOD ASDF/OPERATE:OPERATE (SYMBOL T)) ASDF/LISP-ACTION:LOAD-OP :CLAMP) [fast-method]
  0]
  *
Oh, are you using it with Quicklisp? I also tried installing quicklisp as well, but it didn't change the error.

-----

2 points by highCs 3367 days ago | link

I didnt installed clamp using quicklisp which wasnt able to find it. I put clamp myself in the local-projects folder.

I think ccl has an asdf thing and must be at the origin of the quicklisp folder look up. I hoped it was standard.

-----

1 point by akkartik 3367 days ago | link

But you have quicklisp installed with ccl? That's why CCL looks in quicklisp/local-projects?

-----

1 point by akkartik 3367 days ago | link

Hey, is CCL not open-source?

-----

2 points by highCs 3367 days ago | link

It's even free buddy: http://ccl.clozure.com/

-----

1 point by akkartik 3367 days ago | link

Ah, I got confused because the svn repo came with a prebuilt binary. But it has the sources too.

-----

More