Arc Forumnew | comments | leaders | submit | akkartik's commentslogin
1 point by akkartik 5108 days ago | link | parent | on: Apply for macros: a solution

I'm not sure I understand you, so let me show a few examples:

  ; function calls inside macros?
  wart> (def f(x) x)
  wart> (mac foo(a b) `(cons ,a ,f.b))
  wart> (foo @'(1 (2 3)))
  (1 2 3)

  ; splice friendly macros?
  wart> (foo @(foo @'(1 ((2 3)))))
  (1 2 3)

  ; splice friendly macros?
  wart> (mac bar(a b) `(cons 3 ,@(foo a b)))
  wart> (bar @'(1 (2 3)))
  (3 . 1)
Do these address any concerns?

-----

1 point by rocketnia 5108 days ago | link

It does tell me I don't understand your system yet, at least. :-p

So if x is a variable containing ''abc, when we say (bar x x) we wind up evaluating (cons 3 ''(cons ''x ''x))?

When we evaluate (cons ''x ''x), it strips off the '' to get (cons foo foo), and then we end up with (''abc . ''abc)?

Then, altogether, we end up with a result of (3 . (''abc . ''abc)), which appears at the REPL complete with ''?

Let me know if I'm getting anything wrong.

-----

1 point by akkartik 5108 days ago | link

Almost:

  wart> (mac foo(a b) `(cons ,a ,b))
  wart> (mac bar(a b) (prn `(cons 3 ,@(foo a b)))) ; prn for debugging
  wart> ((fn(x) (bar x x)) 'abc)
  (cons 3 x . x)
  004cell.cc:158 car of non-cons: x
  004cell.cc:158 car of non-cons: x
  (3 . abc)
My bar example is poorly constructed; besides that I think you're right.

-----

1 point by rocketnia 5105 days ago | link

Oh, I see. Would you mind showing me what happens with (mac bar(a b) `(cons 3 ,(foo a b)))?

-----

1 point by akkartik 5105 days ago | link

  wart> (mac foo(a b) `(cons ,a ,b))
  wart> (mac bar(a b) `(cons 3 ,(foo a b)))
  wart> ((fn(x) (bar x x)) 'abc)
  005types.cc:263 can't coerce symbol abc to function
There was a typo in grandparent, so let me summarize the correct versions:

  wart> (mac foo(a b) `(cons ,a ,b))
  wart> (mac bar(a b) `(cons 3 ,@(foo a b)))
  wart> ((fn(x) (bar x x)) @'(abc))
  (3 . abc)

  wart> (mac foo(a b) `(cons ,a ,b))
  wart> (mac bar(a b) `(cons 3 ,(foo a b)))
  wart> ((fn(x) (bar x x)) @'(abc))
  005types.cc:263 can't coerce symbol abc to function   ; no change
I think you're asking what happens to (''a b c). Hmm, I don't translate that to ''(a b c). It seems to be acting like (a b c). Does that seem right?

---

Another potential concern: I'm using a single boolean to track whether we encounter a '', and I reset it before unquote's eval and check it after. I think it might need to be a stack of booleans to handle nested expressions, but I've not been able to come up with a failing test case.

http://github.com/akkartik/wart/blob/6d5d5a845c/007scope.cc#...

-----

1 point by akkartik 5105 days ago | link

Ah, I answered my own question. It turns out this doesn't work:

  (all present? '((1) (2 4 6)))
if you define all as:

  def all(f seq)
    (and @(map f seq))
For precisely this reason.

(present? is a synonym for idfn.)

-----

1 point by rocketnia 5105 days ago | link

Hmm, what happens when you say `,(write `,'foo)? ^_^

-----

1 point by akkartik 5105 days ago | link

  wart> `,(write `,'foo)
  foo005types.cc:263 can't coerce symbol foo to function
Is that what you expect? I'm not sure what you're trying.

-----

1 point by rocketnia 5105 days ago | link

Something bugs me about this claim:

" '' is an internal detail. The reader provides no way to type it in. If you ever see it printed at the REPL, that's a bug."

If unquote adds a '', then `,'foo should return ''foo, right? Then 'write should write ''foo at the REPL.

In order to weasel out of anything that stripped '' from the arguments to a function, I tried putting `, around the whole command, in the hopes that it would somehow manipulate the state of the don't-strip-'' flag(s) for the duration of the command.

In any case, there's no reason foo should be called, is there? O_o

In other news, this might be an easy test case for the scope of the don't-strip-'' flag. If your implementation sets it to true on the way in and false on the way out, then it'll become false on the way out of the inner `, , rather than recovering its previous value of true.

-----

1 point by akkartik 5095 days ago | link

"If unquote adds a '', then `,'foo should return ''foo, right?"

Unquote doesn't add '', @ does. Perhaps that changes your example?

"In any case, there's no reason foo should be called, is there? O_o"

:) Yeah that's a bug with my paren inference; it was implicitly wrapping the expression in parens. I'd never seen backquote-comma before. Here's a temporary workaround:

  wart> (list `,(write `,'foo))
  foo(foo)  ; ignore the (foo) -- it's the return value
Update: fixed (http://github.com/akkartik/wart/commit/70cfb18504)

  wart> `,(write `,'foo)
  foofoo

-----

1 point by akkartik 5104 days ago | link

Hmm, I haven't fully digested this, but I do have a passing test on nested unquote: http://github.com/akkartik/wart/blob/a5a9f2ffcd/030.test#L35

-----


Are you trying to insert a literal '@'? You need to escape it as '@@'. I believe racket has some support for variable interpolation that is triggered by '@', and the 'eof' error is because it's trying to read a variable name after the @, but there's nothing but a space.

-----

4 points by rocketnia 5111 days ago | link

For the record, this doesn't have anything to do with Racket. This is atstrings, a feature of Arc.

Atstrings is off by default in plain Arc 3.1, but it's activated by the line (declare 'atstrings t), and news.arc includes that line for its own purposes.

-----

1 point by lark 5113 days ago | link

Escaping as '@@' worked, thanks.

-----


Ok, reproduced. Let me take a look.

One bug: you don't need the parens inside the [].

Update: it turns out support for multipart/form-data in anarki is incomplete. I'll see if I can fix this; in the meantime you'll have to POST to a static defop which is then responsible for reading and parsing the multipart body from req!in. webupload.arc shows how to read, but it punts on parsing.

Update 2: your test should now work. http://github.com/nex3/arc/commit/e3cfc8cac3

-----

1 point by lark 5113 days ago | link

Thank you for the fix, that was fast!

I get in trouble when needing to update Anarki. Some patches like this one don't apply on the Anarki version I use and it seems only a brand new git clone gives the latest Anarki version. Which then requires I move around data, stuff in static, etc.

Is there a better way? How can I separate my app from the Anarki changes?

Can one run .arc programs from a directory that is different than the directory Anarki lives in?

-----

1 point by akkartik 5113 days ago | link

A failing test makes everything easier!

You can run anarki from anywhere:

  $ racket -f $ARC/as.scm
If you aren't making many changes to the base arc it might also make sense to stay updated with anarki. You just have to do a git pull every now and then. Keep your app in new files; that'll ensure pulls don't cause conflicts.

In the short term if you send me your srv.arc I'll make the change for you. (email in profile)

-----

1 point by lark 5113 days ago | link

Note that the latest Anarki reports the following after calling (asv):

  reference to undefined identifier: _ranked-stories*

-----

1 point by akkartik 5112 days ago | link

Yes, anarki loads news.arc by default. You probably don't want to do that in your repo. Delete the line from libs.arc. Feel free to do that in anarki as well if it'll make your life easier.

-----

1 point by lark 5111 days ago | link

It seems strange news.arc is included.

-----

1 point by akkartik 5111 days ago | link

Yes the original distribution doesn't load it by default. But I found that many people come to arc wanting to see how HN runs and it seemed worth reducing one step for them.

On a more pragmatic level, I found I was repeatedly making changes and forgetting to test them with news.arc. Loading it by default was at least a superficial sanity check. But this isn't a big deal. Like I said, feel free to delete the load, commit and git push.

-----

1 point by lark 5113 days ago | link

The test from the following link no longer works as expected after the fix. 0 bytes are saved.

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

-----

1 point by akkartik 5112 days ago | link

Yes, good point. Since the server now processes POST bodies, you won't have access to the unparsed data. I'll update the webupload.arc example.

Update: a few bugfixes later you can see all the different kinds of forms in action here: http://github.com/nex3/arc/blob/cc0f448538/lib/form-tests.ar...

There's still one open question regarding filenames. So far we've been writing uploaded files to random filenames, but the uploaded filename is actually present in the POST request. I see 2 ways to provide it to the programmer (say when uploading a file in a field called file):

a) (arg req "file") returns just the contents of the uploaded file; the filename is in (arg req "filename"). "filename" is the multipart header, so I could just inline all the various headers into the args table.

b) (arg req "file") returns an alist or table with the various headers for that part of the multipart POST. File contents are in ((arg req "file") 'contents).

c) (arg req "file") returns file contents like in a, and other metadata like filename or creation-date is in some other field of req, say req!multipart.

b generalizes to multiple file uploads in a single form, but it's also a little more work to get at form input values. c addresses this but now you've got stuff for a field scattered in multiple places. What do people prefer?

-----

1 point by lark 5107 days ago | link

> Update: a few bugfixes later you can see all the different kinds of forms in action here: http://github.com/nex3/arc/blob/cc0f448538/lib/form-tests.ar...

Does the fnid multipart form test (the second test) and the static multipart test (the third test) work for you?

They do not work for me. I get a "srv thread took too long for <ip address>". I suspect this could be related to nginx again -- though, I started the webserver on a different port and run form-tests through that different port.

How come a non-multipart test works and a multipart test does not? Is there something going on with multipart and ports?

-----

1 point by akkartik 5106 days ago | link

Yeah they work for me. Can you try them without nginx?

It's possible nginx has some settings that are specific to multipart requests..

-----

1 point by lark 5106 days ago | link

You missed my point.

When testing multipart stuff, I start the app in a custom port -- not port 80. So it should not be talking to nginx at all.

Unless there's something in Anarki that makes it talk to it...

-----

1 point by akkartik 5106 days ago | link

Yeah sorry I didn't read your comment thoroughly.

-----

1 point by lark 5106 days ago | link

I tried having nginx stopped and only one Anarki running, and tests two and three do not work for me. Uploading a 7K file runs forever until the thread is killed (srv thread took too long).

-----

1 point by akkartik 5104 days ago | link

Ack, I just tried it with a 3MB file and the server lost connection.

Looks like the static multipart form hangs and times out while the fnid multipart form immediately disconnects. Is that what you see?

:(

-----

1 point by akkartik 5104 days ago | link

A 150k plaintext file works but a 147k binary pdf does not.

Update: The bug has to do with reading in bytes vs characters. Earlier, srv would readc from the POST body unless type was multipart, and your code (like the webupload example) would readb from the body otherwise. Now srv is always the one reading body, and it always reads characters using readc. As a result it gets confused by binary uploads that don't translate to characters.

Update 2: The sentence beginning 'Earlier' is incorrect, and webupload was always using readc as well. I'm not sure what I was looking at.

-----

1 point by akkartik 5104 days ago | link

  $ ls -l bintest
  -rw-r--r-- 1 akkartik akkartik 145974 Jun  5 12:39 bintest
  $ racket -f as.scm
  arc> (w/infile f "bintest" (len:readbytes 200000 f))
  145974
  arc> (w/infile f "bintest" (len:readchars 200000 f))
  141878
readchars does some interpretation but otherwise works fine. However when trying to upload bintest through a socket, it never terminates. Most curious.

Update: I've confirmed that the bytes in the file fail to be encoded as a unicode string, so presumably that's the issue. Another bit of sloppiness is that we're trying to read n characters from the request body where n is the Content-Length in bytes. webupload.arc has always had this problem.

-----

2 points by lark 5104 days ago | link

This seems consistent with me having to use readb rather than readc to get webupload.arc to work before.

-----

1 point by akkartik 5104 days ago | link

Ok, try it out after a git pull.

http://github.com/nex3/arc/commit/b3d97ffc91

Your existing code won't work as is. Since it's meaningless to try to convert possibly-binary data to a string, file contents are now returned as a list of bytes. There's a helper called bytes-string for when you're sure you have just ascii data. Otherwise you'll need to know the encoding of text uploads and convert the bytes appropriately.

I should warn you that it's gotten a lot slower. You might need to temporarily up threadlife. I have some ideas for speeding it up, but let's check first if this works for you.

-----

2 points by lark 5100 days ago | link

lib/form-tests.arc seems to work. It does take over a minute to upload a 415kb file over localhost.

I see the following error:

  Can't coerce  (98 98 83 116 88 110 82 115 98 103 . nil) sym
Should there be a working example in form-tests.arc that uses bytes-string?

-----

1 point by akkartik 5100 days ago | link

Ok, do a git pull for an example.

http://github.com/nex3/arc/commit/46e3820a6b

Can you give more detail on how you ran into that error? I'm sure there are still bugs.

-----

3 points by lark 5100 days ago | link

Here's a testcase:

  (mac usform (n s f . body)
   (w/uniq ga
     `(tag (form method 'post action fnurl* class (string ,s) id ,n name ,n
                enctype "multipart/form-data")
       (fnid-field (fnid (fn (,ga)
                           (prn)
                           (,f ,ga))))
       ,@body)))

  (defop || req
    (usform "this-is-a-bug" "" [ (pr "args are " _) ]
          (tab (row "price: " (input "price"))
               (submit))))

  (def main ()
     (asv))

-----

1 point by akkartik 5100 days ago | link

Ah, this is because the fnid field is being read as a list of bytes. I could convert fnid to string as a special case. Another option is to pass it in with the action url like in aform-multi: http://github.com/nex3/arc/blob/46e3820a6b/lib/srv.arc#L560

Update: Ok, I finally found a way to check when it's safe to convert to string, so now all fields (including fnid) will auto-convert to string when possible. If you get a list of bytes you know it has some sort of non-ascii data.

http://github.com/nex3/arc/commit/d5331ac897

But it's probably made things a tad slower still (let me know). Sucks that reading characters in scheme just hangs when it should throw an error.

-----

1 point by akkartik 5094 days ago | link

Eek, there was still a bug in that. Here's the corrected commit: http://github.com/nex3/arc/commit/1882b3b589

I've also made it faster; 4x faster in my experiments. http://github.com/nex3/arc/commit/16173ec1ff

Do a git pull and give it a whirl.

-----

1 point by akkartik 5106 days ago | link

That is really weird. I find myself momentarily out of ideas :( Maybe some sort of linux setting that controls how ports are opened? Are you running iptables or anything like that?

-----

1 point by lark 5104 days ago | link

Which version of racket are you using? I ran this with 5.2.1.

-----

1 point by akkartik 5104 days ago | link

5.0.2 on Ubuntu. Do you think that could be an issue?

-----

1 point by lark 5106 days ago | link

No iptables. This is indeed strange.

-----

1 point by lark 5112 days ago | link

If b does not break backward compatibility, I prefer b. Else c.

-----

1 point by akkartik 5112 days ago | link

Done. I came up with a way to get the best of both worlds. Multipart request args are now packaged in a table with all metadata, and with the actual body in key "contents". The arg helper is smart enough to deref "contents" in this table, so you can just say (arg req "file") to get its contents. To get at other fields, use the new function multipart-metadata.

http://github.com/nex3/arc/commit/72a6c69b38

-----

2 points by akkartik 5125 days ago | link | parent | on: Generalizing expressions as dataflow

rocketnia, you should really submit your posts here. Don't be shy, it's not like we have a lot to see here. And I find I check Arc Forum a lot more often than Google Reader ^_^

---

It's actually kinda interesting to explore why this is. I have a site as well, but most people don't come there. I need to go where the audience lives: facebook, twitter, G+, arc forum. The readers throng where everybody else is, and where useability is great. RSS promised a collectively-owned common area, but its useability was inadequate. Facebook and G+ have great useability, but it's concerning to give a single entity so much power over our conversations. Is there a way to incent people to build a great experience without owning the venue of conversation?

And why do I not go to Google Reader as much? In the beginning it was because I subscribed to too much stuff[1]. http://readwarp.com has solved that problem for me. Now I only subscribe to low-volume feeds on Google reader; the rest goes into Readwarp so I can sip from it at random. But there's a new problem I'm just starting to grow aware of: even if I have only 10 posts a day on google reader they're all over the map, and the brain sometimes shrinks from the prospect. To go to Google Reader I need to be in an open enough mood. Whereas when I come here (or even when I go to HN) I have a better sense of what to expect.

[1] It seems we rediscover this problem on every new platform (RSS, twitter, facebook, ...): http://akkartik.name/blog/2009-05-19-21-30-46-soc

-----

1 point by kinleyd 5123 days ago | link

Interesting post, akkartik. I still use Google Reader and continue to enjoy it so I guess I haven't subscribed to as much as you had. I agree HN and Arc Forum give more focused subjects and primarily rely on Reader to keep up with version updates, and new blog posts by people who I follow who don't have regular new post patterns. That primarily saves me a long list of visits to different places only to discover there isn't any new.

Is readwarp.com something you set up? It looks good.

-----

1 point by akkartik 5123 days ago | link

Yeah, old-timers here know I like to talk about readwarp :) I built the website in arc. (The feed crawling pipeline is in python, primarily so I could use BeautifulSoup.)

-----

1 point by kinleyd 5123 days ago | link

Cool.

-----

1 point by akkartik 5125 days ago | link | parent | on: Generalizing expressions as dataflow

Sequel: http://rocketnia.wordpress.com/2012/04/29/a-dataflow-syntax-.... I haven't wrapped my head around it all (are linear variables different from linear types?).

FredBrach was thinking about something similar, decoupling scope declarations from variable declarations (http://arclanguage.org/item?id=16147)

-----

2 points by rocketnia 5115 days ago | link

Whoops, I guess I didn't reply to this.

Yes, part of what FredBrach did with scopes was eerily similar to what I was thinking about at the time. ^_^ I didn't talk about it much in response, but I did consider it promising.

I was also thinking about this topic a couple of days before http://arclanguage.org/item?id=15831, where I wrote "Suppose there were a special form (varscope <var> <...body...>) which scoped (<var> <name> <val>) as a special form in the body, which both defines and assigns to a variable and returns the new value. One can say (var foo foo) to define a variable without modifying it."

I think this kind of generalized scope jives well with a linear system. Linearity is about assuming every value is produced once and consumed once by default. Everyday lambda calculus lets you introduce a variable at the acquisition site and then (nonlinearly) use it any number of times in the body. For the reverse of that, when we're dealing with values we use once but define in any number of places (e.g. for set accumulation or nondeterministic choice), maybe it can make more sense to "introduce" the variable at the usage site.

My blog post's "Aside" expressions cater to many-to-many, zero-to-many, many-to-zero, and zero-to-zero operations, where there's no obvious definition or usage site to single out for the variable introduction.

---

"I haven't wrapped my head around it all (are linear variables different from linear types?)."

Hmm, when I said "linear variable," I just meant a variable with a linear value or a linear type. This kind of variable appears in the code at exactly one definition site and one usage site, unless the code uses conditionals or something.[1]

If you're in the midst of changing your question to "are linear values different from linear types?" the answer is no, I'm talking about the same concept. :) However, it is possible to have dynamically checked linear values in an untyped language. Likewise, I bet it's even possible to have linear typing without linear values, in the sense of fostering a programming subculture which considers the types important and the values unimportant.

If you're in the midst of changing your question again, no, I'm not sure why it's called "linear." :-p But in order to rationalize the name myself, I look at the life story of a single value. For an intuitionistic value, this timeline can split apart across multiple parts of the program at once. A linear value can't typically branch, merge, begin, or end, unless it does so by transferring its content into new linear values. This makes its life story linear.

[1] Conditionals don't even exist in the expression language I described in that blog post, and I expect that'll make it less helpful as a language but easier to implement. :/ I haven't come up with a way to add conditionals yet.

-----

1 point by akkartik 5137 days ago | link | parent | on: A story of Common Lisp standardization

The standard libraries have poor names, over-complex interfaces. The eq vs eql vs equal nonsense, for starters (http://arclanguage.org/item?id=13690, http://www.arclanguage.org/item?id=14596). Many standard functions have crappy control abstractions (http://www.arclanguage.org/item?id=13664).

I find many of the interactive features to be over-engineered and/or under-documented. I've never been able to use the condition system to do anything useful. It's not apparent what compiler errors are recoverable and how to recover from them.

I dislike its package management. But this is a general criticism of most languages -- no matter how late-bound the language, there's a consistent temptation toward early binding for performance reasons (see the latter half of http://arclanguage.org/item?id=15587, ie footnote 1. Also http://arclanguage.org/item?id=12057)

Bottomline: while lispers like to say the world hasn't fully understood the power of 70's lisp, it's equally true that Common Lisp is stuck in the 70's and hasn't benefited from ergonomic advances of the past 20 years. It still uses UPPERCASE. These issues are certainly minor compared to having macros. But they carry some weight.

:)

-----


Thanks for posting this! I look forward to playing with an english version at some point.

Also, I'd love to hear more evidence/elaboration for the idea that Basic is easier to learn than Python or Scheme or Javascript.

-----

2 points by mohamedsa 5141 days ago | link

Glad you seemed to like the post!

I don't have empirical evidence, but I think that the advantage of 80s basic for learning is that there are no hidden semantics that need to be known -- what you see is exactly what you get. No parameter passing, no local variables, no call/return (except in gosub, which is presented later), no blocks...Basic is - in a sense - like assembly language with math expressions and graphics.

More modern languages for teaching are usually very high level and focus on how to make cool things with the computer, but IMO they make the computer seem mysterious and complex, since the child knows that if they write that code so and so will happen, but don't understand how it happens. I think a language more resembling the computer's execution model is better to demystify programming.

-----

3 points by rocketnia 5140 days ago | link

"No parameter passing, no local variables, no call/return (except in gosub, which is presented later), no blocks...Basic is - in a sense - like assembly language with math expressions and graphics."

I think this kind of technical simplicity can also be found in concatenative programming (no parameter passing, no local variables, no blocks, no math expressions) or point-free functional programming (no local variables, no blocks, nothing but math expressions).

On the other hand, people who want to try teaching those styles can probably fashion their own library in Factor or Haskell that sets up the foundation they want to teach from. Those languages don't impose a lot of syntactic cruft.

---

"...they make the computer seem mysterious and complex, since the child knows that if they write that code so and so will happen, but don't understand how it happens."

I'd say real understanding isn't so easy to find among programmers.[1] Between JIT, predictive branching, optimizing compilers, and other such supercharging platform features, application programmers have long been willing to sacrifice an easy-to-understand code-to-mechanism mapping in favor of performance, even for imperative code.

Most of our high-level languages do leave things mysterious at some point, and in doing so, they save the programmer from peering into the insanity-inducing complexity underneath. :-p

Imperative languages have mystery as much as other languages, but sometimes their mystery matches up with the "how" questions we raise: "How does the computer begin to show a graphic on the screen" might be taken care of a single "begin to show a graphic on the screen" statement.

I think the "how" questions could be less imperative and more reactive: "How is the computer showing a graphic on the screen?" "How does the system know where the mouse is pointing?" In a reactive programming model, these questions could have corresponding mysterious primitives: "Sustain a graphic on the screen." "Watch where the mouse is pointing." When a programmer builds compound demands in terms of these, what else could it mean but to hold more than one demand at the same time? Concurrency is the intuitive default.

I think a programmer who can't say "how" a program works... simply hasn't built up a significant enough program to be worth explaining. They're still using mainly the primitives, whose "how" is mysteriously unanswered by the language.

Don't get me wrong, I'm happy you're making a language to help people teach programming, and I even think you're on a fine path sticking with the more mainstream and classic kinds of programming. I'm just hoping to debunk some assumptions and expand your horizons, either so you can change your direction or be even more confident where you are. :)

[1] Despite my know-it-all tone, I'm not volunteering myself as a counterexample, lol.

-----

2 points by mohamedsa 5140 days ago | link

I appreciate the horizon expansion attempt :)

When I was talking about "less mysterious and complex" I wasn't thinking about how a real CPU works, but how a programmable machine works.

My idea is that a language too high-level would make the child think of a computer as something having human-like behavior, that the computer just "knows" what to do when you talk to it. On the other hand a programming language with a well-known execution model and few assumptions would mean the child can know early how to map a language's syntax to its semantics.

Let's compare two programming languages for children, Logo and Kalimat. To print "hello" ten times in Logo:

repeat 10 [print "hello]

Here the child has to either assume the computer 'just knows'/be told "you'll understand later", or otherwise has to take some conceptual leaps in order to understand what's really happening:

* The stuff between [] is code not to be executed yet, a description of a future action to be applied

* This code is given to 'repeat'

* 'repeat' would apply this code as many times as needed

Now in Kalimat:

label top

x = 0

print "hello"

x = x + 1

if x<10 : goto top

Assuming the child already knows assignment 'if', and 'goto', the code here can be analyzed, traced, and adapted, without having to know little more than what is already known.

When the student becomes more confident, they can learn about 'for' and 'while', while being told that under the hood, those high-level constructs are pretty much the same as the previous version.

As for the many other points in your comment, I'll happily be re-reading them and pondering :)

-----

2 points by rocketnia 5139 days ago | link

  label top
  x = 0
I think these lines should be reversed. :)

(By the way, here are some terse formatting instructions: http://arclanguage.org/formatdoc. You can edit a post for about an hour after making it if you need to experiment.)

---

"When I was talking about "less mysterious and complex" I wasn't thinking about how a real CPU works, but how a programmable machine works."

Where do you make the distinction? These days I like to think of a machine as being any tool at all, and programming as being a way to control that tool by writing specification documents of what it should do.

If someone begins with no understanding of how a computer could work, maybe it makes things plausible to think there's an underlying mechanical device like a player piano reading a sequence of triggers off of a tape. But rather than focusing on that tape, I'd like to think it's also plausible to ground understanding in terms of writing the blueprints to that machine (parts of which may be tapes if necessary).

http://en.wikipedia.org/wiki/Analogue_computer

http://en.wikipedia.org/wiki/Fluidics

http://en.wikipedia.org/wiki/Electronic_circuit

I won't claim my own intuition works this way, but obviously some people work with this stuff. :-p

---

"Let's compare two programming languages for children, Logo and Kalimat."

I actually think you have apples and apples there and you're not comparing them as such. In Logo, if you want to solve 'repeat 10 [print "hello]' in a more complicated way, you can:

  to greet :numberoftimes
    if :numberoftimes > 0 [
      print "hello
      greet difference :numberoftimes 1
    ]
  end
  
  greet 10
(I just tested this code at http://www.calormen.com/logo/. It's my first Logo code. ^_^ )

I think both this and your Kalimat example involve more conceptual leaps than the "repeat" version. Here are just a few of them:

* The stuff after Kalimat's : or inside Logo's [] is code not to be executed yet.

* This code is given to "if".

* "if" applies this code if appropriate.

Beyond this, the student is challenged to understand user-defined names, arithmetic simplification, operator precedence, sequential execution, and bounded scope (in Kalimat's case, the kind of dynamic scope that arises from variable assignment).

On the plus side, it probably pays off best to choose an appropriate level of challenge for the student, and this is a good milestone (if not starting point), since the concepts are used together to solve a problem in a way that may help clarify all of them at once.

-----

3 points by mohamedsa 5137 days ago | link

In essence, my argument was

* How many concepts must be taught to the child before writing useful programs?

* How many of those concepts are right there, and how many are hidden?

I intuitively feel that Kalimat's example is just more simple and direct than the Logo equivalent, but "intuitively feel" and "just more simple..." are not very scientific; perhaps when Kalimat is more experimented with, we could have more empirical results.

But at least I can try to justify my feelings a little:

* The Logo example seems more nested while the Kalimat one is flatter. "Do this task, then that one" seems easier to keep inside one's head than do this task, which is made out of so and so.

* The Kalimat example completely avoids the need to teach function definition and invocation.

* The Logo code needs a discussion about variable scope and function activations; how :numberoftimes has a value in this greet different from that greet.

Yes, in Kalimat we'd have a related discussion about mutable variables, but a variable is an isolated concept that doesn't need to be explained alongside invocation and scope (at this level).

But again, you've made me look again at my assumptions and those of the Logo creators, and ask myself again and again about those assumptions; and that's definitely a good thing :)

-----

2 points by rocketnia 5136 days ago | link

"The Logo example seems more nested while the Kalimat one is flatter. "Do this task, then that one" seems easier to keep inside one's head than do this task, which is made out of so and so."

Surprisingly, I actually see your point. :-p For languages where "stepping" even makes sense, you can focus on explaining a very narrow window of the program, and then explaining another very narrow window of the program, following those steps. If the language is based on GOTO and simple variables, then the state of the program at any window is of a constant size, rather than structured into a stack or a tree.

Concatenative programming would pretty much lead to a stack-like growing and shrinking state right away (regardless of whether it takes shape as an actual stack).

The kind of programming I guess I'd call combinatoric programming (point-free functional, such as arithmetic expressions) would lead to a tree of partial results.

These systems could probably be broken down into even simpler subsets to limit the complexity, but probably not in a way that feels as open-ended as GOTO.

---

"The Kalimat example completely avoids the need to teach function definition and invocation."

The Logo example avoids the need to teach label definition and jumping. :-p

There's a little hiccup in understanding functions when GOTO's around. When I studied C after Applesoft BASIC, I remember being confused and curious about what would happen if I did a goto from one function to another. That's just an erroneous use of C's goto, and I think when I found that out, I got a little frustrated that anyone would program in a language with such arbitrary limitations. :-p

---

"Yes, in Kalimat we'd have a related discussion about mutable variables, but a variable is an isolated concept that doesn't need to be explained alongside invocation and scope (at this level)."

In case you missed it, the converse is also true: Invocation and scope could be introduced without introducing mutability. I can't say it's easier to do that, but it's at least independent.

Once again, I think there's some negative interplay between concepts here. Classmates from my intro programming classes found it nontrivial to grasp that passing a value to a function wasn't just the same thing as assignment to its parameter. That flawed interpretation worked pretty well until recursion came along. Even then, it was easy to repair the understanding by assuming it's a temporary assignment that reset itself after the function ended. Then they'd run into trouble when lexical scope came along, but... you know, that never came along. XD

Once I was out of the intro programming classes, they switched the intro curriculum from Java to Python. That means lexical scope might actually become relevant to the students before they learn another language... so I'm actually very glad in hindsight.

Eep, I'm not sounding like a history lesson of some sort, so for reference, my college days were 2005-2009. >.>; Pretty recent... but not as recent as I'd like, lol.

---

"But again, you've made me look again at my assumptions and those of the Logo creators, and ask myself again and again about those assumptions; and that's definitely a good thing :)"

Yeah, hopefully you feel justified in whatever you settle on. ^_^ Nothing's without downsides to me, so I don't tend to do value judgments, just a stream of personalized suggestions.

-----

3 points by jsgrahamus 5140 days ago | link

I liked the fact that you included easy graphics in your language. That is a way to immediately involve the student in a medium that they presumably already like and are acquainted with.

It reminds me of Logo, which was also targeted at youth, and included graphics primitives. I used it myself to create some drawings to help us hang pictures :=)

I wonder about some of your choices. The Goto in 1 example reminds me of spaghetti coding, and probably some of the early programs I wrote. Logo, on the other hand (http://en.wikipedia.org/wiki/Logo_%28programming_language%29), included other branching commands which lend to better programming practices. It also included parameter passing, etc. Might be good to take a look. Children are capable of amazing things.

-----

2 points by mohamedsa 5140 days ago | link

I decided when writing Kalimat to enable a teacher - if they so choose - to remove all sorts of abstraction and write code that resembles how it's going to run. When dealing with a child I think learning to code is more important than learning to code well, and removing all obstacles for that is a good design choice.

This way, the student can go directly into writing interesting programs using only if/goto/expressions. Later they can learn more disciplined forms like procedures or OOP, also included.

But I don't want to impose my ideas on users; if the teacher disagrees with me, they can completely ignore goto and use other control structures!

-----

1 point by akkartik 5143 days ago | link | parent | on: Using arc on production system

I built a full-time website with it for a year (temporarily down). But it didn't use a database, just flat files like HN.

-----

1 point by jsgrahamus 5142 days ago | link

I'm wondering about something which might have high document storage requirements. I guess it would have to be web-based (don't know how exactly one would integrate arc with another GUI environment).

I know I've seen comments here about integrating it with a db. Anyone remember the article # or URL?

Thanks, Steve

-----

2 points by thaddeus 5142 days ago | link

Here are some options for you:

Option 1. Use Fleetdb.

http://fleetdb.org/. Here's some rudimentary code to make this work with anarki: http://blackstag.com/fleetdb-client.arc

Option 2. Use any database that supports an HTTP interface.

You can use the anarki http library here: https://github.com/nex3/arc/blob/master/lib/http.arc along with something like Riak or Couchdb, etc.

Option 3. MySql over HTTP:

http://code.nytimes.com/projects/dbslayer

Option 4. Get the FFI working to access MySql and other more traditional database systems.

Some people had been working on the FFI interface, but so far I've yet to see anything that works. You can read my noob entry about this here: http://arclanguage.com/item?id=10839 and there were recent attempts by others as well.

-----

1 point by jsgrahamus 5142 days ago | link

Some good ideas. Thanks, Thaddeus. Steve

-----

1 point by evanrmurphy 5143 days ago | link

Readwarp?

-----

1 point by akkartik 5143 days ago | link

Yup

-----

1 point by akkartik 5135 days ago | link

It's back up but quite slow and flaky because it has a lot less RAM now: http://readwarp.com.

-----

2 points by akkartik 5144 days ago | link | parent | on: Apologies for the frequent downtime

I'm curious: who are these people you're showing arc to and how do you find them on a regular basis? ^_^

-----

2 points by zck 5143 days ago | link

Generally, I'll try to show it to some of my programmer friends, or to people at work.

I had actually come up with an interview question: take two strings and return the index of the first difference. I posted some arc code, and wanted to have people run it.

My code:

  (def first-difference (seq1 seq2 (o comparer is))
       "Returns the index of the first difference between seq1 and seq2, or nil if
        they are the same. This function uses 'comparer as the function to compare
        elements of the sequences."
       (withs (len1 (len seq1) ;; 'withs ([var1 val1]* ) binds each var to its val
               len2 (len seq2)
               helper (afn (seq1 seq2 pos) ;; 'afn makes an anonymous function that
                                           ;; can recurse by calling 'self
                           (if (is len1
                                   len2
                                   pos) ;; at end of both sequences
                               nil
                             (or (is len1
                                     pos)
                                 (is len2
                                     pos)) ;; at end of only one sequence
                             pos
                             (~comparer seq1.pos
                                        seq2.pos) ;; found a difference
                             pos
                             (self seq1 seq2 (+ pos 1)))))
               (helper seq1 seq2 0)))

-----

2 points by akkartik 5143 days ago | link

Nice example. For exposition is it useful to simplify the problem by focusing on lists?

  (def first-difference(seq1 seq2)
    (if (or no.seq1 no.seq2
            (~iso car.seq1 car.seq2))
      0
      (+ 1 (first-difference cdr.seq1 cdr.seq2))))
Edit: no, that's not right, because it doesn't return nil on identical sequences. Here are two variants with and without the accumulator. Which is easier for noobs?

  (def first-difference(seq1 seq2 (o pos 0))
    (if
      (and no.seq1 no.seq2)
        nil
      (or no.seq1 no.seq2 (~iso car.seq1 car.seq2))
        pos
      'else
        (first-difference cdr.seq1 cdr.seq2 (+ pos 1))))

  (def first-difference(seq1 seq2)
    (if
      (and no.seq1 no.seq2)
        nil
      (or no.seq1 no.seq2 (~iso car.seq1 car.seq2))
        0
      'else
        (only.+ (first-difference cdr.seq1 cdr.seq2) 1)))
 
  (test-iso "" 0 (first-difference nil '(34)))
  (test-iso "" nil (first-difference '(1) '(1)))
  (test-iso "" 1 (first-difference '(1) '(1 2)))
  (test-iso "" 3 (first-difference '(1 2 3 4) '(1 2 3)))

-----

1 point by jsgrahamus 5142 days ago | link

  arc> (def 1st-diff (str1 str2 i)
    (if (or (is str1 "") (is str2 ""))
      0
      (or (> i (- (len str1) 1)) (> i (- (len str2) 1)))
      i
      (no (is (str1 i) (str2 i)))
      i
      (1st-diff str1 str2 (+ i 1))))
  #<procedure: 1st-diff>
  arc> (1st-diff "" "" 0)
  0
  arc> (1st-diff "" "" 0)
  0
  arc> (= astring "abcde")
  "abcde"
  arc> (= bstring "abced")
  "abced"
  arc> (1st-diff "" "" 0)
  0
  arc> (1st-diff astring "" 0)
  0
  arc> (1st-diff "" astring 0)
  0
  arc> (1st-diff astring bstring 0)
  3
  arc> (1st-diff bstring astring 0)
  3
  arc> (1st-diff astring "abcdef" 0)
  5
  arc> (1st-diff "abcdef" astring 0)
  5
  arc>

-----

1 point by jsgrahamus 5142 days ago | link

It's interesting to compare this to the solution in my workday language. One of the reasons that M is shorter is because its functions are more tolerant. For arc, I had to make sure that the index to the string was not too large. In M, it simply returns an empty string if you attempt to access a string position beyond the length. And I used iteration with M. Perhaps the arc solution would have been shorter had I done the same there.

Interesting exercise for this newbie.

-----

2 points by akkartik 5142 days ago | link

Yeah you can index past the end in my arc variant (http://github.com/akkartik/wart). But it currently returns nil. What should it be?

I'd love to see the M version expanded into english.

-----

2 points by jsgrahamus 5142 days ago | link

Great.

I'm used to seeing it as the empty string

Here is the expanded M version (! = LF):

  IF X=Y WRITE !,0 ; No difference
  ELSE  FOR I=1:1 IF $EXTRACT(X,I)'=$EXTRACT(Y,I) WRITE !,I QUIT

-----

1 point by akkartik 5141 days ago | link

I still don't know enough MUMPS to follow it :)

Are you concatenating strings? Is that where returning "" is useful? In wart concatenating strings is tolerant of non-strings, so I think returning nil should be ok.

-----

2 points by jsgrahamus 5140 days ago | link

If the string X = the string Y, print 0 (to indicate there are no differences / MUMPS uses 1-based indexing of strings)

Otherwise, go through each character of the strings and at the point where they differ, print that index and quit looping.

MUMPS does not error out on attempting to extract a character beyond the string's length. So in that event, 1 string's extract will return the empty string, which will differ from the other string's extract and will cause you to print the index and quit. Not generating such an error, of course, cuts down on the code needed.

-----

2 points by zck 5141 days ago | link

So if we enter the for loop, we know there is a difference between the two sequences.

So we just need to find an index position i where (isnt seq1.i seq2.i) is true. If indexing off the end of a string causes an error, we need to put in code to prevent that. If indexing off the end of a string returns anything that can't be an element of the string, we can compare each index i until we find a difference, knowing that we'll eventually find one.

-----

1 point by jsgrahamus 5140 days ago | link

Correct.

-----

1 point by jsgrahamus 5142 days ago | link

test-iso?

-----

1 point by akkartik 5142 days ago | link

https://github.com/nex3/arc/blob/ec47676f99af11b6fa8935a3cb0...

-----

3 points by jsgrahamus 5143 days ago | link

In my work language:

  I X=Y W !,0 ; No difference
  E  F I=1:1 I $E(X,I)'=$E(Y,I) W !,I Q
A challenge for me is arc/lisp/scheme's verboseness.

-----

1 point by akkartik 5143 days ago | link

What is that, J?

-----

1 point by rocketnia 5143 days ago | link

I think you may already have an inkling somewhere in the back of your mind.... http://arclanguage.org/item?id=15341

-----

1 point by akkartik 5143 days ago | link

Lol.

-----

1 point by jsgrahamus 5142 days ago | link

MUMPS / M / Caché - Used it for most of 30 years. In the U.S. mostly used on medical information systems, however it is also used in banks, credit unions, Ameritrade, etc.

-----

3 points by akkartik 5144 days ago | link | parent | on: Apologies for the frequent downtime

Hells yeah!

-----

More