Pepsi and Coke (No. 3)
A few notes from Ian.
The implementation of "print". The following version would be adequate in the rounded paren syntax. Also, it takes advantage of existing print: method.
(define print
(lambda (object)
[StdOut print: object]))There is "for" construct that uses "syntax". With "syntax", you can define a new syntax called "block". And, with block one could use collect: (and do: and others) in the ()-world:
['(1 2 3) collect: (block (arg) [arg * '3])]
How to write such "block" syntax is left to the reader until tomorrow. (I'll post another entry tomorrow or Friday.)
Pepsi and Coke (No. 2)
One of Coke's strength is manipulating syntax trees, like any other lispy languages. In Coke, there are some features that lets you transform a syntax tree into another form. It doesn't have all the features that a sophisticated Lisp macro system provides, but still you can have fun with it.
For the way I'm thinking to implement Ruby methods, where the number of actual arguments for a method can be different at a call site to another, I think I need to define a function that generates different functions. Such a generated function sits at a call site, handles the actual arguments, and gives "cooked" arguments to another function that represents the body of the Ruby method.
First, let me see if I can really generate different functions from one function. In Scheme, a function can indeed return different functions. I used an online Scheme interpreter here (http://www.yuasa.kuis.kyoto-u.ac.jp/~yhara/webscheme/demo/nine5.html) and typed:
(define x '())
(define (foo n)
(if (> n 0)
(let ((f (lambda (a) (* a n))))
(set! x (cons f x))
(foo (- n 1)))))
(foo 3)
((car x) 2)
((cadr x) 2)
((caddr x) 2)The function "foo" calls itself given number of times, and for each call, it creates a "new" closure and links it to the list "x" (The executable body of the code may be shared but the environment part of the closure are different). The result from the above code is:
(define x (quote ())) => () (define (foo n) (if (> n 0) (let ((f (lambda (a) (* a n)))) (set! x (cons f x)) (foo (- n 1))))) => #lambda (foo 3) => #null ((car x) 2) => 2 ((cadr x) 2) => 4 ((caddr x) 2) => 6
As you can tell from the last three expressions, the list "x" holds three different functions (or closures). Can we use the same implementation of "foo" in Coke?
It turned out that the answer is no for two reasons. First, you cannot define the following function (due to a compile time error) in Coke:
(define x [Array new: '0])
(define foo
(lambda (n)
(if [n > '0]
(let ((f (lambda (a) [a * n])))
(set x [[Array with: f], x])
(foo [n - '1])))))The reason of this compile time error is that "n" in "[a * n]" is a free variable, but I think it can't be an argument of the enclosing environment created by a lambda. As I heard from the author, Coke is a language with C-semantics, so functions are not closures. And, nested functions in Coke cannot be translated to functions in C.
Secondly, the Coke compiler doesn't create different closures or functions for each invocation of lambda. A minor variation of "foo" in Coke would be:
(define x [Array new: '0])
(define r '0)
(define foo
(lambda (n)
(if [n > '0]
(let ((f (lambda (a) [a * r])))
(set r [r + '1])
(set x [[Array with: f], x])
(foo [n - '1])))))There is still a free variable "r" in the (inner) lambda, but it is a global variable that is updated for each iteration. (Oh, by the way, if you'd write the part "[n - '1]" as "[n - 1]", it wouldn't stop. because "1" is '0. You have to be careful to deal with numbers...)
This code snippet compiles but doesn't work as you wish. The following is the results from the interactive shell after evaluating the code above:
.(foo '3) => 0 .([x first] '2) => 13 .([x second] '2) => 13 .(== [x first] [x second]) => 1
"13" is "6" in the object world, and it means that 2 multiplied by 3 was computed by both the first entry and the second entry of "x" Array. The last expression shows that "[x first]" and "[x second]" are indeed identical. I'd imagine that a (static) use of lambda is usually compiled just once. It makes sense that it is C semantics, but this is not convenient for what I would like to do. One way I found is to create different syntax trees and compile them to create different functions on the fly. And, of course, creating a syntax tree easily is one of the stuff Coke supports.
Do you know the quasi-quotation mechanism in Lisp-family languages? Coke has it, too.
I leave the detail of quasi-quotation to other materials, but basically you can construct an Expression object from a sort of "template" with holes, and actual values that should go to the holes. For example:
`(* 1 3)
is a template (without a hole). If you print the result, you get:
.[`(* 1 3) size] => 7 .[`(* 1 3) print] (#* 1 3) => 17
It is an Expression whose size is 3 and contains "*", "1", and "3".
To make a hole in the template, you use an identifier with comma:
`(* 1 ,x)
and to fill the hole, you just supply the value for the identifier in the environment:
(define x '3) `(* 1 ,x)
If you evaluate the expression, "x" is filled with the actual value bound to the identifier. In this case, you get "(#* 1 3)" Expression object as the result. (There is also ,@ that "splices" the actual value, but you can learn about it elsewhere.)
Now, I would like to create an executable function from the Expression object. I figured out that "_eval" message does it. (There may be a simpler way, actually. But couldn't find it yet. Of course, the system is under heavly development so these details are subject to change.)
So, to mimic the foo function in Scheme does, I ended up with:
(define new-function
(lambda (val)
[`(lambda (a) [a * ',val]) _eval]))
(define x [Array new: '0])
(define foo
(lambda (m)
(if [m > '0]
(let ((f (new-function m)))
(set x [[Array with: f], x])
(foo [m - '1])))))The "new-function" function first creates a new function that multiplies the argument with a constant. The constant is taken from the value of the actual argument for "new-function" and is embedded into the newly created function. The notation "',val" looks tricky, but the single-quote is necessary because converting the value to a string form seems to be done in the object world, but it doesn't put a quote. Resulting Expression is then converted to an executable function by "_eval" and returned. For each invocation of "foo", "new-function" is called and the result is stored into the global "x" Array.
If you evaluate:
.(foo '3) => 0 .([x first] '2) => 5 .([x second] '2) => 9 .([x third] '2) => 13 .(== [x first] [x second]) => 0
you can tell it is doing what you would like it to do. Of course, you can simplify "foo" to something like:
(define foo
(lambda (m)
(if [m > '0]
(let ((f [`(lambda (a) [a * ',m]) _eval]))
(set x [[Array with: f], x])
(foo [m - '1])))))Okay, the quotes and stuff can confuse you, but we'll see more confusing stuff that use "syntax" form later on.
欧文の日記
http://materia.jp/blog/さんにスタイルシートの変更点を教えてもらったので、早速適用してみました(ありがとうございます)。案の定というべきか、テキストの領域幅が狭いデザインなので、日本語の場合には右がちょっとがたがたになってしまいますね。でも、それほどひどいことにもならないようなのでこれで行きたいと思います。
Pepsi and Coke (No. 1)
I'm playing with the "Ian's stuff" last two days.
I'm learning by doing and the example project I
picked is to write Ruby in it.
To do this, one will require to write:
- The mapping from the Ruby object model to the
Coke one. Since the Coke's model is malleable and
the essential part of it is more or less
instance-based, the class and module hierarchy
(and instance-specific behavior) of Ruby should be
easily mapped onto it. A method definition of
Ruby has some complex rules and allows variable
arguments. There should be some indirection so
that such method can be represented a set of coke
functions.
- The grammar definition. Alex has been working
on a Meta-II like grammar processor (written in
itself on top of Coke).
Unfortunately, the latter is not readily usable
for platforms I use. So, I'd do the former first
for the time being.
At a glance, Coke syntax may look a bit like
Lisp. But there is one fundamental difference:
"expressions are not cons pairs, but Expression
objects." An Expression object is a subclass of
IdSt Array, which is fixed-width collection of
other objects. For example, if you write '(1 2 3)
in Coke, it is an (Array-like) Expression object
whose length is 3. In the other words, the
Lispy-looking "()-world" is already contaminated
by objects in a fundamental way.
BTW, an expert says: "The most accurate thing you
could say is that Coke expressions are parse tree
nodes and the semantics is that of C."
There are also a few things to keep in mind:
- The primary data that Coke manipulates is
"primitive" data.
- A quotation (more or less) gives you simple way
to represent a simple object.
- With a pair of square brackets, you can send a
message to an object.
These are simple in essence, but can really
confuse you in different ways. One is that the
SmallInteger object has a tagged representation,
but the interactive shell shows them as primitive
numbers.
In below, I'll get some executable examples. I
use jolt in idst-5.7 distribution. You I'd
recommend to download the source and built it by
yourself. As you go, you'll want to modify the
source of jolt as well. You should always give
"boot.k" as initial command line argument of
executable of jolt (called "main" or "main.exe")
and "-" to mean you are using it interactively:
---------
bash$ ./main boot.k -
---------
It should show some message and a prompt (".").
Let us type "3" (one character for three not the
double quatation marks) into the interactive
shell, it means 3 in the primitive data world.
So, the interactive shell returns 3:
---------
.3
=> 3
---------
On the other hand, you can type "'3" to mean 3 in
the objects' world. Note that the -eval and
-print part of the interactive shell (or
read-eval-print loop of the shell) doesn't know it
was an object so it prints out "7" (3<<1|1):
---------
.'3
=> 7
---------
Similarly, a string enclosed by double quotation
marks represents a primitive string (like C
string), but a single quotation makes it an
object.
---------
."abc" ; => C string
.'"abc" ; => String object
---------
Under this shell, the number arithmetic can be
confusing:
---------
.(* 3 3)
=> 9 ; primitive data 3 times primitive data 3.
.(* '3 '3)
=> 49 ; (3<<1|1)*(3<<1|1).
.['3 *'3]
=> 19 ; (3*3)<<1|1
.[3 * 3]
=> 3 ; ((3>>1)*(3>>1)<<1|1)
---------
The lispy version of function (prefix "*" at the
head of ()-list) interprets given arguments as
primitive numbers and multiply them. The message
version of "*" interprets receiver and arguments
as objects and multiply them. In both cases, the
printed results are given in the primitive data
representation. What happens if you try to send
"*" message to a primitive data? If fails rather
horribly:
---------
.[2 * 2]
13 [main] main 308 _cygtls::handle_exceptions: Exception: STATUS_ACCESS_VIOLATION
560 [main] main 308 open_stackdumpfile: Dumping stack trace to main.exe.stackdump
---------
and crashes because the object version of multiply
cannot accept primitive data. Booleans and
Characters in IdSt are simply mapped to the Coke
world; Any non-zero value is true and zero is
false. And, Characters are integers. (There
seems no notation for an IdSt Character object in
Coke.)
Let us continue the experiment. A good way is
to create your own test program file (let us call
it "tmp.k") and write your stuff into it. To
invoke it from the interactive shell, you do:
---------
bash$ ./main boot.k tmp.k -
---------
As an example function, let's define
"vector-map" function in Coke. The vector-map
function accepts a function and an Array. It
applies the function to each element of the Array
and stores the results into new Array and return
it. First, you add a few lines to your test file
to "import" the pre-compiled IdSt objects into the
Coke-world and name them accordingly. Also you
can define a utility method "print" for Object:
---------
(define Object (import "Object"))
(define Array (import "Array"))
(define String (import "String"))
(define [Object print] [StdOut nextPutAll: [self printString]])
---------
Then, define vector-map (it took me quite a
while):
---------
(define vector-map
(lambda (func list)
(let ((s [list size])
(ret [Array new: [list size]])
(idx '0)
(tmp 0))
(while [idx < s]
(set tmp (func [list at: idx]))
[ret at: idx put: tmp]
(set idx [idx + '1]))
ret)))
---------
Or, one could write:
---------
(define vector-map
(lambda (func list)
(let ((s [[list size] _integerValue])
(ret [Array new: [list size]])
(idx 0)
(tmp 0))
(while (< idx s)
(set tmp (func [list at: [SmallInteger value_: idx]]))
[ret at: [SmallInteger value_: idx] put: tmp]
(set idx (+ idx 1)))
ret)))
---------
to get the same effect. In the former, "s" and
"idx" hold objects (for example, "[list size]"
returns a number object), and in the latter, they
hold primitive data. Just see the usage of them
in the body of "while" changes from []-world to
()-world. I haven't measured the performance, but
the former is simpler to read. One thing you
would forget most is to put a quote in the
increment of "idx". by the time you write a few
things in the ()-world, switching to []-world
requires some mind twist, especially "idx" already
holds and object so you don't have to quote it.
The variable "tmp" is unnecessary, so you can
write:
---------
(define vector-map
(lambda (func list)
(let ((s [list size])
(ret [Array new: [list size]])
(idx '0))
(while [idx < s]
[ret at: idx put: (func [list at: idx])]
(set idx [idx + '1]))
ret)))
---------
I put "tmp" to signify that naked 0 is kind of nil
(in fact, it is NULL in the C sense) and good to
initialize a variable. In either one, you can use
it:
---------
(define n '(1 2 3))
(define n (vector-map (lambda (x) ['3 * x]) n))
[n print]
---------
Now, since the objects are almost there for you
to manipulate, you might think that the following
would work:
---------
(define n '(1 2 3))
[[n collect: (lambda (x) ['3 * x])] print]
---------
However, an expert of Coke told me that it
doesn't work because "collect:" expects a block in
IdSt world, but Coke's lambda is a function and
not a closure. For the time being, we put up with
this kind of verbose description.
The reason that the above code doesn't work is
not about mixing ()'s and []'s. They are quite
tolerant as long as it makes sense. For example,
you can write:
---------
[((lambda (x) ['3 * x]) ['(1 2 3) first]) print]
---------
The lambda takes an object and multiply it by 3 in
the object world. You give the first element of
the list (an Expression array) and pass it to the
function as an argument. To the returned object,
you send "print" message. Unlike Smalltalk or
IdSt, you have to put "[]" to each message
sending. For example, you can't write:
---------
['(1 2 3) first print]
---------
to mean the same thing.
Okay, here is what I've learned so far. If I
don't get bored, I'll keep going and write more
entries.
Ianのやつ
Ianのやつを勉強中。Lispの世界とObjectの世界を行ったり来たりできるものの、数字も普通の数とタグつきのオブジェクトの世界で別だし、メッセージとして送るか式として評価するかによってシンタックスを変えなくてはいけないので、なかなか混乱します。mapを書くだけでも死ぬほど時間がかかって、ようやく
(define map
(lambda (func list)
(let ((s [list size])
(ret [Array new: [list size]])
(idx '0)
(tmp 0))
(while [idx < s]
(set tmp (func [list at: idx]))
[ret at: idx put: tmp]
(set idx [idx + '1]))
ret)))
(define n '(1 2 3))
(define n (map (lambda (x) ['3 * x]) n))
[StdOut nextPutAll: [n printString]]というものになりました。listは実はExpression型のオブジェクトで、Arrayのように振舞います。コンパイラの中間表現として使われているものです。