Well, it's part way there...
What I've got now is a cross platform system:
Erlang launches and links in a port driver, using a slightly modified ESDL.
The window is created in pure OpenGL, with SDL handling the event callbacks.
Every event gets passed back up to Erlang, where strokes are collated.
Erlang sends accumulative drawing instructions as the pen moves, then does a full redraw when the pen lifts.
That all works, and is nice.
The main question now is, how can I make my ink look as nice as microsoft's? They've put a lot of work into theirs, and it shows.
Here are my thoughts:
Ink quality is a combination of: Pressure representation, ink flow representation and fluid edges.
My first try involved drawing GL_LINE_STRIPS, thickening the line segment according to the pressure at the end vertex. That looked... Crude, to say the least. It was nice and fast, but quite aliased. Turning on antialiasing for ink DEFINITELY didn't solve the problem, and it looks like most graphics cards might be deprecating glLineWidth. Apparently all OpenGL compliant devices must support a width of 1, the rest is hope. Nevertheless, I kept plugging on that for a bit, went through building a gaussian fragment shader to soften the edges. Only a 3*3 kernel, I must admit. Helped a little, and gave a nice impression of blue ink in the way the color suddenly varied wildly across the stroke, but didn't make it much less jaggeddy.
I also tried drawing multiple layers of lines, for a sort of transparent bleed effect at the edges. Looked ok, but was still not antialiased so suffered quite badly from being either 1 or 0 pixels extruded from the overlying stroke.
The current attempt involves taking the original line coordinates and building quads around them. The advantages of this are:
They're hardware accelerated in a way that lines might not be.
They can be textured. Lines cannot.
They don't suffer from the aliasing problem in the same way, and have float precision angles. This makes for much nicer sides.
Problems with it?
It's hard to calculate, so as we get hundreds of strokes on the screen it might slow down badly.
At the moment there's some cockup with the trig where a particular angle (ENE or SWS on a compass) has visible end swapping - the outer line becomes the inner and vice versa.
We'll have to fix that. Stu will have to fix that, in fact. He's my trig monkey, and much loved for it.
Screenshots coming up, depending whether there are any convenient tools for clipping in Ubuntu.
Oh, btw I just found out that Awesomium hasn't released its Linux port yet so might have to move primary dev of this either back to Windows or to mac. Probably mac. It just seems crazy to try for cross platform but develop on windows.
Saturday, June 20, 2009
Thursday, June 11, 2009
You're going to think I'm an idiot, but...
I thought I'd put down for my future reference some things I learned today about C...
I've been in high level languages pretty much my entire career. I learned Java first, even built a game and game design tooling in Swing, so I've been in the wars. Since then Ruby, some Smalltalk, lots of JS, lots of C#, some Python, some Erlang. Probably a couple others, if I really think about it. Point is, those are all high level languages - automatic garbage collection and quite a bit of protection between you and the computer.
Not that I'm hitting buffer overruns and all the other classic nasties yet, mostly because of the circumstances of my encounter with C.
Basically I'm adapting existing embedded driver code from ESDL - taking the bits I want because my API's going to end up running to about a page and I'll do all the OpenGL work in C. This means that there's quite a lot of code there, written by pretty good C programmers - some of them the original ddll:_/_ authors, some of them Dan Gudmunsson and whoever he was working with for Wings3D. They're doing things that seem to me to be pretty clever, but some of them just flew under my radar the entire morning. For example, it was a great surprise to me when I eventually noticed that you could do this:
#include whatever
#define aCollection = {
#include stuffInTheCollection
}
Which works because #include is a literal compile time macro, which inserts the content of the file directly into the source (maybe not literally, but that's what I'm guessing - it certainly behaves that way). If you're not looking for that sort of thing to happen, you just overlook it over and over, because that part of the file is just usings and imports, and of no great significance unless you're trying to disambiguate where something came from. Which still happens - a LOT - and is bloody hard to work out sometimes. I haven't yet seen any really bad ones, but I reckon if you were overly clever you could string together a bunch of macros and it would be literally impossible to find any trace of your referenced functions in the source code. Grep would be useless and so would I.
Oh, the other interesting thing about the above code is that it implies that this is a valid header file:
{1,2,3},
trailing comma intended. Very grating if you're coming down from something like Java which just doesn't tolerate syntactic malformation at all (except for itself, but that's another story).
So as far as I can guess, C is just basically a huge undifferentiated clump of functions - sort of mash everything in every included file into one file and you'd have the contents of your binary. Sounds obvious, when you put it like that, but I keep looking for objects and scopes and namespaces and stuff - containers to help me look for my functions. They're not there.
More soon, probably (and eventually I'll finish off the silver standard NewLisp InkCanvas and post that too.
I've been in high level languages pretty much my entire career. I learned Java first, even built a game and game design tooling in Swing, so I've been in the wars. Since then Ruby, some Smalltalk, lots of JS, lots of C#, some Python, some Erlang. Probably a couple others, if I really think about it. Point is, those are all high level languages - automatic garbage collection and quite a bit of protection between you and the computer.
Not that I'm hitting buffer overruns and all the other classic nasties yet, mostly because of the circumstances of my encounter with C.
Basically I'm adapting existing embedded driver code from ESDL - taking the bits I want because my API's going to end up running to about a page and I'll do all the OpenGL work in C. This means that there's quite a lot of code there, written by pretty good C programmers - some of them the original ddll:_/_ authors, some of them Dan Gudmunsson and whoever he was working with for Wings3D. They're doing things that seem to me to be pretty clever, but some of them just flew under my radar the entire morning. For example, it was a great surprise to me when I eventually noticed that you could do this:
#include whatever
#define aCollection = {
#include stuffInTheCollection
}
Which works because #include is a literal compile time macro, which inserts the content of the file directly into the source (maybe not literally, but that's what I'm guessing - it certainly behaves that way). If you're not looking for that sort of thing to happen, you just overlook it over and over, because that part of the file is just usings and imports, and of no great significance unless you're trying to disambiguate where something came from. Which still happens - a LOT - and is bloody hard to work out sometimes. I haven't yet seen any really bad ones, but I reckon if you were overly clever you could string together a bunch of macros and it would be literally impossible to find any trace of your referenced functions in the source code. Grep would be useless and so would I.
Oh, the other interesting thing about the above code is that it implies that this is a valid header file:
{1,2,3},
trailing comma intended. Very grating if you're coming down from something like Java which just doesn't tolerate syntactic malformation at all (except for itself, but that's another story).
So as far as I can guess, C is just basically a huge undifferentiated clump of functions - sort of mash everything in every included file into one file and you'd have the contents of your binary. Sounds obvious, when you put it like that, but I keep looking for objects and scopes and namespaces and stuff - containers to help me look for my functions. They're not there.
More soon, probably (and eventually I'll finish off the silver standard NewLisp InkCanvas and post that too.
Wednesday, June 10, 2009
It is indeed pretty superficial
Looks something like this:
(Apologies in advance for what blog formatting does to the erlang syntax).
-module(canvas).
-compile(export_all).
-define(WIDTH,640).
-define(HEIGHT,480).
-record(sdlmem, {type, bin, size}).
-define(_PTR, 64/unsigned-native).
go()->
setup().
setup()->
case catch erl_ddll:load_driver("../priv", "sdl_driver") of
ok -> ok;
{error, R} ->
io:format("Driver failed: ~s ~n", [erl_ddll:format_error(R)]);
Other ->
io:format("Driver crashed: ~p ~n", [Other])
end,
Port = open_port({spawn, "sdl_driver"}, [binary]),
register(esdl_port, Port),
cast(21, <<0:32/native>>),
io:format("Connected to C through Port: ~p~n",[Port]).
cast(Op, Arg) ->
erlang:port_control(esdl_port, Op, Arg),
ok.
call(Op, Arg) ->
erlang:port_control(esdl_port, Op, Arg).
send_bin(Bin) when is_binary(Bin) ->
erlang:port_command(esdl_port, Bin).
send_bin(#sdlmem{bin=Bin}, _, _) -> send_bin(Bin);
send_bin(Bin, _, _) when is_binary(Bin) -> send_bin(Bin);
send_bin(Term, Mod, Line) -> erlang:error({Mod,Line,unsupported_type,Term}).
That's hardcoding every constant that they generated for esdl, and inlining a whole bunch of stuff that they rightly had in separate files. I'm thinking at this point, though, that there's no point erlang being the one to drive the gl. For one thing, all the other devs on my team are going to want to have documentation resources available to them for the gl work they have to do, and it's all in C. All of it. Which I guess makes sense because as far as I know OpenGL itself is in C.
So. Design decision now is, do I have a high level or a low level API for my erlang stuff? I really don't want Erlang specifying vertices, for instance. I'm much more inclined to build a detailed stroke object with color, pressure, timestamps etc., serialize it down to C and let it throw away the information it doesn't care about (which isn't much really) as it turns it into GL code. The cool thing about that would be that if there's another rendering engine which isn't OpenGL (say, for instance, a JS implementation in-browser or a superfast DirectX one), the erlang client wouldn't need to change anything.
Yah, that's pretty much the decision made for me, I think. Although I do think that I might accept more detail than I send. Maybe the API would look something like this:
C Rendering front End
---------------------
Stroke begun(Point)
Stroke point(Vertex,Pressure etc)
Stroke ended()
Erlang Client
-------------
Render(Strokes, Children, Whatever else I can think of)
Anyway, that's a start. And a lot simpler to deal with (although much less powerful) than the whole big ESDL kit and caboodle. Much credit to Dan for his work, just not worth the porting effort for the 1% I'm actually going to use.
(Apologies in advance for what blog formatting does to the erlang syntax).
-module(canvas).
-compile(export_all).
-define(WIDTH,640).
-define(HEIGHT,480).
-record(sdlmem, {type, bin, size}).
-define(_PTR, 64/unsigned-native).
go()->
setup().
setup()->
case catch erl_ddll:load_driver("../priv", "sdl_driver") of
ok -> ok;
{error, R} ->
io:format("Driver failed: ~s ~n", [erl_ddll:format_error(R)]);
Other ->
io:format("Driver crashed: ~p ~n", [Other])
end,
Port = open_port({spawn, "sdl_driver"}, [binary]),
register(esdl_port, Port),
cast(21, <<0:32/native>>),
io:format("Connected to C through Port: ~p~n",[Port]).
cast(Op, Arg) ->
erlang:port_control(esdl_port, Op, Arg),
ok.
call(Op, Arg) ->
erlang:port_control(esdl_port, Op, Arg).
send_bin(Bin) when is_binary(Bin) ->
erlang:port_command(esdl_port, Bin).
send_bin(#sdlmem{bin=Bin}, _, _) -> send_bin(Bin);
send_bin(Bin, _, _) when is_binary(Bin) -> send_bin(Bin);
send_bin(Term, Mod, Line) -> erlang:error({Mod,Line,unsupported_type,Term}).
That's hardcoding every constant that they generated for esdl, and inlining a whole bunch of stuff that they rightly had in separate files. I'm thinking at this point, though, that there's no point erlang being the one to drive the gl. For one thing, all the other devs on my team are going to want to have documentation resources available to them for the gl work they have to do, and it's all in C. All of it. Which I guess makes sense because as far as I know OpenGL itself is in C.
So. Design decision now is, do I have a high level or a low level API for my erlang stuff? I really don't want Erlang specifying vertices, for instance. I'm much more inclined to build a detailed stroke object with color, pressure, timestamps etc., serialize it down to C and let it throw away the information it doesn't care about (which isn't much really) as it turns it into GL code. The cool thing about that would be that if there's another rendering engine which isn't OpenGL (say, for instance, a JS implementation in-browser or a superfast DirectX one), the erlang client wouldn't need to change anything.
Yah, that's pretty much the decision made for me, I think. Although I do think that I might accept more detail than I send. Maybe the API would look something like this:
C Rendering front End
---------------------
Stroke begun(Point)
Stroke point(Vertex,Pressure etc)
Stroke ended()
Erlang Client
-------------
Render(Strokes, Children, Whatever else I can think of)
Anyway, that's a start. And a lot simpler to deal with (although much less powerful) than the whole big ESDL kit and caboodle. Much credit to Dan for his work, just not worth the porting effort for the 1% I'm actually going to use.
Damn.
Well, I give up on the way I was doing it. The problem isn't SDL 1.3 at all. The tests accompanying that are fine. It's this effort to port ESDL over to 1.3 for the bits I need that's going nowhere. I'm not convinced it's the way I want to do it anyway.
Now, I don't know much about this stuff, but wouldn't it be cool to have a rendering model where every erlang process took responsibility for maintaining its own render state? Maybe? Sort of objects in the old sense, along with visual responsibilities. I guess I'm thinking a sort of Smalltalky thing - although in my mind Smalltalk is very MVC?
Anyway, superficial glue C code coming up, instead of a voluminous ESDL port that I'd never use most of.
Now, I don't know much about this stuff, but wouldn't it be cool to have a rendering model where every erlang process took responsibility for maintaining its own render state? Maybe? Sort of objects in the old sense, along with visual responsibilities. I guess I'm thinking a sort of Smalltalky thing - although in my mind Smalltalk is very MVC?
Anyway, superficial glue C code coming up, instead of a voluminous ESDL port that I'd never use most of.
Tuesday, June 9, 2009
This is a fun day
It's been a bit of a long haul...
The mission is to represent arbitrary graphics - stylus ink, primarily, and images. It will ideally be in OpenGL because I'd like it to be cross platform and because I want to use Awesomium as a render source when we get more advanced. The programming language of choice is Erlang, because that's what the server architecture is in and I'm experimenting with doing a sort of distributed grid thing instead of classic client/server.
Basically there are two things that need to be done:
1. Prove gold standard ink from a pen - that is, receive and represent pressure information with sufficiently timely polling to have a high fidelity reproduction of the user's movements.
2. Render that to an OpenGL surface, preferably with a high level API.
The obvious choice is SDL, for all these things. The first proof of concept I knocked up was in SDL 1.2, which worked fine for 2 and for half of 1, but didn't have pressure information coming off the stylus.
Someone went and built a Summer of Code project to bring that information to SDL 1.3 Hang on... Szymon Wilczek. That's who did it. Anyway, it works fine. The problem is I can't get anything to render in SDL 1.3, no matter how I try it. I've tried the compat.c, and working through the texture to surface attachment api by hand, but there's just no output. Maybe it's because I'm on a laptop without a graphics card? I'm not sure. Anyway, I had this code working for 1.2 and it's very frustrating!
The mission is to represent arbitrary graphics - stylus ink, primarily, and images. It will ideally be in OpenGL because I'd like it to be cross platform and because I want to use Awesomium as a render source when we get more advanced. The programming language of choice is Erlang, because that's what the server architecture is in and I'm experimenting with doing a sort of distributed grid thing instead of classic client/server.
Basically there are two things that need to be done:
1. Prove gold standard ink from a pen - that is, receive and represent pressure information with sufficiently timely polling to have a high fidelity reproduction of the user's movements.
2. Render that to an OpenGL surface, preferably with a high level API.
The obvious choice is SDL, for all these things. The first proof of concept I knocked up was in SDL 1.2, which worked fine for 2 and for half of 1, but didn't have pressure information coming off the stylus.
Someone went and built a Summer of Code project to bring that information to SDL 1.3 Hang on... Szymon Wilczek. That's who did it. Anyway, it works fine. The problem is I can't get anything to render in SDL 1.3, no matter how I try it. I've tried the compat.c, and working through the texture to surface attachment api by hand, but there's just no output. Maybe it's because I'm on a laptop without a graphics card? I'm not sure. Anyway, I had this code working for 1.2 and it's very frustrating!
Sunday, June 7, 2009
Visual Fucking Studio
Let me count the ways:
You inexplicably stop being able to compile my code correctly until I apply a Windows Update (yah, seriously).
'Publish Now' in properties behaves differently to 'Publish' in solution explorer. Neither is correct - xaml files are not deployed with the app. It's... Hard... To run a WPF front end without any fucking xaml. Not impossible, you know, if I'd done all my development in pure C# all along, but then how would I have enjoyed the thrill of making a value converter do something completely inappropriate like representing an object with a relevant text string (three classes, for some of my members).
You have to run as administrator. Even though PAPA FUCKING MICROSOFT says that applications should endeavour not to run as administrator ever.
Every time I even glance at a xaml file you grind to a whimpering fucking halt for like five minutes, leaving me pounding the keyboard and shouting 'I didn't mean it! Stop!' (There is no option to disable XAML reading).
Resharper (makes life bearable) and ViEMU (makes typing bearable) don't work together - you end up having to hammer escape like nine times every time a tooltip comes up, just to be sure you're back in command mode.
You inexplicably stop being able to compile my code correctly until I apply a Windows Update (yah, seriously).
'Publish Now' in properties behaves differently to 'Publish' in solution explorer. Neither is correct - xaml files are not deployed with the app. It's... Hard... To run a WPF front end without any fucking xaml. Not impossible, you know, if I'd done all my development in pure C# all along, but then how would I have enjoyed the thrill of making a value converter do something completely inappropriate like representing an object with a relevant text string (three classes, for some of my members).
You have to run as administrator. Even though PAPA FUCKING MICROSOFT says that applications should endeavour not to run as administrator ever.
Every time I even glance at a xaml file you grind to a whimpering fucking halt for like five minutes, leaving me pounding the keyboard and shouting 'I didn't mean it! Stop!' (There is no option to disable XAML reading).
Resharper (makes life bearable) and ViEMU (makes typing bearable) don't work together - you end up having to hammer escape like nine times every time a tooltip comes up, just to be sure you're back in command mode.
What do I fail at next?
So, that was fib. That's pretty much 'Hello, World!' for functional programming (except for Erlang, where hello world is a complete mapreduce algorithm outpacing everyone but google), so I need a project. Something stupid and inappropriate, I was thinking. Computer game?
Shit, why not? Why not build a platformer in lisp? It's got all the things I like:
I don't know how to do it.
I don't have time to do it.
It's probably a ludicrous choice.
It would make Paul Graham smile.
That's pretty much the rubric against which I assess all my new ideas, and it gets four big thumbs up. So here goes:
Tk or something.
Brb, learning Tk.
Nooo... I'm not learning Tk. I'll just use straight OpenGL instead for the moment. Here's an unmodified demo code:
No, here's a public failure to remember to publish my code. When I get home.
NewLisp as standard lisp? Pshah!
Okay, so there's no such thing as a standard lisp. Every single person in the world has written his own lisp (Snurf, but that's the power of lisp! Yeah, right. I'd be so happy, too, if everyone had his own CPU architecture. Would be great), so there's no real argument for purity or education on which way to go.
I'll stick to NewLisp for the moment. I think it's pragmatic and well documented.
I'll stick to NewLisp for the moment. I think it's pragmatic and well documented.
Saturday, June 6, 2009
NewLisp doesn't have Loop?
Damn! It's like the uber macro! Even non lispers know about loop in all its non lispy complexity...
Anyway, I was trying to comma separate the list (and maybe along the way fix the nil problem - although I had actually thought lists always had an empty list in the cons cell - maybe I'm crazy):
I think I'll change convention here, too. Should be pretty clear when I'm writing lisp and when I'm commenting in English so I'm going to drop the ;;comment notation.
Here's a first approximation, after ten minutes hacking. I already know what my initial problem is going to be, by the way. It's not that there are too many parens, there are about the right amount. I just can't put them in the right goddamn place. My eyes sees absolutely nothing wrong with:
(list first(lst) str rest(lst))
and maybe if you're not a lisper yours doesn't either. But that's not how you invoke functions in lispland. It should be:
(list (first lst) str (rest lst))
which actually looks nicer now that it's there. It's just that the first doesn't leap out at me yet. So anyway, here's my first take on join. Fairly erlang influenced in terms of 'just cram all the symbols together and they'll be flattened on output:
(define (joinString str lst)
(let (acc) '())
(doJoin acc str lst))
(define (doJoin acc str lst)
(if(= lst '())
acc
(doJoin (list acc str (first lst)) str (rest lst))))
Which reminds me! I should flatten it!
Actually, I just found the final answer:
> (join (map string (map fib (clean nil?(range 1 10)))) ",")
"1,1,2,3,5,8,13,21,34,55"
Sorry to ruin the suspence, but you can't ignore library functions when they're already there...
Here's a better answer, where the nil cleaning has been rolled into range itself via an auxiliary function:
>(join (map string (map fib (range 1 10))) ",")
Actually, here's one better still, which enhances the library 'join' function:
(define (joinString str lst)
(join (map string lst) (string str)))
resulting in:
>(joinString ', (map fib(range 1 10)))
And that one I'm happy with.
Still, I wish I knew why that damned nil keeps getting in there. If this were CL I understand that it would be the list terminator. But NewLisp says it isn't. Obviously there's something wrong with my logic - but that is a matter for tomorrow. Today I'm going to call a success, even though I eventually did have to trawl the API looking for things like (first aList) and (last aList). Comma separated fib list was produced, and now will never be referenced again :)
Anyway, I was trying to comma separate the list (and maybe along the way fix the nil problem - although I had actually thought lists always had an empty list in the cons cell - maybe I'm crazy):
I think I'll change convention here, too. Should be pretty clear when I'm writing lisp and when I'm commenting in English so I'm going to drop the ;;comment notation.
Here's a first approximation, after ten minutes hacking. I already know what my initial problem is going to be, by the way. It's not that there are too many parens, there are about the right amount. I just can't put them in the right goddamn place. My eyes sees absolutely nothing wrong with:
(list first(lst) str rest(lst))
and maybe if you're not a lisper yours doesn't either. But that's not how you invoke functions in lispland. It should be:
(list (first lst) str (rest lst))
which actually looks nicer now that it's there. It's just that the first doesn't leap out at me yet. So anyway, here's my first take on join. Fairly erlang influenced in terms of 'just cram all the symbols together and they'll be flattened on output:
(define (joinString str lst)
(let (acc) '())
(doJoin acc str lst))
(define (doJoin acc str lst)
(if(= lst '())
acc
(doJoin (list acc str (first lst)) str (rest lst))))
Which reminds me! I should flatten it!
Actually, I just found the final answer:
> (join (map string (map fib (clean nil?(range 1 10)))) ",")
"1,1,2,3,5,8,13,21,34,55"
Sorry to ruin the suspence, but you can't ignore library functions when they're already there...
Here's a better answer, where the nil cleaning has been rolled into range itself via an auxiliary function:
>(join (map string (map fib (range 1 10))) ",")
Actually, here's one better still, which enhances the library 'join' function:
(define (joinString str lst)
(join (map string lst) (string str)))
resulting in:
>(joinString ', (map fib(range 1 10)))
And that one I'm happy with.
Still, I wish I knew why that damned nil keeps getting in there. If this were CL I understand that it would be the list terminator. But NewLisp says it isn't. Obviously there's something wrong with my logic - but that is a matter for tomorrow. Today I'm going to call a success, even though I eventually did have to trawl the API looking for things like (first aList) and (last aList). Comma separated fib list was produced, and now will never be referenced again :)
Yay blogging
Ya, yay blogging.
So I'm learning lisp today. Read some Yegge, read the comments. Have a good book about macros, currently lost. Can't remember what it's called. Was self published in what was clearly a custom typography. All page numbers wrong, front cover already fell off. That said, good book. Didn't understand what the hell it was about. So, bunch of trawling later:
Number of lisps in existence:
4,908,801.
Number of lisps which clearly indicate within the first 100 pages of their site that they support Vista (actually my heart's in 7 now but I still have my home laptop on Vista):
Clojure, NewLisp.
Number of lispers who apparently care about Windows:
1 (me, and I'm rounding up).
I like the look of Clojure but I figured I'd start in pure lisp and pick Clojure up later on when I need the libs. Pshah, Java libraries. Someone make me a lisp that works on the Erlang VM and I'll be happy. Yah, I know. Virding already did LFE. Again, I'll pick it up when I start doing any serious work. I don't want to be learning Lisp in an environment that's full of gotchas, or where someone has already ironed out the wrinkles.
So. NewLisp.
Here's my first lisping, just firing in the dark into a REPL based on the vague knowledge that there should be parenthesies (if a REPL is as good as I think it is, I should come out of this with an acceptable fib function):
newLISP v.10.0.2 on Win32 IPv4, execute 'newlisp -h' for more info.
>+ 2 2
nil
> + 2 2
+ <40cb15>
2
2
> + (2 2)
+ <40cb15>
ERR: illegal parameter type : 2
> (+ 2 2)
4
;;AH HA! That's how function application works! Let's move on to an incorrectly calculated fib! And figuring out function definition.
> (define fib n 1)
nil
> (define fib(n 1))
ERR: invalid function in function define : (n 1)
;;It seems to think I've tried to pass 1 to n. I guess that's what it does with lists.
> (define (fib n)(1))
(lambda (n) (1))
;;Well, that's an acceptable definition...
> fib()
(lambda (n) (1))
ERR: invalid function : ()
;;And that's clearly not the way to call it.
> fib
(lambda (n) (1))
;;No, I wanted that evaluated. Maybe I defined it wrong.
>(fib)
ERR: invalid function : (1)
called from user defined function fib
;;Oops, did it again. That's calling 1. What if it weren't in a list?
>(define (fib n)1)
(lambda (n) 1)
>(fib)
1
;;Yippee! Well, that's not quite correct as a fibonacci function though... Let's try to move it up to recursion. Baby steps: Define the exit condition, otherwise recurse. Isn't there some special syntax about letrec or something? Let's see...
;;Firstly, how does the if work?
>((if(1)2)
ERR: invalid function in function if : (1)
;;I guess 1 and 0 aren't true and false. Wouldn't it be a full list or an empty list? Maybe? Shot in the dark:
((if('(1)2)
if <408f74>
ERR: list index out of bounds
;;Well, it made the function okay. Was there some problem with that quote thing? I thought that these two were equal: (quote(1)) and '(1). Aren't they? I wonder if I could find out:
...About five minutes trying (eq) and (equal) and (equals) before I blunder into (= 1 1). Now to try out the quote thing:
>(= '1 (quote 1))
true
;;Yay! And I haven't had to google lisp syntax yet! Now, back to the problem at hand:
>(if true 2 3)
2
;;Right. So my halt condition looks something like:
>(if (= n 1)1)
;;Which is hard to test in the REPL because I need to give n a value. How do I give n a value? I have an idea that it relates to let binding, and might look something like
>(let n 1)
ERR: invalid let parameter list in function let : n
>(let (n 1))
nil
> n
nil
;;Well, maybe that was right but it sure didn't produce the desired result.
;;I'll try a different tack.
> (define (finished n) (= n 1))
(lambda (n) (= n 1))
> (finished 2)
nil
> (finished 1)
true
;;And then we'll use finished internally in fib:
> (define (fib n) (if (finished n) n fib(n-1)))
(lambda (n)
(if (finished n)
n fib
(n-1)))
> (fib 1)
1
> (fib 7)
ERR: invalid function : (n-1)
called from user defined function fib
> (define (fib n) (if (finished n) n (fib n-1)))
(lambda (n)
(if (finished n)
n
(fib n-1)))
> (fib 7(
ERR: missing parenthesis : "...(fib 7(\n X\224\""
> (fib 7)
ERR: call stack overflow in function if : finished
called from user defined function fib
...
called from user defined function fi
> (define (fib n) (if (finished n) n (fib (- n 1)))
;;Oops!
ERR: missing parenthesis : "...ib n) (if (finished n) n (fib (- n 1)\200\232\""
;;Okay, I'm officially getting pissed off with not having my brackets visually synchronized. I'm from VI land, we don't put up with this sort of shit.
> (define (fib n) (if (finished n) n (fib (- n 1))))
(lambda (n)
(if (finished n)
n
(fib (- n 1))))
> (fib 8)
1
> (fib 1)
1
;;There we go! Might not be the soundest proof of recursion ever, but it terminated and it came out at the right end. Now let's go for a proper algorithm:
> (define (fib n) (if (finished n) n (+(fib (- n 1))(fib(- n 2))))
ERR: missing parenthesis : "...shed n) n (+(fib (- n 1))(fib(- n 2))\200\232\""
;;That's annoying.
> (define (fib n) (if (finished n) n (+(fib (- n 1))(fib(- n 2)))))
(lambda (n)
(if (finished n)
n
(+ (fib (- n 1)) (fib (- n 2)))))
;;That works for 1 and for 7 but not for two. Finished needs to be redefined:
> (define (finished n)(<> (fib 8)
21
> (fib 1)
1
> (fib 2)
1
> (fib 3)
2
> (fib 4)
3
;;Yay! Fibbonaci! I wonder how I could go about printing out a nice set of fibonacci numbers, comma separated? Firstly, how to print stuff out? Well, it can just be the return for the moment. Secondly, how to comma separate stuff? It sounds like an accumulator function to me. How would... No, wait. This is lisp. I was about to go and google, like, lisp.lang.collections;
Heh.
So... A comma separating accumulator please:
> (define (commaSeparatedCountdown functionToApply numberToCountDownFrom numberToCountDownTo) doCSCountdown () functionToApply numberToCountDownFrom numberToCountDownTo)
(lambda (functionToApply numberToCountDownFrom numberToCountDownTo) doCSCountdown
() functionToApply numberToCountDownFrom numberToCountDownTo)
;;I figure a top level function which takes the parameters and then a low level function which adds in things like the empty starting list which I don't want to specify every time.
;;I just hit a problem: I know there's such a thing as (cons something aList) but I want to cons lots of things together. Is it really as clunky as (cons something (cons somethingElse aList))? Surely there's something nicer than this? Anyway, here's the ugly (I only have to cons twice every time so I'm not too fussed. There should be an aggregation function defined somewhere I would think).
> (define (doCSCountdown acc f from to ) (cons acc (f from) cons(', (doCSCountdown acc f (- 1 from)))))
(lambda (acc f from to) (cons acc (f from) cons (', (doCSCountdown acc f (- 1 from)))))
> (doCSCountdown () '((n)n) 3 0)
ERR: invalid function : ()
called from user defined function doCSCountdown
;;Oh. Apparently () isn't the empty list. nil? Or a quoted list?
> (doCSCountdown '() '((n)n) 3 0)
ERR: list index out of bounds in function cons
called from user defined function doCSCountdown
;;Apparently not.
;;Maybe I'm going about this the wrong way. Baby steps. Let's try just mapping the various fibs into a list, then we can comma separate them later. This would mean being able to create a range (1..10) and then mapping it to our fib function. I wonder if there's already a range function?
;;Oh, incidentally: Empty lists are like this - you have to quote them.
> '()
()
> ()
ERR: invalid function : ()
;;So, back to range.
;;Oh, just worked out let too: (let (a) 1)
;;So now really back to range:
(define (range start end)
(let (acc) '())
(if (= start end)
acc
(cons start (range (+ 1 start) end))))
;;That was pretty cool. It just worked. I still don't understand why the first parameter to let has to be a separate list though. What's the deal with that? Can you assign the same value to several bindings at the same time?
;;Now, the useful thing is going to be mapping to the range. Let's take a quick sample:
...About forty minutes of banging my head against the wall later.
;;Doesn't seem like my range function is that good - it produces
(1 2 3 4 nil) instead of (1 2 3 4). And that blows everything apart. And the only way I've found to not produce the nil is not to not produce it, but to take it out in what must be the hackiest way ever:
(rest(reverse(range 1 10)))
I am not proud. Moving along...
Now it all looks something like this:
> (map fib (rest(reverse(range 1 10))))
(55 34 21 13 8 5 3 2 1 1)
;;Which, if you take out all the grossness with my range algorithm, is sort of okay... Except for it being in the wrong order :)
> (reverse(map fib (rest(reverse(range 1 10)))))
(1 1 2 3 5 8 13 21 34 55)
And that's enough for today.
Okay, one small revision: Instead of letting my code fall apart on the nil, I'll just make finished consider it as another end condition. Now it looks like this:
>(define (finished n)(or nil (< n 2)))
Which gives us:
> (map fib (range 1 10))
(1 1 2 3 5 8 13 21 34 55 nil)
Which is what I wanted all along, except for the commas. I'll do those later.
So I'm learning lisp today. Read some Yegge, read the comments. Have a good book about macros, currently lost. Can't remember what it's called. Was self published in what was clearly a custom typography. All page numbers wrong, front cover already fell off. That said, good book. Didn't understand what the hell it was about. So, bunch of trawling later:
Number of lisps in existence:
4,908,801.
Number of lisps which clearly indicate within the first 100 pages of their site that they support Vista (actually my heart's in 7 now but I still have my home laptop on Vista):
Clojure, NewLisp.
Number of lispers who apparently care about Windows:
1 (me, and I'm rounding up).
I like the look of Clojure but I figured I'd start in pure lisp and pick Clojure up later on when I need the libs. Pshah, Java libraries. Someone make me a lisp that works on the Erlang VM and I'll be happy. Yah, I know. Virding already did LFE. Again, I'll pick it up when I start doing any serious work. I don't want to be learning Lisp in an environment that's full of gotchas, or where someone has already ironed out the wrinkles.
So. NewLisp.
Here's my first lisping, just firing in the dark into a REPL based on the vague knowledge that there should be parenthesies (if a REPL is as good as I think it is, I should come out of this with an acceptable fib function):
newLISP v.10.0.2 on Win32 IPv4, execute 'newlisp -h' for more info.
>+ 2 2
nil
> + 2 2
+ <40cb15>
2
2
> + (2 2)
+ <40cb15>
ERR: illegal parameter type : 2
> (+ 2 2)
4
;;AH HA! That's how function application works! Let's move on to an incorrectly calculated fib! And figuring out function definition.
> (define fib n 1)
nil
> (define fib(n 1))
ERR: invalid function in function define : (n 1)
;;It seems to think I've tried to pass 1 to n. I guess that's what it does with lists.
> (define (fib n)(1))
(lambda (n) (1))
;;Well, that's an acceptable definition...
> fib()
(lambda (n) (1))
ERR: invalid function : ()
;;And that's clearly not the way to call it.
> fib
(lambda (n) (1))
;;No, I wanted that evaluated. Maybe I defined it wrong.
>(fib)
ERR: invalid function : (1)
called from user defined function fib
;;Oops, did it again. That's calling 1. What if it weren't in a list?
>(define (fib n)1)
(lambda (n) 1)
>(fib)
1
;;Yippee! Well, that's not quite correct as a fibonacci function though... Let's try to move it up to recursion. Baby steps: Define the exit condition, otherwise recurse. Isn't there some special syntax about letrec or something? Let's see...
;;Firstly, how does the if work?
>((if(1)2)
ERR: invalid function in function if : (1)
;;I guess 1 and 0 aren't true and false. Wouldn't it be a full list or an empty list? Maybe? Shot in the dark:
((if('(1)2)
if <408f74>
ERR: list index out of bounds
;;Well, it made the function okay. Was there some problem with that quote thing? I thought that these two were equal: (quote(1)) and '(1). Aren't they? I wonder if I could find out:
...About five minutes trying (eq) and (equal) and (equals) before I blunder into (= 1 1). Now to try out the quote thing:
>(= '1 (quote 1))
true
;;Yay! And I haven't had to google lisp syntax yet! Now, back to the problem at hand:
>(if true 2 3)
2
;;Right. So my halt condition looks something like:
>(if (= n 1)1)
;;Which is hard to test in the REPL because I need to give n a value. How do I give n a value? I have an idea that it relates to let binding, and might look something like
>(let n 1)
ERR: invalid let parameter list in function let : n
>(let (n 1))
nil
> n
nil
;;Well, maybe that was right but it sure didn't produce the desired result.
;;I'll try a different tack.
> (define (finished n) (= n 1))
(lambda (n) (= n 1))
> (finished 2)
nil
> (finished 1)
true
;;And then we'll use finished internally in fib:
> (define (fib n) (if (finished n) n fib(n-1)))
(lambda (n)
(if (finished n)
n fib
(n-1)))
> (fib 1)
1
> (fib 7)
ERR: invalid function : (n-1)
called from user defined function fib
> (define (fib n) (if (finished n) n (fib n-1)))
(lambda (n)
(if (finished n)
n
(fib n-1)))
> (fib 7(
ERR: missing parenthesis : "...(fib 7(\n X\224\""
> (fib 7)
ERR: call stack overflow in function if : finished
called from user defined function fib
...
called from user defined function fi
> (define (fib n) (if (finished n) n (fib (- n 1)))
;;Oops!
ERR: missing parenthesis : "...ib n) (if (finished n) n (fib (- n 1)\200\232\""
;;Okay, I'm officially getting pissed off with not having my brackets visually synchronized. I'm from VI land, we don't put up with this sort of shit.
> (define (fib n) (if (finished n) n (fib (- n 1))))
(lambda (n)
(if (finished n)
n
(fib (- n 1))))
> (fib 8)
1
> (fib 1)
1
;;There we go! Might not be the soundest proof of recursion ever, but it terminated and it came out at the right end. Now let's go for a proper algorithm:
> (define (fib n) (if (finished n) n (+(fib (- n 1))(fib(- n 2))))
ERR: missing parenthesis : "...shed n) n (+(fib (- n 1))(fib(- n 2))\200\232\""
;;That's annoying.
> (define (fib n) (if (finished n) n (+(fib (- n 1))(fib(- n 2)))))
(lambda (n)
(if (finished n)
n
(+ (fib (- n 1)) (fib (- n 2)))))
;;That works for 1 and for 7 but not for two. Finished needs to be redefined:
> (define (finished n)(<> (fib 8)
21
> (fib 1)
1
> (fib 2)
1
> (fib 3)
2
> (fib 4)
3
;;Yay! Fibbonaci! I wonder how I could go about printing out a nice set of fibonacci numbers, comma separated? Firstly, how to print stuff out? Well, it can just be the return for the moment. Secondly, how to comma separate stuff? It sounds like an accumulator function to me. How would... No, wait. This is lisp. I was about to go and google, like, lisp.lang.collections;
Heh.
So... A comma separating accumulator please:
> (define (commaSeparatedCountdown functionToApply numberToCountDownFrom numberToCountDownTo) doCSCountdown () functionToApply numberToCountDownFrom numberToCountDownTo)
(lambda (functionToApply numberToCountDownFrom numberToCountDownTo) doCSCountdown
() functionToApply numberToCountDownFrom numberToCountDownTo)
;;I figure a top level function which takes the parameters and then a low level function which adds in things like the empty starting list which I don't want to specify every time.
;;I just hit a problem: I know there's such a thing as (cons something aList) but I want to cons lots of things together. Is it really as clunky as (cons something (cons somethingElse aList))? Surely there's something nicer than this? Anyway, here's the ugly (I only have to cons twice every time so I'm not too fussed. There should be an aggregation function defined somewhere I would think).
> (define (doCSCountdown acc f from to ) (cons acc (f from) cons(', (doCSCountdown acc f (- 1 from)))))
(lambda (acc f from to) (cons acc (f from) cons (', (doCSCountdown acc f (- 1 from)))))
> (doCSCountdown () '((n)n) 3 0)
ERR: invalid function : ()
called from user defined function doCSCountdown
;;Oh. Apparently () isn't the empty list. nil? Or a quoted list?
> (doCSCountdown '() '((n)n) 3 0)
ERR: list index out of bounds in function cons
called from user defined function doCSCountdown
;;Apparently not.
;;Maybe I'm going about this the wrong way. Baby steps. Let's try just mapping the various fibs into a list, then we can comma separate them later. This would mean being able to create a range (1..10) and then mapping it to our fib function. I wonder if there's already a range function?
;;Oh, incidentally: Empty lists are like this - you have to quote them.
> '()
()
> ()
ERR: invalid function : ()
;;So, back to range.
;;Oh, just worked out let too: (let (a) 1)
;;So now really back to range:
(define (range start end)
(let (acc) '())
(if (= start end)
acc
(cons start (range (+ 1 start) end))))
;;That was pretty cool. It just worked. I still don't understand why the first parameter to let has to be a separate list though. What's the deal with that? Can you assign the same value to several bindings at the same time?
;;Now, the useful thing is going to be mapping to the range. Let's take a quick sample:
...About forty minutes of banging my head against the wall later.
;;Doesn't seem like my range function is that good - it produces
(1 2 3 4 nil) instead of (1 2 3 4). And that blows everything apart. And the only way I've found to not produce the nil is not to not produce it, but to take it out in what must be the hackiest way ever:
(rest(reverse(range 1 10)))
I am not proud. Moving along...
Now it all looks something like this:
> (map fib (rest(reverse(range 1 10))))
(55 34 21 13 8 5 3 2 1 1)
;;Which, if you take out all the grossness with my range algorithm, is sort of okay... Except for it being in the wrong order :)
> (reverse(map fib (rest(reverse(range 1 10)))))
(1 1 2 3 5 8 13 21 34 55)
And that's enough for today.
Okay, one small revision: Instead of letting my code fall apart on the nil, I'll just make finished consider it as another end condition. Now it looks like this:
>(define (finished n)(or nil (< n 2)))
Which gives us:
> (map fib (range 1 10))
(1 1 2 3 5 8 13 21 34 55 nil)
Which is what I wanted all along, except for the commas. I'll do those later.
Subscribe to:
Posts (Atom)