This is the first entry in the dev diaries, and so I'd like to establish some things about this project:
And now for stuff actually about the project: as it currently stands, there is a bunch of dart classes to represent functions which can be represented by compositions, sums, subtractions, multiplications, divisions and exponentiations of logarithms, constant, trigonometric, inverse trigonometric, hyperbolic, inverse hyperbolic, absolute value and sign functions; and an interpreter of a language which is basically a very poor subset of javascript with a few different keywords. The "middle-term" objective of this project is to implement a language capable of creating and operating with these functions, as well as determining their natural domain, codomain and range, and solving equations involving such functions (in the form f(x) = g(x))
In order to do this, we (I) must first change the way functions can be evaluated. (As of this entry, 'function" will
refer to values of this data type, and "routines" will be used to represent what in other programming languages is
refered to as a function, as explained in the language tour). As of now, they can only be evaluated by passing as
argument a map containing as keys the name of every variable, and as values the values of these variables.
E.g.,
f(x,y,z) can be evaluated with the vector (1,2,
3) by calling f({x : 1,y : 2, z : 3}). I want for it to be possible to
evaluate these functions by simply passing f(1,2,3). For that, we must implement a
few things: first, we must know the set of variables in which a function is defined, which is already done in the
"parameters" getter. Then, we must define a method which takes a list of doubles and assigns them to this map. For us
to know what position in this list corresponds to which variable, we will make it so that they are evaluated in
lexicographical order.
We must also write the documentation for the language. I feel the best way to make sure it is as complete as possible is by writing the full documentation from the beggining and changing it as soon as anything new is implemented. This does, however, mean that we will write a very useless documentation to start with. That is it for this first entry, I hope to have new things to write about soon!
In the previous entry, I said I would start writing the language documentation before progressing with development.
I immediatelly noticed that was a bad idea. First, I don't even have the knowledge required to do it, and second, doing all this setup to keep programming is taking a slight effect on my motivation to continue this project. Yes, if the language takes off in any way I have to do it, and i would definately learn a lot doing it. However, it's not what I want to be learning right now. Therefore, I changed the name of the "documentation" section to "language tour", and in it I will simply explain the basics of what the language is capable of. After all, the chance someone decides to learn this language before learning others that also have dynamic typing, if/else blocks, while and for loops, etc. is virtually zero.
In the first entry, I mentioned it would be necessary to change how we evaluate functions in order to continue developing the language.
For this, a few changes are still necessary. Currently, I am changing the way functions are evaluated. Before, evaluating a function would be done by passing a Map < String, double >, but since ΒScript doesn't even have the type double, that doesn't make any sense. therefore, we now evaluate them by passing a Map < String, BSFunction >, which has a second benefit: we know can do function composition properly! Before, things like
f = sin(x)
g = f(x ^ 2)
wouldn't work. now, that's perfectly valid! Besides, calling a function like that will not perform simplifications. if we had, for example
f = sin(x)
g = f(2)
g would evaluate to sin(2), not to
0.909...
if we wanted it to evaluate to its approximation, we can use the approx getter, which, in the language, will translate
to the ~ operator.In ΒScript, one would write:
g = ~sin(2)
to get 0.909...
While implementing what I described in the previous entry, I noticed an easy way to solve a few problems I was thinking about. Since now function composition is now properly possible, we can create new functions differently. Before, we created them by calling factory functions external to the class, which means, there is a 'Sin' class with only a private constructor, and a public function "sin" which returns BSFunction objects. This works fine for the Dart classes, but is a little annoying for the language because there wouldn't be "default" objects for each function. If, say, we wanted to determine the domain of the sine function, we would have to do something like:
f(x) = sin(x);
print domain(f);
But if we had default functions, simply calling print domain(sin) would work! By the way, I'm sorry if all of this sounds like a poorly explained ramble about implementation details, but that is exactly what these dev diaries are going to be.
Since the first iteration of this project, one of the hardest problems I had to solve was how to handle stuff being multiplied by -1. There were basically three different solutions I thought about for this problem, all of them good in some ways, and really annoying in others;
Well, everything I needed to do before actually implementing the functions into the language is ready. By the way, I ended up changing to using negatives as a class, because I noticed there was a good way of dealing with other function attributes (namely, parameters, and in a later date domain and codomain) which wouldn't apply to negatives. So, now I have to actually discuss how they will work. In order to declare a function, one does something like:
let f(x, y, z) = sin(x) + cos(y) + z ^ 3;
Where 'let' is the keyword to declare variables, f is the variable name, and (x, y, z) is the optional parameter list. This list is used in case the user wants to defined a function with extra variables that aren't actually necessary (such as f(x) = 0) or to evaluate them in a order other than alphabetic. e.g., if I wanted z to be the first parameter, I would be able to declare the function f(z, y, x). If I wanted to call this function, i could use an expression such as:
print f(2, 10, cos(z));
outputting:
sin(2) + cos(10) + cos(z) ^ 3
Which is not necessarily what you expected. ΒScript keeps things exact if you don't explicity state you want approximations done, through the approximate (~) operator:
print ~f(2, 10, cos(3));
outputting:
-0.90005104017
ΒScript is also capable of calculating partial derivatives. For that, use the del() / del() operator. As your teacher probably told you in calculus class, this isn't a real division, it's a notation (until he told you it actually was a division of differentials, and, just like in calculus class, we might have differentials later):
print del(f) / del(x);
outputs:
cos(x)
Partial derivatives and higher-order derivatives are also supported:
let f = x ^ 2 * y ^ 3;
print del(f) / del(x, y, x);
outputs:
6 * y ^ 2
When all of these things are implemented, ΒScript V0.1 will be ready! as minor things, I'll change the keywords "var" to "let" and "function" to "routine".
ΒScript version 0.1 is ready! It has a million bugs and not even half of what I want it to be able to do, but now it at least lives up to it's proposal: One can create functions in it! I won't explain everything here, because that goes into the language tour, but it is alreaady pretty exciting. For more information about it, go there!
Yesterday I finished v0.1, but that doesn't mean I'll take a break! I'm thinking about what I want to call ΒScript v0.2. The primary focus now will be to develop the things I feel will be necessary for making the twitter version of ΒScript (yes, thats a thing). Basically a bot which will answer to tweets with ΒScript code with their output. For that, I will turn off defining and using routines and loops (don't want anyone killing my bot with a while(true)). Other than that, I want to try to make error handling better, since I already noticed it doesn't show some of them properly. As for actual new features, I'm planning the creation of "comparison expressions", which are basically equations and inequalities, and the ability to create subsets of R, which will come in handy with later updates.
I started to define set classes to represent subsets of R. This wil have many uses in future updates, so I want to do it well. Since I want to be able to define many types of sets, I decided that there will be a few different subtypes:
Up to this point, everything is fine. The problem is trying to define operations between sets. we need to define how to do unions, intersections and complements between all of the above types. This is not such a big problem for complement: we can just have a complement method which itselfs check what is the type of the other Set in order to properly compute it. A bigger problem comes from unions and intersections: they are comutative! That means that using the tactic above will lead to a lot of hassle. Essentially, for all 3 operations, we need to fill out the following table:
Interval | Roster | Builder | Disjoint Union | Empty | |
---|---|---|---|---|---|
Interval | |||||
Roster | |||||
Builder | |||||
Disjoint Union | |||||
Empty |
However, there's a catch. Unions and intersections are commutative, which means the table is simetric. I don't have to fill it all the way, The upper triangular part is enough. With all that taken into account, i need to think of a data structure to represent these "operation tables" that won't be too hard to extend later...
In Brazil, we have a saying: "Não se mexe em time que está vencendo", which has the same meaning as "If it ain't broke, don't fix it", but brazilian and soccer-y. Anyway, I'm about to start fixing the not-broken thing: I want to make semicolons optional, if not completely unnecessary, for ΒScript. Why? because I want the language to be written as closely as possible to actual maths, and we don't use semicolons for that in math. This will, of course, create a million problems, but that's the whole point of this entire language: making me think about problems which I can actually solve, unlike, you know, the dumpster fire that the world currently is.
Anyway, my approach to this is going to be similar to python's (according to Crafting Interpreters, by Bob Nystrom, the book the language was based on) approach: A line end is going to be interpreted as meaningful, except when the previous token always needs something to follow it (unlike python, which uses the explicit \ to do so). This list of tokens would basically be every operator, '('', '['', '{', ',' and '.'. This of course means a bunch of things would become invalid, but most of them are ugly, so I don't care. When 0.2 is done, I'll write about what this new set of rules means in the language tour, mainly because I haven't thought about the consequences yet.
Today was one of the first days I could actually use my comparison class to solve something. It was very simple, but just seeing a program I created be able to transform an inequation into a solution set was something amazing to feel. The first time I was able to see the output of print del(sin) / del(x); was definitely the best day of the year; and today wasn't so good, but automating set operations made it a little better.
Everything in ΒScript is a tree. The interpreter works by traversing trees, functions are trees, logic expressions are trees. Trees within trees within trees. I tried implementing at least sets to not be trees, but even that failed. I thought I would be able to always evaluate a set operation to a exact set (represent A union B as C instead of A union B). I almost did, but at the very last case I thought of, I noticed it would be impossible. The intersection of an Interval (something in the form a < x < b) and a builder set (something in the form {x | f(x) = g(x)}) can only be exactly evaluated if we know every solution of f(x) = g(x) in the interval (a, b). Considering f and g are supposed to be literally any differentiable function, that is impossible. Therefore, I'll have to create another set type to represent intersections of intensionally-defined sets. The same will apply to relative complements of intensional sets. With that, I'll be forced to represent sets defined in terms of operations involving intensionally-defined sets as trees.
After almost three months in the making, ΒScript V0.2 is finally done! It now supports real set definitions and operations, optional semicolons, a few new keywords and operators, among other things. I am very happy to have finished it, but the show must go on! First of all, I want to share some of my conclusions regarding the update: it is very unstable and badly written. As I said, optional semicolons are hard, and messing with them broke a lot of things and really tanked parsing speed. It needs to be redone, preferentially in a way that is dealed with completely in the scanner. Besides, the "operation tables" for set operations weren't as good as I thought. In another note, I noticed that as I extend the functionalities of functions and sets, their classes will become more and more complicated, and adding new operations will become very hard. It also makes it completely impossible for the users of the language to add new functionalities, which is prohibitively bad. A programming language can't stand if users can't develop libraries, and for two of the 5 base data types to be complex but closed is inacceptable.
With these things in mind, I would like to propose part of what will be ΒScript V0.3: a refactoring of the data type classes using the Visitor design pattern and/or a modification of it, a rewrite of the grammar to avoid linebreak tokens as much as possible and actually use Backus-Naur Form. But these things aren't really new funtionalities, it is just "backend" stuff. What will actually be new in 0.3? first, tri-state logic. I've realized that True and False aren't enough, because ΒScript sometimes won't know how to evaluate something exactly. For those things, it is better to have a "unsure" or "unknown" value, to replace the "better false negatives than false positives" policy. True will mean absolutely true and false absolutely false. Unsure means unsure, and, for a while, a lot of things will return it. Some will probably keep returning it forever.
But tri-state logic isn't what 0.3 is mainly about. The update will be called "The Unicode Update", because it will include a lot of unicode alternatives for operands. No more "union", "intersection", "===". We will use ∪, ∩ and ≡. See, I think programming has more to do with aesthetics than some would be inclined to believe, and making sure ΒScript is pretty is very important to me. I think allowing unicode operators when they exist is good for readability and to make ΒScript look more like math, which is kind of the point. Of course, I don't expect people to have these symbols on their keyboards, so I will also write a formatter for ΒScript which automatically does these replacements. It will also help enforce the formatting rules which i'd like people to follow that won't be possible when the linebreak tokens are removed. And talking about aesthetics, I will redo the website, because it is just too ugly.
the way optional semicolons was implemented was that "if you don't want to put one, a linebreak is fine". This is horrible. Don't do this. In all the places linebreaks were allowed in the middle of an expression, i had to check and remove them by hand. This happens because to check for meaningless linebreaks in the scanner, i could only look back, since the next token wasn't scanned yet. To solve this, i decided to add an extra function in the end of the scanner which looks at the token list to check for linebreaks where the next token automatically discards the linebreak, such as ), ], } and else.
first of all, i'd like to say i've changed my mind. MM/DD/YYYY is still the worst, but YYYY-MM-DD is better than DD/MM/YYYY.
With that out of the way: ΒScript V0.3 is ready! We now have tri-state logic, unicode alternatives for many operators and some bug fixes. This update was a lot smaller language-wise, because it also includes a formatter, a syntax highlighting vs code extension and a redo of this site! It was not only the unicode update, but the first major aesthetics update.
Let's be honest here. Right now, ΒScript doesn't do anything unique. And it probably will remain like that for a while. But 0.4 may start making it actually useful: The update of "explain" statements! "explain" will, well, explain how every operation in the following block is executed. For instance, explain ∂(sin(x) ^ 2) / ∂(x) would print out the step-by-step to arrive at the result. Besides that, it will include a ΒScript android app, which will include an full interpreter and a section to train derivating harder functions.