Arc Forumnew | comments | leaders | submitlogin
2 points by akkartik 3763 days ago | link | parent | on: An alternative worldview to 'modularity'

Yes, that's exactly the trade-off I've been thinking of. When I program I like to start out writing tests at the start when I don't yet know what properties I should try to enforce. Over time I start finding places where tests aren't good enough, and start feeling the need for more rigorous checking. But by then I've painted myself into a corner with my choice of language, and it's too hard to switch to a more rigorous platform.

In Mu my plan to break this dichotomy between coverage and soundness is to allow arbitrary metadata to be attached to instruction operands. As a result you aren't stuck with the type annotations and checking that I build into the system. You can have arbitrary metadata and will be able to write programs that can deduce and check this metadata to enforce all sorts of properties. Arbitrary checkers feels to me like a generalization of Lisp macros.

2 points by akkartik 3763 days ago | link | parent | on: An alternative worldview to 'modularity'

Yes, I wanted to say something similar in response to your phrasing that "type systems (including dynamic and static type systems) are another way to carve out the territory". When you say "dynamic type systems", in particular, I think Python and Ruby, and the modularity guarantees there seem much inferior to tests. But Haskell and the more advanced type systems might well disprove my thesis.
2 points by Pauan 3764 days ago | link | parent | on: An alternative worldview to 'modularity'

Oh, and I completely forgot about typeclasses.

Typeclasses are amazing. They're similar to multimethods, but more powerful, and they have the same performance as regular functions.

You can use them to solve the expression problem, giving you a huge amount of flexibility, power, and conciseness. Or you can use them to create dynamically scoped variables which actually work correctly (unlike dynamic variables in most languages).

The thing is, typeclasses are only possible with static types. They're the main reason I changed Nulan to be statically typed.

----

By the way, I would like to point out that the static type systems in languages like Haskell is very different from the static type systems in other languages like C, C++, or Java. So even if you like/dislike one of them, you might end up liking the other. I used to lump all of the static type systems together, but I think that's a mistake.

There are bugs which are not caught by C, C++, or Java, but which are caught by Haskell. And there are programs which do not type check in C, C++, or Java, but which do type check in Haskell.

2 points by Pauan 3764 days ago | link | parent | on: An alternative worldview to 'modularity'

That sounds about right. Static types can only guarantee a certain subset of behavior. Unit tests work for a huge amount of behavior, because they run at run-time. They are very versatile. But there are still some things that unit tests cannot test.

In particular, unit tests cannot guarantee the absence of behavior. As an example, Haskell can use its static type system to say, "this function (and any functions it uses) is guaranteed to be pure". Or you can say, "this function (and any functions it uses) is guaranteed to only use certain side effects, but other side effects do not happen".

Languages like Idris go even further and can guarantee that a function terminates (never goes into an infinite loop)!

As another example, Haskell has Software Transactional Memory, and it uses the static type system to guarantee that arbitrary I/O cannot occur, which is important because the transaction might be retried multiple times.

I don't remember which language it was in, but I remember reading about a language that used static types to guarantee that exceptions are always handled. In other words, you will never have an uncaught exception.

Nulan uses its static type system to make a distinction between blocking impure functions and non-blocking impure functions. In JavaScript, there's a big difference between blocking and non-blocking, both in terms of behavior and performance. By using the static type system, it's possible to say "this function (and any functions it uses) is always blocking, and never non-blocking".

As you are aware, Rust uses its static type system to guarantee that pointers are always used correctly, and that data races cannot occur, even with concurrency.

But the biggest reason I like static types is because they make refactoring easier. Let's say you have a type that represents a person. So you have a name property, age property, etc.

Now let's say you want to add in a new property. Some of the existing functions will need to change to accommodate this new property. Without static types, you need to carefully go through your entire code base and find the functions which use the type, and then change their behavior to work with the new property.

