Arc Forumnew | comments | leaders | submit | aw's commentslogin
1 point by aw 5491 days ago | link | parent | on: Should stdin, stdout have parens around them?

You are correct on all points: the std ports do need to be parameters, I am using my defvar hack to do that, and this is a change to the language.

-----

2 points by aw 5491 days ago | link | parent | on: How to Future-Proof Your Code

For myself I'm still finding out the patterns and abstractions for writing code in order.

For example, the rule says that code shouldn't depend on code that comes after it, but then what about recursive definitions? Asking the question leads me to discover things like extend and defrule.

And there are subtler questions that I don't really understand yet. For example, if I write unit tests that work with earlier definitions, then those unit tests should still work after the later definitions are loaded. So later definitions should extend the program while letting the earlier definitions still work. But what does that mean exactly?

-----

2 points by akkartik 5491 days ago | link

the rule says that code shouldn't depend on code that comes after it, but then what about recursive definitions? Asking the question leads me to discover things like extend and defrule.

It's really interesting that you start with a simple axiom and end up with something largely overlapping with the principle of separation of concerns.

I say 'overlapping' because I sense you aren't quite using extend in the same way. You're using extend more rigidly (no negative connotation intended) than me, chopping your functions up into finer bits than I do.

Hmm, I find myself focusing on the parallel between overrideable definitions in dynamic languages like arc and ruby, and languages like haskell that permit defining functions in multiple clauses:

  fact 0 = 1
  fact n = n * fact(n - 1)

-----

2 points by aw 5491 days ago | link

You're using extend more rigidly

Yes, I should probably clarify that usually I'm not that rigid either. Most of my Arc programming is exploratory programming (figuring out what it is that I actually want) not engineering (implementing a solution to known goals).

Now the transition from exploratory programming to engineering is an interesting one. If I've created a program (using primarily exploratory programming techniques) and then I want my program to run on Arc-m, that's then largely an engineering effort with an easy to describe goal: I want my program to work the same on Arc-m as it does on Arc-n.

Paying the cost of "always" doing things in a particular rigid way doesn't make sense during exploratory programming. But if then I have a particular engineering task (convert this to run on Arc-m), turn introducing some rigidity (scaffolding if you will) is often useful: writing some unit tests, getting the dependencies laid out clearly.

-----

1 point by akkartik 5491 days ago | link

The benefit of interleaved tests is future-proofing, like you said. When making large, non-localized changes such as the move to a new arc, you know exactly the first test that failed. But what happens when you scale from one file to two? When multiple files of definitions depend on arc.ss?

The benefit of keeping tests in a distinct file: you can reason about the final semantics of definitions without thinking about the order in which they are orchestrated. That is a useful simplification except when making large localized changes.

I'm not certain of these ideas by any means. Just thinking out aloud. Would it be useful to analyze the tree of dependencies, and to execute tests in dependency order regardless of the order they're written in? Would that give us the best of both worlds?

-----

1 point by aw 5491 days ago | link | parent | on: Arc Conference

1. SV

2. happy to see what happens

3. I'd probably give a useful talk (such as, calling libraries in other languages from Arc), and, if there's time, a fun talk (the extraordinary elegance of parsing by matching)

4. No.

-----

2 points by aw 5494 days ago | link | parent | on: Arc run error - beginner

I haven't tried OSX myself, but with recent versions of MzScheme you should no longer use the "-m" option. Try just "mzscheme -f as.scm" and see if that helps.

Also, if you try just "mzscheme" without any arguments, do you get an mzscheme prompt like this?

  Welcome to MzScheme v4.2.4 [3m], Copyright (c) 2004-2010 PLT Scheme Inc.
  >
if you still get the "Abort Trap", there's a problem with your MzScheme installation. You could try downloading directly from http://racket-lang.org/download/ (mzscheme got renamed to Racket). If you can't get MzScheme / Racket to work, you can also ask for help over on http://racket-lang.org/community.html

-----

1 point by aaronmoodie 5494 days ago | link

hey aw, this is saying that arc still requires MzScheme? https://github.com/mxcl/homebrew/commit/547edb0d39175a2e3898...

-----

1 point by waterhouse 5494 days ago | link

It's wrong. "racket -i -f as.scm" works fine and I've been using it for a few months. (Note that this is for arc3.1, which you are using, so that should be fine.)

-----

1 point by akkartik 5494 days ago | link

Though I switched temporarily back to PLTv4 after reporting this bug: http://bugs.racket-lang.org/query/?cmd=view&pr=11380 (since fixed at HEAD)

