Presenting my educational language, and a special feature in its design: how it's set in multiple layers of ease of learning: primitive imperative, structured, OOP, and then more advanced stuff like concurrent programming.
The concept isn't new, since it's kind of like Racket, but Racket starts at once with things like expressions and blocks, while Kalimat starts at a much simpler level that looks more like QBasic.
I really admire your attitude. A few months ago I started learning CL (I don't know any other language well) but got discouraged quickly (not by its syntax - I loved it and the brevity of the language) but by the fact that my reasons for learning a language are unfortunately slightly more pragmatic. Then I went back to python but I don't enjoy it as much as I did with CL. I know it may sound like a cliche but (even for a Lisp newbie) I could its beauty. Then I read about Arc so thought it might be the best of both worlds - Lisp and modern. In all honesty, the fact that Paul Graham hasn't officially updated it for a while is not encouraging. As much as I admire your attitude to it, I think I need to think more practically in terms of languages:(
In layman terms (I'm still a Lisp newbie) how would you describe differences between Arc and CL. What has improved? I know it has been cleaned of unnecessary bits.
>> If unit tests are as great as I say they are, do we need lexical scope?
Very very interesting. Unit testing.. This is such an engineering concept. Why not built in the language with meta tags (I don't know if it's possible at all)?
>> Without them changes may break seemingly distant, unrelated code. Something to think about.
Let's try the fun of an extreme code expansion language without any compromise :)
Some of you were right, there is a sensible problem in names/scope I've not expected. But I've the answer to everything :)
Libraries.
What are libraries? There are application foundations. In other words, applications are built on top of libraries.
So let's make it as it should.
A libraries is a function which takes in arguments an other libraries or an end application.
Let loadlast be a function which bind to a symbol the eval of the last instruction of a file. And let use the arc evaluation syntax.
App.ext:
////////////// app.ext ////////////////////
(loadlast '"lib1.ext" 'MyLib1)
(loadlast '"lib2.ext" 'MyLib2)
(loadlast '"lib3.ext" 'MyLib3)
(= 'MyApp
'(*put your application here*))
(MyLib1 '(MyLib2 '(Mylib3 MyApp))) ; This launches the whole
MyApp ; that makes MyApp a lib. MyApp is working with MyLib1, MyLib2 and MyLib3 and thus must be embed at least on top of a stack which contains them.
Lib1.ext:
///////////// lib1.ext ////////////////////
{
*blabla*
{
arg1; it evaluates (MyLib2 '(Mylib3 MyApp)) which can now use lib1 via the dynamic scope system
}
*blabla*
}
Oh sorry if I've not been clear: I'm in favor of dynamic scope :)
>> The argument that lexical scopes are entangled with our notion of functions, so let's drop them since they're not an orthogonal concept
Exactly. In the fun exploration of an ultimate language for good programming, name conflicts should not drive the language design at all.
Programmers should manage their name spaces with care. Also, having a tool for this, like namespaces, is not a problem. Seems even pretty good and it fixes everything.
>> That's a lot like PicoLisp. In PicoLisp, functions are just lists:
Functions are lists of instructions/operations you can call and re-call. In every languages of the world. - meaning there is no reason they are treaten in a special case or with a special type.
The true concept is the call.
>> Unfortunately, this approach means not having lexical scope. If any function has a parameter named or + and it calls foo, foo's behavior might be surprising.
That's about bad programming. Just know what you're doing.
>> Worse, you can't use lambdas to encapsulate state! (Or other context...)
I gonna look at those lambdas. Thx
>> With dynamic scope, you might as well define every function at the top level; local function syntax is only useful for code organization.
That's not a question of code organization. That is a question of sense. If you define your functions at the top level because you can do it, you'll need a debugguer and a default scope system based on lexical scope. Beleive me :)
So with dynamic scope, you just have a functions system which plays its role: the possibility to repeat code. And a macro system which plays its role: the possibility to - over - tweak the source text in a way which has nothing to do with programming in itself. Functions and macros should be orthogonal concepts. That's the meaning of a concept: something which is orthogonal to every other concepts in the system.
>> In some cases, dynamic scope can be useful for certain variables (typically configuration variables), but it's actually very easy to simulate dynamic scope in a non-concurrent program; just change a global variable and reset it afterwards.
The fact that Object Oriented programming exists tell you re wrong here.
>> That's because asterisks ( ) create italics on this forum*
By fixing some mistakes I've made, I can go forward.
I think I'm able to eliminate the def and have a working evaluation/call system.
Let's say, we can have symbols and lists of symbols only. Symbols can be bound to another symbol or list.
For number and integer, the arithmetic functions work on the symbols as if they were number or integer. I don't see any problem in that, ie. lambda calculus.
Also, let the previous scope system.
Evaluation. An evaluation of a symbol gives its bound symbol or list.
If one evaluates a list, it's a call.
And now, the calls.
We can call everything. A call on a symbol bind the symbol to the following argument or to a list of the following arguments. If the symbol hasn't been called before in the current scope, it is defining a new symbol on the scope.
And if one call a list, it's a function call.
So the previous code looks like this now:
('y '2)
{
('x '1)
('MyFn '((pr x) (pr y)))
(MyFn)
{
('x '10) ;no problem in this anymore
('y '3.14)
(MyFn)
}
(MyFn)
}
> 1
> 2
> 10
> 3.14
> 10
> 2
What we can see now is that, everything ends up with a '.
That's why I would like to explore the opposite strategy, an ' in front of what I want to evaluate.
It gives:
(y 2)
{
(x 1)
('pr x)
(MyFn (('pr 'x) ('pr 'y)))
('MyFn)
{
(x 10)
(y 3.14)
('MyFn)
}
('MyFn)
}
> x
> 1
> 2
> 10
> 3.14
> 10
> 2
I would like to put a star (like in C) instead of a ' for evaluation but I didn't succeeded.
Ok let do that if I've well understood. That's cool :)
>> I think it's very valuable to have a way to say "this variable is lexically scoped", meaning that functions called within the scope can't access it.
Do you mean can't access it in the sense of c++ private lib or kind of can't use it?
In the case of can't use it:
Why would a function evaluate a variable which does not exist from its point of view? ie. compilation error
I've found for the inner zap.
{
(def MyFn '( (= MyFn '(3.14)) (1) ) )
(pr (MyFn))
(pr (MyFn))
}
> 1
> 3.14
:)
EDIT: you have edited your text, I need to re-evaluate it. But unfortunately, I've to sleep now :D
Let's continue tomorrow :) Thank you, that's pretty interresting :)
For the fun, I gonna make a few try of implementations of such a language. Let's see if I can do a little bit better/atomic/consistent/powerful in the basic concepts.
One of my other concern is to generalize terms. And the spaces idea looks like a pretty good idea: a name space, a symbol/bind space, a type space, an algebric space and a call space .slt. Each term having something in those.
So I can bind things to 3.14 or to "hello world" and so I can call the function bound to 4 with ((+ 2 2)).
I'm not sure it will work at all ie. consistency/useability.
Also, macros are very useful because of the scope flexibility they offer (the only other thing they offer is code text über tweaking): we love use so-called global/context variables and we certainly need more flexibility for that.
I think that there is a misconception in the programming world behind the fact that scopes are defined by functions. I think - in fact I'm sure because I've already done it - you can define scopes independently of everything else. And then, you can call a function which will use variables of the call context, arguments becoming true arguments, not context vars. OO tried to solve that with just the bad idea.
I will use {} for scopes.
So let's try with the bind rules I've defined earlier:
(def y '2)
{
(def x '1)
(def MyFn '((pr x) (pr y)))
(MyFn)
{
(x '10) ; let's say its possible in the call space since 1 is not a list
(def y '3.14)
(MyFn)
}
(MyFn)
}
> 1
> 2
> 10
> 3.14
> 10
> 2
Don't see any problem in that except there is no argument in my function. I've not found something I like for the args already but it will come.
Note that:
(MyFn '({(def x '"blabla") (pr x) (pr y)}))
is possible to. I don't see any problem in that. The rule is: scope are independant of everything.
>> In fact, the defset utility lets us add that behavior ourselves if we want it.
At this stage, I also can go back to C :)
Do you get what I mean? Arc, if I may, should be a language which is damn hard consistent at least on the basic concepts and which use kind of self generated strategy to reduce LOCs, not hard coded pseudo-concept like most languages.
Ok. I will maybe try to write an implementation by myself with C.
By the way it also leads to: may a symbol be unbound (=bound to nothing)? I think not. I think that a symbol should be bound to a function self assignment and then we might do
> (x 1)
x bound to 1
And then, a hastable would be:
> '((x 1) (y 2) (z 3))
blabla
By the way the quote here which is required seems be the beginning of an absolutely beautiful system - the way one bind x, y and z before the retreiving of a value of such a hashtable might be the start of such dynamic evaluations/calls... And of course, one might not bind x, y and z before such an expression.
EDIT: so there should be kind of bound and unbound hashtables. Sounds good. I might be wrong.
Also, =, which do not evaluate the first argument which is pretty ok (other functions we write do the same on arguments), is able to bind something new to x.
Oh and
> (x 1 2)
x bound to '(1 2)
which is a good start for the consistency of the functions.
I'm looking for something like that when I'm working on something like arc.
>> The = macro is set up to special-case certain function names, such as car.
Here we are. :D Yup sure. Sounds just terribly bad.
Same for:
arc> (+ (eval (car '(x))) 2)
7
AND the fact we can't assign a value to x with a derivative of that expression - that expression being good in itself.
But well. I'll continue the tutorial because I've just read half of it - and I just blocked on the hashtables the true purpose of my questions here.
Why - I'm looking for a semantic reason, not a syntaxic one - can't we get a hashtable like this (or kind-of of course):
(map = (x y z) '(1, 2, 3.14))
Please note that map should map eh. And there is no reason it don't. At least in your answer - by the way thank you for it, I'll win a lot of time by studying it.
I'm pretty sure we can do absolutely everything with symbols, evaluation (and non-evaluation), calls, lists, bindings and some generic flow controls and some things I'm missing...
After that we can bind a combinaison of those things to a symbol like 'map or 'my-super-hastable-über-easy-to-use and win LOCs, it's not a problem.
If I'm missing something, a true reason that, for example, map can't build a map - that is bind symbols to values and returning the whole in a list the same way we can ________ symbols to values and returning the whole in a list, you gonna be my new best friend :)
To be clear: whether I'm missing a terribly obvious syntax to do this with map or there is a semantic reason I'm missing or it's a problem.
>> Also, please just copy-paste the session from your terminal in future.
I see, I will.
>> If it's easier we can go over more examples interactively over chat somewhere. Let me know if you want to try that; you'll see my email if you click on my username above.
Cool, that's more fun for sure :)
>> ... (the other points) ...
Fortunately I got those things. Also seems like the prompt is doing (pr (eval MyPrg))