Unit tests won't help you with that, because the unit tests are all testing the old properties, not the new property. So in the end, you have no choice but to just bite the bullet and spend a significant amount of time searching through the code and making the changes.

But with static types, it's as simple as adding the new property, compiling, fixing any type errors, compiling, fixing any type errors, etc.

Each type error tells you the file and line where the error occurs, making it easy to fix. You don't need to search through your code to find the places which need fixing: the type system does that for you. And after all the type errors are fixed, it's basically guaranteed that your code is correct.

Another example: a function used to always return a result, but now you need to change it so that it can potentially fail. So anybody that uses that function now has to account for the possibility of failure. With a static type system, you simply make the change and fix the type errors.

With unit tests, you better hope that you have 100% code coverage, because you're going to need it to find all of the functions which need to be changed. And don't forget that the unit tests need to be updated, and you probably need to add in new tests as well. That takes time, in addition to the time spent fixing the code.

So the end result is that it's faster and easier to refactor with a static type system, and it's more likely to be correct as well.

So I think static types and unit tests complement each other: they can both do things that the other cannot. Static types can make guarantees that unit tests cannot, and static types also remove some of the burden from unit tests, which speeds up development without sacrificing correctness.

On the other hand, unit tests can make certain guarantees that static types cannot. So in the end, I think you need both.

P.S. Static typing also helps tremendously with things like smart IDEs. I think Unison is a really cool example of that:

http://unisonweb.org/

2 points by akkartik 3764 days ago | link | parent | on: An alternative worldview to 'modularity'

Yes, you're right. I'm not very familiar with Haskell, but I was thinking about it when I called the title "An alternative.."

I think of Haskell as being to tests what up-front design is to Agile. It's great if you're trying to carve out right and wrong behaviors along the dimensions it's been designed to help with (and my vague sense is that linear types and other such advances are trying to expand that set of dimensions). But how would you ensure that your program meets its performance guarantees? Or is guaranteed not to block between these two memory barriers? Tests are less rigorous and more work, but if you're willing to put in the elbow grease they'll work for anything you throw at them.

2 points by Pauan 3764 days ago | link | parent | on: An alternative worldview to 'modularity'

I would like to point out that type systems (including dynamic and static type systems) are another way to carve out the territory.

Especially in languages like Haskell, static types are used all over the place to define intentions and boundaries. Languages like Idris and Agda go even further, with dependent types and proof systems.

Unit tests are great, but there are many useful properties that unit tests cannot test, but static type systems can test. And vice versa: there are things that unit tests can test that static types cannot.

Also, things like QuickCheck[1] allow us to encode our intentions in very concise ways (much more concise than traditional unit testing).

Rather than saying this:

  (reverse (reverse [])) is the same as []
  (reverse (reverse [1])) is the same as [1]
  (reverse (reverse [1 2])) is the same as [1 2]
  (reverse (reverse [1 2 3])) is the same as [1 2 3]
  (reverse (reverse [3 2 1])) is the same as [3 2 1]
  (reverse (reverse [1 2 3 4 5])) is the same as [1 2 3 4 5]
  ...
You instead say this:

  for every list, (reverse (reverse list)) is the same as list
And it will then generate the unit tests for you.

So, in general I agree with you. I just disagree with the idea that unit testing is the only way we have to specify our intentions. There are many ways, and we should use every useful tool which is available to us.

I think that the more we can specify our intentions (in a testable way), the better our programs will be.

----

* [1]: https://en.wikipedia.org/wiki/QuickCheck

2 points by Pauan 3764 days ago | link | parent | on: ASK: Axioms of Arc?

It's far more than 19.

13 types + 5 special forms + 1 global variable + 92 built-in functions.

And that's not including things such as first-class functions, lexical scope, first-class continuations, tail call elimination, or macros. I don't even know how to count those.

The fact is that practical programming languages need a lot of axioms. If you look at the list of built-in functions, they are all useful, and most of them are for I/O, which cannot be written in an axiomatic way.