-----

3 points by elibarzilay 5493 days ago | link

That fix is also in 5.0.2.

-----

1 point by akkartik 5493 days ago | link

That was quick! Upgraded.

-----

1 point by aaronmoodie 5494 days ago | link

thanks waterhouse. Did you install from src? I'm a little unsure about the configure option. I'm assuming "--prefix=/usr/local/racket --enable-macprefix" ?

-----

1 point by waterhouse 5494 days ago | link

I didn't install from source, and have no idea about doing that. What I do to install is: Download DrRacket from racket-lang.org. Move it to the Applications folder as is conventional. Add to the ~/.bash_profile the following line:

  export PATH=/Applications/Racket\ v5.0.2/bin:$PATH
Now you'll be able to use the "racket" and "mzscheme" and "raco" and all other programs from the "bin" subdirectory by just typing their names. (This applies to any terminal windows opened after you've changed .bash_profile.)

-----

2 points by aaronmoodie 5494 days ago | link

always making things hard for myself... thanks for your help waterhouse. I'm all up and running now, including rlwrap. Arc time. Thanks!

-----

1 point by aaronmoodie 5494 days ago | link

thanks aw, it is my mzscheme install. I'll install racket and give it a go.

-----

1 point by aw 5495 days ago | link | parent | on: How to Future-Proof Your Code

it also seems like it could be easily broken by someone merely not observing the order conventions

If you like unit tests, one option to make sure that you've put things in dependency order is instead of running your unit tests after loading all the code, put the unit tests for a definition immediately after the definition. That way those unit tests for the definition will run before the rest of the code has been loaded.

-----

2 points by aw 5495 days ago | link | parent | on: How to Future-Proof Your Code

Welcome!

Certainly there's no way to decompose say ac-fn into its own module

It is possible (though somewhat awkward) to create your own forwarding mechanism.