I think it would be interesting to write an "eval" function in Arc, and see how few functions it needs in order to accomplish it. But I don't see much practical benefit to it.

1 point by kinnard 3765 days ago | link | parent | on: ASK: Axioms of Arc?

Very thorough. That's 19 though, and some of them don't seem very axiomatic.
4 points by Pauan 3765 days ago | link | parent | on: ASK: Axioms of Arc?

I took a look through ac.scm, and here are the axioms I found:

* These built-in types: cons sym fn char string int num table output input socket exception thread

* Function calls

* First class lexically scoped functions, including closures (this one has a very complex implementation)

* Required function arguments, optional function arguments, rest function arguments, list destructuring

* Tail call elimination

* Macros (which are just functions annotated with a type of 'mac)

* First-class undelimited continuations (this one has a very complex implementation)

* These special forms: quote quasiquote if fn assign

* Special compiler optimizations for these functions: compose complement andf

* Ssyntax ~ : & (which expands to complement, compose, andf)

* Ssyntax . ! (which expands to function call, function call + quote)

* [ _ ] partial application

* Racket S-expression syntax () [] . ' ` , @ " ; #\ #| |#

* If enabled, you can use atstrings like "foo@bar" which is equivalent to (+ "foo" bar)

* These built-in functions: is < > + - * / mod expt sqrt apply cons car cdr err len annotate type rep uniq ccc infile outfile instring outstring inside stdout stdin stderr call-w/stdin call-w/stdout readc readb peekc writec writeb write disp sread coerce open-socket socket-accept setuid new-thread kill-thread break-thread current-thread sleep pipe-from table maptable protect rand dir file-exists dir-exists rmfile mvfile macex macex1 eval on-err details scar scdr sref bound newstring trunc exact msec current-process-milliseconds current-gc-milliseconds seconds client-ip atomic-invoke dead flushout ssyntax ssexpand quit close force-close memory declare timedate sin cos tan asin acos atan log

* These built-in variables: sig nil t

* Threads and atomics

* Sockets and file I/O

* Exception handling (err and on-error)

I might have missed a few things, but that covers most of it.

2 points by kinnard 3767 days ago | link | parent | on: Arc implementation in C++

I found this to be very approachable, much more approachable than the scheme ac.scm, but that's probably because I'm new to/not used to reading lisp.
2 points by kinnard 3767 days ago | link | parent | on: ASK: What is Lisp Enlightment?

That makes sense.
2 points by kinnard 3767 days ago | link | parent | on: Lisp is more low-level than C

This makes me want to learn more level programming . . . but not while I'm learning lisp.
5 points by rocketnia 3767 days ago | link | parent | on: WHY RACKET? WHY Lisp?

This starts out as a pretty great article. By the end, I think the author's enthusiasm belies their goal to avoid baseless Lisp flattery.

Why does the article say it's nice to have lists-as-syntax, but then say Racket's syntax transformers are even better?

Why does the article say it's nice that X-expressions reveal "the sequential nature of the data elements" of a document, but then say it's nice that Scribble lets you embed Racket expressions at arbitrary places in your document?

IMO, all these things are nice options to have at the same time, and maybe that's what the author thinks too. It would be nice to hear more concrete reasons for preferring these features though, especially since concrete reasons are what the article is trying to offer.

---

"The prob­lem with Lisp flat­tery is that it makes sense only to ex­pe­ri­enced Lisp pro­gram­mers. To oth­ers—es­pe­cially those who are try­ing to de­cide whether to learn and use a Lisp—it just comes across as un­sub­stan­ti­ated hoodoo."

This is true, but certain kinds of experience bring the hoodoo feeling back. When I see the elegance of computational trinitarianism, it makes me want to use algebraic data types instead of s-expressions to represent program structure. Some programming languages are absolutely in the axiomatic style, even in the sense that they're specified fully as a half-page diagram with lots of Greek letters all over. Coming back to a Lisp, everything's messy again, and it makes it really hard to swallow the hype.

I'd say there are some specific benefits that Lisps have to offer, but people easily confuse these benefits with other accidental properties of the languages.

Most of the historic advantages of Lisp have already been plundered by other languages. What does "Lisp" even mean now that garbage collection, first-class anonymous functions, interactive debuggers, run time code loading, and package managers are available for basically every popular language?

Does "Lisp" just mean macros? Python, Groovy, Idris, Agda, Haskell, OCaml, etc. each have some variation of macros or AST manipulation, but does that make them Lisp dialects? What about cases like JavaScript and Ruby, which have extensive libraries for parsing and manipulating their own code but have no support built into the language?

Angular.js watches for changes to your first-class environments and uses them to re-macroexpand the DOM tree. That's pretty Lispy!

You might say that the difference isn't technical but cultural. Well, Java "managed to drag a lot of [C++ programmers] about halfway to Lisp." JavaScript was explicitly "Scheme in the browser" until it was dragged about halfway to Java. I think there's a case to be made that, culturally, these languages were Lisps doing the best they could.

Do we call them Lisp dialects? I don't think we do. I think we only call something a Lisp dialect if its syntax is structured as lists of lists and if it's not a Forth dialect.

So what are the concrete benefits of lists as syntax? I use them for specific reasons:

- If I were stranded on a new platform or otherwise compelled to reinvent a good syntax from a limited set of tools, it would be pretty easy to start off with parens. This is due to the sequentiality of text as a code representation format. Parens let us conceptually break up a piece of text into a bunch of text-with-holes-in-it pieces joined together. In other words, trees of interpolated strings. Lists are an extreme case of interpolated strings where there is no string left over; it's just some abstract sequential format consisting entirely of holes. I tried using interpolated strings as syntax, and the string-parsing operations made macroexpansion too inefficient, so I settled on lists as a consolation prize.

- When every syntax is a list, it doesn't matter if the syntax is standard or custom. Every syntax looks just as good or bad as every other. This might help people adopt new, innovative operators they might have scoffed at in another language. Racket is an example of a project that cultivates a variety of innovative syntaxes to coexist all at the same time, such as Typed Racket, web server's stateless servlets, and the legacy r5rs, r6rs, and mzscheme languages.

Those are the advantages of parens to me. I'm currently extrapolating on the same principles in my designs for comprehensive s-expression quasiquotation and string quasiquotation. Adding quasiquotation to s-expressions is a lot like adding parens to flat text. :)

2 points by akkartik 3767 days ago | link | parent | on: Lisp is more low-level than C

This post has spooky timing considering I just mentioned that PG quote: http://arclanguage.org/item?id=19484
3 points by akkartik 3768 days ago | link | parent | on: WHY RACKET? WHY Lisp?

I mostly agree with this about how lisp advocacy can be misleading. My one quibble is with his deemphasis of macros. I think they're only #8 on his list because he's been using Racket. A more fluent system for macros makes them far more broadly applicable.

I absolutely agree that "everything is an expression" deserves to be #1. Rust is the first non-functional language to steal this idea: http://lucumr.pocoo.org/2012/10/18/such-a-little-thing. That probably explains Rust's relative success with macros as well. My big insight doing Wart was to realize that "homoiconicity" was really nothing more than "everything is an expression". There's nothing special about lists in particular besides that.

3 points by akkartik 3768 days ago | link | parent | on: ASK: What is Lisp Enlightment?

I think the "enlightenment" happened when the only other language people had programmed in was at the level of Fortran or C. Here's another quote in response:

"There have been two really clean, consistent models of programming so far: the C model and the Lisp model. These two seem points of high ground, with swampy lowlands between them. As computers have grown more powerful, the new languages being developed have been moving steadily toward the Lisp model. A popular recipe for new programming languages in the past 20 years has been to take the C model of computing and add to it, piecemeal, parts taken from the Lisp model, like runtime typing and garbage collection." (http://www.paulgraham.com/rootsoflisp.html)

At this point it's only macros that separate Lisp from other languages (though languages like Rust have been trying to steal that as well). So don't go looking for enlightenment. Everybody's trajectory is different.

2 points by kinnard 3769 days ago | link | parent | on: Anarki is now a Racket package

Glad to be of service.

I'm still learning lisp and haven't fully wrapped my head around macros. So the hygienic vs unhygienic debate is still more or less opaque to me :D
5 points by akkartik 3769 days ago | link | parent | on: Anarki is now a Racket package

This is glorious. Thanks rocketnia for doing this, and thanks kinnard for repeatedly but politely pushing for it.
3 points by rocketnia 3769 days ago | link | parent | on: Anarki is now a Racket package

Starting Anarki at the command line hasn't changed if you're using the "arc" script. If you're invoking boot.scm yourself (e.g. if you're on Windows like me), the command has changed a bit.

Before:

  $ racket -f boot.scm -e "(tl)"
After:

  $ racket -t boot.scm -e "(tl)"
Unfortunately, the Racket package builder wants to compile every .scm file it finds in the file tree, and it expects all of those to have a #lang or a (module ...) form surrounding the whole file. I had to change boot.scm to make that work. I tried several ways to make "-f boot.scm" work, but "-t boot.scm" was the closest I could get.
2 points by rocketnia 3769 days ago | link | parent | on: Anarki is now a Racket package

Clickable links:

[1] https://github.com/arclanguage/anarki/issues/40

[2] https://pkgs.racket-lang.org/

2 points by kinnard 3773 days ago | link | parent | on: π-calculus FAQ

TLDR: π-calculus is to concurrent programs as λ-calculus is to sequential programs.

"The new language construct is much more lightweight than any code we might have written purely at the application level, even with Lisp macros."

The new language construct...

  -(def editor ((o u the.me))
  +(def editor ((t u me))
As language changes go, this is only slightly better than the change of using a short name.

Like, every time you type "fn" you could have typed "lambda", and that is indeed a certain kind of inconvenience.

In this case, every time you type "(t" you could have typed "(o" and "the." instead. So it has the slight benefits of a shorter name, and it has the added benefit that it makes you move your cursor less. (On the other hand, if you're refactoring an existing use of "(o", it makes you move your cursor more.)

This is the kind of thing I consider macro-expressible, rather than a language innovation. Libraries should be able to provide this. And in fact they can; my Lathe libraries have customizable pattern matching support already.

Yeah, Arc macros can't update the built-in pattern-matching syntax this way. My library does it by implementing its own special version of (fn ...) and such. For Hacker News in particular, it might have been easier to add (t ...) to the language rather than to overhaul the design of pattern-matching throughout the codebase to be extensible.

---

"You still have to declare which variables you plan to use in each function signature, so it's possible to look at the function and know exactly what state it depends on."

That's not even true. A call to (user-name ...) still depends on the state of the.me, and that's no longer visible in the signature.

It worries me that static knowledge would be used as a justification for this technique when the knowledge isn't even sound. That's where we get arbitrary language restrictions that have no real benefit.

I hope this static knowledge is meaningful in some weaker way that just wasn't quite articulated here.

---

To the author: I'm sorry I'm railing on this. I appreciate the information about how Arc's doing.

3 points by hjek 3774 days ago | link | parent | on: ASK: How does sml differ from sxml

SML seems a bit terser than SXML since attributes aren't wrapped in parenthesis and don't use an @.

SML example from your link:

(tagname attr "value" attr2 "value2" (tagname2) (tagname3 "data"))

SXML example from your link:

(tag (@ (attr1 "value1") (attr2 "value2")) (nested "Text node") (empty))

2 points by hjek 3774 days ago | link | parent | on: ASK: Best way to learn Lisp?

MIT have some good freely available videos on Lisp, where they go through a lot of philosophical stuff: https://archive.org/details/mit_ocw_sicp
2 points by hjek 3774 days ago | link | parent | on: ASK: Best way to learn Lisp?

If you already know Javascript, you write that with LISP syntax: https://github.com/anko/eslisp

That might reduce the entry barrier to building something.

2 points by hjek 3774 days ago | link | parent | on: NEWBIE:: new to Lisp, new to Arc.

You might want to give Welder a look.

Welder is an arc IDE bundled with Rainbow (an arc implementation in Java that is complete enough to run News).

https://github.com/conanite/rainbow


With the current implementation of Arc, it is possible but rather tricky to write hygienic macros.

However, there is a very simple change that can be made to the Arc compiler which would make hygienic macros easy to write.

The Arc compiler would have a big hash table which maps from global symbols to gensyms.

Whenever you define a global variable, it will create a new gensym and store it in the hash table.

When using the quasiquote syntax, it will lookup the variable in the hash table and will use the gensym rather than the symbol.

As an example, if you have this Arc program:

  (= foo 1)

  (mac bar ()
    `(+ foo 2))
The Arc compiler would expand it to this code instead:

  (= #<foo:1> 1)

  (mac #<bar:1> ()
    `(#<+:1> #<foo:1> 2))
In this case, #<foo:1>, #<+:1>, and #<bar:1> are gensyms, rather than ordinary symbols.

Now the macro bar will always expand to the correct global variable foo, and therefore it is 100% hygienic.

If you want to intentionally break hygiene, you can simply do this:

  (mac bar ()
    `(+ ,'foo 2))
The above code uses "," to insert the symbol "foo", rather than the gensym for foo.

This change is very easy to make in the Arc compiler, and it only requires a few minor changes to the language.

2 points by claytonkb 3775 days ago | link | parent | on: ASK: Best way to learn Lisp?

I think Arc is perfect for what you want - it was designed from the ground-up for "exploratory programming". Arc not only preserves its Lisp roots "under the hood", it also captures the spirit of Lisp, IMO. It clears away what I consider to be syntactical pollution in, say, Common Lisp.

Start with the foundation:

https://arclanguage.github.io/ref/

Lisp has a bad reputation for being difficult; the most difficult thing about Lisp is the concept of quotation (and quasi-quotation). At the REPL (http://tryarc.org/):

  > (list 1 (+ 1 1) 3)  
  (1 2 3)
This list contains a sub-list - (+ 1 1) - which is itself evaluated to give the final list that Arc returns.

  > (list 1 '(+ 1 1) 3)  
  (1 (+ 1 1) 3)
Putting a single-quote in front of the sub-list causes the interpreter to not evaluate that list but, instead, to return it in quoted form, exactly as-is.

  > '(list 1 (+ 1 1) 3)  
  (list 1 (+ 1 1) 3)
We can quote the whole list if we like, in which case, the whole list is returned as-is. Quotation is "blocking", that is, once the interpreter hits a single-quote, it stops evaluating the quoted list and any sub-lists within it.

  > `(list 1 (+ 1 1) 3)  
  (list 1 (+ 1 1) 3)
The back-tick is the quasi-quote operator and has a similar effect as quote when applied to a whole list. However, it acts differently when we apply the unquote operator (comma) to sub-lists:

  > `(list 1 ,(+ 1 1) 3)  
  (list 1 2 3)
Here, the whole list was quoted but the interpreter did not stop traversing the list, searching for unquoted lists. When found, the unquoted list was evaluated.

Keeping track of exactly when/where you need to use quotation or quasi-quotation can sometimes be challenging. Everything else about Lisp is completely straightforward, that is, no more difficult than any other, more common, language.

2 points by GenArx 3778 days ago | link | parent | on: NEWBIE:: new to Lisp, new to Arc.

all the steps above work out just fine.. thank you for the hand Sir. Now , how may i compile sample programs or test if its running ? ^^

I have also uploaded a screenshot of the process at facebook.com/EPSpost for newbie reference, too.

More