Module A:

  (define ac-fn #f)

  (define (set-ac-fn! fn)
    (set! ac-fn fn))

  (define (ac s env)
    ...
    ((eq? (xcar s) 'fn) (ac-fn (cadr s) (cddr s) env))
Module B:

  (set-ac-fn!
   (lambda (args body env)
     (if (ac-complex-args? args)
     ...

-----

4 points by aw 5496 days ago | link | parent | on: defrule

An alternative to public interfaces is to not treat any part of your code as set-in-stone, but instead to improve your code whenever you think of something better -- such as using better argument names as you come up with them.

The traditional assumption is that if I'm using your code and you change it, I'll find it hard and difficult to change my code to use your new version, and so you should put time and effort into coming up with things like stable public interfaces -- things that you wouldn't otherwise do for yourself, but things that you think will help me.

However, using a powerful language such as Arc I've found easy to change my code to make it work with new releases. For example, the home page of arclanguage.org says "Expect change. Arc is still fluid and future releases are guaranteed to break all your code." Which sounds pretty dire. Yet in practice I've easily rebased my code on every release of Arc since arc0.

Easier in fact for me than my experience using other languages that strive to maintain compatibility with earlier versions -- their libraries end up having so many layers and compatibility modes that it becomes tedious for me to figure out what they're doing and where their bugs are.

-----

1 point by aw 5507 days ago | link | parent | on: List of characters vs string

That's very interesting. I don't think I've heard of the idea of having the printer display a list of characters as a string before.

-----

3 points by fallintothis 5507 days ago | link

I thought it would've been kind of implicit in the perennial "hey, let's make strings lists" idea. Which isn't new, by the way.

Haskell

  Prelude> :type "abc"
  "abc" :: [Char]
Erlang

  1> A="abc".
  "abc"
  2> is_list(A).
  true
Prolog

  ?- is_list("abc").
  true.
What else do all these strings-are-lists languages have in common? They're notoriously slow for string processing. Linked lists use up more memory and simple accesses are O(n) rather than O(1). It's to the point that the Haskell guys wrote the ByteString library to override the standard Prelude functions and added a GHC extension to parse "abc" as a ByteString instead of [Char].

Arc itself has deviated on this point (from http://paulgraham.com/arcll1.html). Comments in arc.arc include:

  ; compromises in this implementation: 
  ...
  ; separate string type
  ;  (= (cdr (cdr str)) "foo") couldn't work because no way to get str tail
  ;  not sure this is a mistake; strings may be subtly different from 
  ;  lists of chars

  ; idea: get rid of strings and just use symbols
  ; could a string be (#\a #\b . "") ?
PicoLisp uses the strings-are-symbols approach (http://software-lab.de/doc/faq.html#strings). And that illustrates that it's all a matter of focus. PicoLisp is dynamic, perhaps to a fault. By design, it has no arrays, strings, lambda keyword, or even a compiler. But if you have any investment in efficiency, proper strings seem smarter, if only as a library for when you need the performance (as in Haskell). Never mind arguable conceptual differences.

Arc awkwardly straddles this divide. It has (a) hash tables instead of alists, but (b) lists instead of arrays, (c) a "compiler" that's mostly to keep code from being prohibitively slow, and (d) proper strings, but everyone says they want lists.

It seems to me that the people who want strings == lists are mostly looking for a Grand Unified Theory of Sequences. The idea is that if you don't want the standard lib to be a mess of

  (case (type x)
    cons
      ...
    string
      (coerce 'string (... (coerce x 'cons)))
    god-forbid-we-add-another-sequence-type
      (coerce ...))
you'll just make everything lists, and to hell with the distinction. Everything's the same, so it's simpler!

You could tackle the problem from the other end. Other languages invest in abstractions to write code that sanely deals with different data types. What sort of abstractions do we get out of this? Often some form of object orientation, be it message-passing or generic functions or what-have-you. It starts looking pretty enticing: man, we could make everything an object, and to hell with the distinction. Everything's the same, so it's simpler!

Hmm...

-----

4 points by d0m 5507 days ago | link

Do you think it's a bad thing to have a specialized library for optimized string processing? In the big majority of cases, I personally don't care at all about the performance of my string processing. Would the needs arrive, I'd be perfectly willing to use another library specialized for that (Which I would accept that all the high level function won't work for optimization reasons).

Also, as you say, a couple of (case) would be needed in the standard library. But again, I hardly see why it is a bad thing? (Maybe multi-methods would be a better approach but that's another debate). My point is that I don't really care what the standard lib code looks like.. the important part is the end language result.. no?

Finally, I get your point about the "Grand Unified Theory of Sequences". However, this list of chars suggestion is more about making the code behaviour simpler.. not that much about trying to be "minimalist to be minimalist".

For instance, what (map bleh "test") should do? What should it return? If I know that "test" is simply a list of character, I know for sure what would happen (bleh would be applied on a character and map would returns a list of whatever the function returns). Now, what does it currently do? Maybe the same thing, maybe not. (See that post: http://arclanguage.org/item?id=12311) If my function returns a string instead of a character, it crashes.. since when does map expect something special? I.e. (map ... (range 1 10)) doesn't expect me to return a list of integer.

-----

2 points by fallintothis 5507 days ago | link

Do you think it's a bad thing to have a specialized library for optimized string processing?

Not in principle. Especially if it was totally transparent. This isn't the case in Haskell, because you wind up importing ByteString functions qualified with a prefix like B, so your code is littered with B.map this and B.length that.

But then, if it's totally transparent (i.e., same behavior & syntax between the inefficient one and the efficient one), what was the point of the separation to begin with? Just use the efficient one.

Also, as you say, a couple of (case) would be needed in the standard library.

Actually, no. I was saying that treating strings as lists would make code shorter. E.g., map has to check for strings manually right now.

  (def map (f . seqs)
    (if (some [isa _ 'string] seqs)
         (withs (n   (apply min (map len seqs))
                 new (newstring n))
           ((afn (i)
              (if (is i n)
                  new
                  (do (sref new (apply f (map [_ i] seqs)) i)
                      (self (+ i 1)))))
            0))
        (no (cdr seqs))
         (map1 f (car seqs))
        ((afn (seqs)
          (if (some no seqs)
              nil
              (cons (apply f (map1 car seqs))
                    (self (map1 cdr seqs)))))
         seqs)))
But if strings were built out of cons, we'd be able to prune out that first branch.

  (def map (f . seqs)
    (if (no (cdr seqs))
        (map1 f (car seqs))
        ((afn (seqs)
           (if (some no seqs)
               nil
               (cons (apply f (map1 car seqs))
                     (self (map1 cdr seqs)))))
         seqs)))
(On that note, would there be a difference between "" and nil?)

Finally, I get your point about the "Grand Unified Theory of Sequences". However, this list of chars suggestion is more about making the code behaviour simpler.. not that much about trying to be "minimalist to be minimalist".

My point was that fusing lists and strings is a popular idea since Arc doesn't really have good facilities for polymorphism. You currently need to do the (case ...) junk (or macroexpand into it or whatever), since there are sequences with their subtle interface differences. Strings-are-lists would get rid of this (until we needed another sequence type...). Object-oriented languages "do polymorphism" better (e.g., with multimethods), but breed a fervor for all-things-OO similar to the just-make-everything-a-list sentiment, even though both are extremes.

I don't really care what the standard lib code looks like.. the important part is the end language result.. no?

Right now Arc sits in the middle, without a unified sequence interface -- be it making everything a list or having some other abstraction -- so we all suffer for it, both in the standard library and in user code. Here's a fun one:

  arc> (count #\a '(#\a #\b #\c))
  1
  arc> (counts '(#\a #\b #\c))
  #hash((#\b . 1) (#\c . 1) (#\a . 1))
  arc> (count #\a "abc")
  1
  arc> (counts "abc")
  Error: "Can't take car of \"abc\""
The difference being count uses each, which is polymorphic (i.e., is written essentially with a big (case ...)), but counts hard-codes recursion on cdrs.

See that post: http://arclanguage.org/item?id=12311

Not to get too hung up on this example -- I can appreciate the simplicity of the strings-are-lists model where map is concerned -- but...

You could argue that map's current behavior is sane. There's a subtle difference between expected behaviors, depending on how you want to interpret map. Currently, map says the output sequence should be of the same type as the input. As such, you might expect an error from

  (map [+ "abc" _] "def")
if it was to be understood roughly as

  (map-as 'string [+ "abc" _] "def")
since strings aren't really lists -- they don't have arbitrary elements. (Using the map-as from my post wouldn't actually throw an error here. coerce special-cases strings and flattens them. Yet another subtlety in expectations: should map's "same type" behavior abide by coerce's rules?)

This tangentially relates back to the contagion point in http://paulgraham.com/arcll1.html -- how to shift between strings and lists depending on what sorts of elements are added.

-----

2 points by d0m 5507 days ago | link

(Note: I don't by any means want to be aggressive. I'm still a beginner in the lisp world.. so everything I say here as a big: "Me being a real newbie thinks: " in front :) )

"But then, if it's totally transparent (i.e., same behavior & syntax between the inefficient one and the efficient one), what was the point of the separation to begin with? Just use the efficient one."

Maybe I wasn't clear.. but I never said it needed to be transparent. If I need my strings to be highly optimized, I'd much prefer use a specialized string library and use, say:

  (let optimized-string (create-optimized-string (get-100000-chars))
   (optimized-string-map blah optimized-string))

or maybe a:

  (with/optimized-string
     (let bleh (get-100000-chars)
       (map blah bleh)))
(which would take care of rebinding core high level functions)

-----

Also, I'm not sure I agree with you on the "(until we needed another sequence type...)." Why another sequence type? Any sequences could be coerce to simple list which work with the core functions. Use map-hash, or map-file, or map-whatever if you need specialized version for other sequences. (Or as I said in the last example)

-----

Finally, again (sorry), I don't really agree on: "map says the output sequence should be of the same type as the input."

Is it true?

  (map [string "test" _] '(1 2 3)) -> ("test1" "test2" "test3")
  (map [string "test" _] "123")) -> err
It's only for string that the output needs to be the input. (Which is weird at best in my opinion).

-----

1 point by fallintothis 5506 days ago | link

I don't by any means want to be aggressive.

Neither do I. Just trying to be terse (not that I am anyways; I have a problem with conciseness). :)

I never said it needed to be transparent

No, but I was. In my limited experience, using ByteStrings in Haskell is still too much work, as opposed to having the standard Prelude functions work "out of the box". Instead of being able to say

  map (\x -> 'x') "abc"
you wind up needing to

  import qualified Data.Bytestring as B

  B.map (\x -> 'x') (B.pack "abc")
When you need to start changing every string-related function, the reader, the writer, blah blah blah, it gets to be a hassle. Perhaps it's less of a pain in dynamically-typed languages like Arc. I don't know.

Why another sequence type? Any sequences could be coerce to simple list which work with the core functions.

Not everything is a linked list. And forcefully coercing every data structure into a linked list wrecks the time & space complexities that give many data structures their purpose. You'd force ranges (virtual sequences, a la Python) to eat up memory, though you certainly want to (say) map over them. You force arrays to have O(n) random access times. You couldn't have immutable sequences because linked lists are mutable. Etc.

Use map-hash, or map-file, or map-whatever if you need specialized version for other sequences. (Or as I said in the last example)

Frankly, ad-hoc polymorphism is ugly. Take Scheme, for instance, whose (R5RS) standard comparison operators include:

  char=? char<? char>? char<=? char>=? string=? string<? string>? string<=?
  string>=?  = < > <= >=
where overloaded comparisons would do.

I don't really agree on: "map says the output sequence should be of the same type as the input."

  arc> (type '(1 2 3))
  cons
  arc> (type (map [string "test" _] '(1 2 3)))
  cons
  arc> (type "123")
  string
  arc> (type (map inc "123"))
  string

-----

1 point by d0m 5506 days ago | link

"When you need to start changing every string-related function, the reader, the writer, blah blah blah, it gets to be a hassle. Perhaps it's less of a pain in dynamically-typed languages like Arc. I don't know."

I also don't know :) Maybe advanced arc users could share their opinion on that?

"Not everything is a linked list. And forcefully coercing every data structure into a linked list wrecks the time & space complexities that give many data structures their purpose."

Yes, this is true.

In fact, it's a decision that needs to be taken.. should the core high level function work against different data structure? (As it does with clojure?)

As for now, map does work with string (but has a weird behavior in my opinion) and doesn't work with hash. Is this what we want?

Also, what do you think about multimethods? Do you think it's something useful to be added in Arc?

---------

About the map input versus output, I get what you mean. However, (map [string "test" _] "123") should work :-/ Maybe the problem lies in the concatenation operator while constructing the new string. i.e.

  (map [coerce _ 'int] "123") could give "116101115116"
 
(I know it's not a good example... however, look at this one) :

  (map [string "(" _ ")"] "test") -> Shouldn't it return "(t)(e)(s)(t)" ?!

-----

2 points by fallintothis 5505 days ago | link

should the core high level function work against different data structure?

The answer seems to be a resounding yes. Polymorphism was one of Arc's main principles when it was 3 weeks old (http://paulgraham.com/arcll1.html), and one that's been more-or-less preserved to this day -- just clunkily.

doesn't work with hash

I don't care for maptable in Arc. Seems like something that should be done by map. I address the point about hash-tables-as-sequences more in http://arclanguage.org/item?id=12341.

Also, what do you think about multimethods? Do you think it's something useful to be added in Arc?

From what I've seen, generic functions (single dispatch or multimethods) seem a "Lisp-y" way of solving the type-dispatch problem, and you don't really need to go full-blown OO about it. But I don't know much about other options.

However, (map [string "test" _] "123") should work :-/

Oh, I agree. I was just saying, the case could be made. :P

I think my map-as (from your original thread: http://arclanguage.org/item?id=12341) reflects map's intended behavior best, since coerce is really our standard for how types should interact. Not that it should be implemented that way, necessarily. But since coerce will flatten strings, it seems fair to say that

  (map [string "test" _] "123")
and

  (coerce (map1 [string "test" _] (coerce "123" 'cons)) 'string)
should return the same thing. At present, map errors, but

  arc> (coerce (map1 [string "test" _] (coerce "123" 'cons)) 'string)
  "test1test2test3"
The map-as approach would still preserve the input type == output type behavior, but do it by coerce's rules, which happen to play nicely with strings here.

-----

1 point by akkartik 5505 days ago | link

I've been thinking about the semantics of maptable. Right now it seems misnamed; it iterates over the table before returning it unmodified. But what should the right semantics be? Should it return a table with modified values for the same set of keys? Or should it return a list? Or should each iteration return a (k v) pair so you can get a new table with entirely new keys (I think map-as does this)? All of these could be useful; I think we need a more elaborate language than just map/fold to describe them.

-----

2 points by rocketnia 5505 days ago | link

(map [string "(" _ ")"] "test")

The function [string "(" _ ")"] returns strings, so if anything, the result of that expression should be a sequence of strings, not a string itself.

Nevertheless, maybe (mappend [string "(" _ ")"] "test") should do what you're thinking.

-----

2 points by aw 5512 days ago | link | parent | on: Arc based on?

Racket is written in C.

-----

2 points by aw 5514 days ago | link | parent | on: A better syntax for optional args

Most recent implementation: http://awwx.ws/table-rw3

Though (for what it's worth) I've since realized that I don't like the {a 1 b 2} syntax; I find the key value pairs aren't grouped together well enough visually for me, and the curly brackets don't stand out enough themselves.

-----

More