String Replacement Driven by CSV Data
anime
he_the_great

Sometimes it is worth writing a up a script that does something of use only once; there are times these this could be a need to generate a bunch of scripts which actually only differ by some predefined set of data. Usually you'll want to write such a program so that the data drives the execution. However this can be fairly challenging if the script language isn't really meant for such a task, an example of this might be SQL. At which point it may be desirable to have a script connect to the database and build a transaction; this may not be an option if security prevents running/installing the needed script, allowing only for someone to run SQL.

Due to the feature set of D building a simple replacement script generator isn't too challenging. And if you need to do more complicated text processing to get at specific data, I personally find D to decent processing options even if they tend to be a little more complicated since the tools for text processing tend to emphasize a single pass causing multi-pass processing to be a little more work.

Here is a program that does a basic generation driven by CSV data for a simple string template.

dlang
import std.algorithm;
import std.csv;
import std.exception;
import std.file;
import std.path;
import std.range;
import std.stdio;

immutable replacementString =
`Some data [ReplaceThis] and [ReplaceThat]`;

auto replacementData =
`23,"fishes that eat"
42,eagles`;

struct Data {
    string number;
    string comment;
}

void main(string[] args) {
    version(FileInput) {
        enforce(args.length == 2,
          "Usage: ./" ~ args[0].baseName ~ " FileName");
        auto replacementData = readText(args[1]);
    }
    foreach(record; csvReader!Data(replacementData))
        writeln(replacementString.replaceWithData(record));
}

string replaceWithData(string str, Data d) {
    return str.replace("[ReplaceThis]", d.number)
        .replace("[ReplaceThat]", d.comment);
}

This script can then be run with rdmd scriptName.d, and I provided a version to show loading data from a file which is rdmd -version=FileInput scriptName.d dataFile.csv.

Conclusion

This is not a statement of D being the most concise means to accomplish this particular task, or that another language like Python, Ruby, PHP, etc. aren't fully capable of performing the same task just as or more easily. What I'd like to get across is that such a simple task is pretty straight forward in a language providing machine accesses such as pointers.


A Practicing Libertarian
anime
he_the_great
One of the more interesting things about the Libertarian platform is that one can apply it on their daily life. When walking down the street, when sitting in a movie theatre, when sleeping in bed, they are all opportunities to practice letting people live their own life.

You may be thinking about how you can apply your belief in helping the poor by working at a food bank or donating to a charity that supports your cause. What you aren't realizing is that you're just practicing the libertarian philosophy by doing these things. So what would it look like to live life with a political belief in helping the poor?

As you're walking into work, when you see a person begging for money, you stop, you look around and locate someone in the area who looks to be well off. You approach this person and start to demand that they give some money, when they don't comply you hold them and remove the money from their wallet and provide it to the bagger. In the world outside of government this is call assault and theft, but when government does it then it is humane and the responsibility of society.

I'd like to make one more note of this, government is not society.
Tags:

No more loops a D Translation
anime
he_the_great

A post by Dead Code Rising entiled "Java 8: No more loops" covers converting some looping operations and shows how the stream API from Java 8 can make the code more readable. I've implemented the examples using both a class and a struct as the data storage, the revision history shows the changes. Since structs tend to be more common in D I will us it as the dominating example. Be sure to build using -main -unittest since all executing code is within unittest blocks and no main is provided.

dlang
import std.algorithm;
import std.range;
import std.typecons;

private struct Article {

    private immutable string title;
    private immutable string author;
    private immutable string[] _tags;

    private this(string title, string author,
                 immutable(string)[] tags) pure {
        this.title = title;
        this.author = author;
        this._tags = tags;
    }

    @property const(string)[] tags() const {
        return _tags;
    }
}

Here the main difference is that D does not require getters and setters for everything, though if you really are concerned that you'll need to use a function later it is best to do so now.

The only data which utilizes a getter is the tags. This is because we want to return a mutable array of the immutable strings, since the returned array is actually a new stack structure the conversion is safely implicit.

dlang
auto makeArticles() pure {
    return [Article("title""jacob", ["tags"]),
         Article("title""author", ["tags""Java"]),
         Article("title""jacob", ["Java""tags"]),
         Article("title""author", ["tags"]),
    ];
}

This will be a helper function to create our data to be used by our unittests, I could have added a version(unittest) to prevent it being built outside of testing

dlang
bool contains(R, T)(R range, T b) {
    return !range.find(b).empty;
}

public Nullable!(const(Article)) getFirstJavaArticle
                        (const(Article)[] articles) {

    foreach (article; articles) {
        if (article.tags.contains("Java")) {
            return typeof(return)(article);
        }
    }

    return typeof(return)();
}

A small helper template was created that would tell of is the element was contained in the range. This likely could be implemented in Phobos as it can be a little more descriptive than the find equivalent.

This is the only time I'm showing the loop version, since these are structures the return time needs a way to indicate that no Article was found, this has been done by using Nullable which is kind of like the Optional used by Java. Writing out the Nullable type was too annoying so I utilized D's typeof(return) which I could use to encapsulate the item of interest.

dlang
public auto getFirstJavaArticle2
            (const(Article)[] articles) {
    return articles.find!(x => x.tags
                          .contains("Java"));
}

unittest {
    immutable arr = makeArticles();
    assert(arr.getFirstJavaArticle() is arr[1]);

    assert(arr.getFirstJavaArticle2().front is arr[1]);
}

Using std.algorithm.find it is a simple task to locate the first instance based on a predicate. Note however, unlike the looped version in this case the array is being returned and positioned at the first occurrence. This does several things for us, the rest of the data could continue to be processed and we've done away with needing the Nullable. However calling front on an empty range is not defined and I did not verify the range was not empty before doing so.

dlang
public auto getAllJavaArticle(const(Article)[] articles) {
    return articles.filter!(x => x.tags.contains("Java"));
}

unittest {
    immutable arr = makeArticles();

    assert(arr.getAllJavaArticle().equal(arr[1..3]));
}

Attempting to get all the Java Articles is very similar to finding the first occurrence. What I want to note about this is we now have two ways at getting the first occurrence. The first provided a range with the tail of the Articles, this one provides a new range that only includes those Java articles and the front will also be the first occurrence and will have the same seek time as the original use of find.

I will be skipping 'groupByAuthor' the new groupBy function has not yet been released and I have not tested my implementation of groupByAuthor.

dlang
public auto distinctTags(const(Article)[] articles) {
    return articles.map!(x=>x.tags)
        .joiner
        .unqualArray
        .sort
        .uniq;
}

import std.traits;
@trusted Unqual!(ForeachType!Range)[]
                 unqualArray(Range)(Range r)
              if (isIterable!Range
                  && !isNarrowString!Range
                  && !isInfinite!Range)
{
    return cast(typeof(return)) r.array;
}


unittest {
    auto arr = makeArticles();

    assert(arr.distinctTags.equal(["Java""tags"]));
}

Grabbing the distinct tags from all Articles is an interesting task because unlike the previous examples this actually requires a heap allocation to accomplish. We start by building a range for all the tags using the map.joiner.

The main goal here is to apply the call of 'uniq' to this new range of all tags (i.e. make a list of uniq tags). However to keep uniq operating in efficient time and space it relies on having all the data sorted, a sorted range can be traversed lazily and does not need a collection of previously found items. It is unfortunate that std.algorithm.uniq will still operate on something other than a std.range.SortedRange without a compile error.

Well sorting requires the ability to swap data, and we are working with immutable strings, this prevents making modifications as that would distort the order of the original immutable array we had defined. In other words, "yay const is keeping us from modifying an ordering of data which may actually be important in its original form!"

Well Phobos doesn't provide a way create a new mutable array from a range of immutable elements. std.array.array will allocate new memory and copy over the data which means even if the original was immutable it no longer has to be. I haven't thought through and tested all the edge cases, but I created a little helper function unqualArray that just asserts that new data is returned and can be mutated. This allows me to create an unqualified array which can then be sorted making it a perfect candidate to mask as a range of unique elements.

Conclusion

D does provide the principles that are found in the functional approach. It is missing some of the functions others may be familiar with by name and some more work is needed to bring const into the flow.

What we can see is that even starting with immutable data objects we can, for the most part, search the data to locate what is of interest. And by designating the mutability of our data the language prevent what could have been harmful mutation utilized in our search for uniqueness. D has come a long way in this regard!

Also unlike the examples provided for Java, D does not collect the elements into a new collection for each operation of the range. Instead return a range that maps itself as a view of the originals, except in the explicit case that a new collection (unqualArray) had to be created.


In a Sea of Rights, What is Freedom?
anime
he_the_great
I want to take some time and convince you that you don’t want freedom, much like the rest of the USA and world. To do this I’ll have to give you a clear understanding of what it means to be free. The current gun safety debate is a good place to use examples and explain the problems with freedom.

To be very clear, freedom is not all or nothing. One country/state/city/county can be more free than another, can be free in one area and not another. Some freedoms are more important than others. I won’t be getting into how to measure/compare freedom.

A common way to describe freedom is, its the ability to do whatever you want except when it interferes with another’s freedom. Another way to say this might be, freedom is the inability to control others. Freedom is a single limit on oneself.

Piers Morgan and others have asked the question, which is more important, the right to life or the right to own a gun. The question is clearly answered with the right to life, there is no contest with a material thing. However freedom doesn’t care; all rights are created equal. In the world of freedom your life is no more important than the rights of others to own and carry deadly weapons. The question is very powerful and those who are pro-freedom do not wish to answer it.

When you look to the side who claims to defend the “right to keep and bear arms” we see another misunderstanding of freedom. When asked, “what about my right to feel safe in public?” Generally the reply is “where does it say that in the Constitution?” which implies the other one “nobody has a right to feel safe.” With freedom, you have both and they are both equal.

Let’s visit the idea that because a life is a more important right, we should take away the right to own firearms. This is an appeal to the preferred freedom. Here is a scenario for a guy, he owns a gun and carries a gun, he takes his gun out and starts dancing around, he points it at a person here, points at another there. Then he fires a shot into the chest of a person who ends up dead. At what point did life get violated? It wasn’t the owning, the carrying or even the flagrant dancing. Life was still preserved when the gun was pointed at someone. Life was only violated when someone was shot. Freedom doesn’t care about those other things and everything is equal in the eyes of freedom.

What I want to bring to light is that this is really about the discussion on when we can make a reasonable assumption that the intent is to kill. For those wishing to have that discussion, the common ground to stand on is, when the gun is pointed at an innocent.

Back on the idea about rights to feelings. This just misses that one can control emotions. And solving the external stimulus triggering a desire for fear can also be done by leaving the area or time travel also known as waiting. So while the “gun rights” side fails to understand freedom themselves, this isn’t an argument that can win.

In a free society, rights aren’t important. The use of the word ‘right’ in a free society is just a simple way to say, “I am free to…” Both sides have their own interpretational mistakes of a right. One side talks only about protected rights, while the other considers them guarantees. This is a big mistake to make about freedom because their are no guarantees.

Consider the idea of a right to health care. If you consider a right a guarantee then this sounds really good. However, freedom this actually means the complete opposite. Currently the government has massive restrictions for running a medical clinic. It is actually limiting freedom by saying someone can’t open without paying for the the require fulfillments of these regulations. Remember, a person can’t control another under freedom, so freedom says one can open a clinic as long as another is not controlled.

Freedom doesn’t allow for regulations which would require certifications and passing an operations check. This will increase risk that a fraudulent business will open and operate with lower quality than we know today. It means no equal opportunity employment or job security, and you don’t get to know if the guy with a gun has passed a background check.

Thank you for reading. I hope that I’ve convinced you that you do not want freedom. A free society is scary, you never know what you’re going to get. So please, the next time you want to increase regulations, restrictions, or start another government welfare program, begin the conversation with, “I want to make America less free so that…”
Tags:

std.typecons.wrap Improvements Continued
anime
he_the_great

My last post mentioned that a change to a Structural template caused a unittest failure. The specific details are as follows.

dlang
    Human h1 = new Human();
    // structural upcast (two steps)
    Quack qx = h1.wrap!Quack;   // Human -> Quack
    Flyer fx = qx.wrap!Flyer;   // Quack -> Flyer
    // strucural downcast (two steps)
    Quack qy = fx.unwrap!Quack; // Flyer -> Quack
    Human hy = qy.unwrap!Human; // Quack -> Human
    assert(hy is h1);
    // strucural downcast (one step)
    Human hz = fx.unwrap!Human; // Flyer -> Human
    assert(hz is h1); // FAIL

The failure was on the direct step from a Flyer straight back to a Human. The Flyer object did not wrap a Human, instead it was a Quack. But the change to use Structural!T doesn't know anything about Quack. This meant that checking if the Flyer could be cast to a Structural!Human would fail because it was not. Templates in D are not contravariant so the Flyer was also not a Structural!Object, even though for all intents and purposes it is.

I believe there is some classinfo I could obtain and setup appropriate calls to make the appropriate jumps back to the original type. However, I can have the compiler do this for me. So I returned to the original object approach by making classes and interfaces use Structural!Object and leave the specific type only for structs.

This is a workable solution since a structure will never nest. If a struct is wrapped into multiple types, the second wrap will be wrapping the Impl class created from the first

On a similar note, I found a request by Andrei which I think is essentially addressed by this work, Class!T.


Improving std.typecons.wrap to support structs
anime
he_the_great

The goal behind 'wrap' is to provide a means to "cast" a type based on structual conformance. If the type provides all the functionality needed then it can be wrapped as the desired type.

In D it is more common to utilize a struct instead of a class. Structs don't provide inheritance so it is not possible to wrap a type into a struct, similarly a final classes prevent wrapping as that type.

What can be done is to take a struct and wrap it into a class. I'm attempting to make this possible.

What I noticed from the implementation was this supporting interface:

dlang
// Internal class to support dynamic cross-casting
private interface Structural
{
    inout(Object) _wrap_getSource() inout @safe pure nothrow;
}

This is utilized during unwrapping. And I thought a good modification would be:

dlang
private interface Structural(T)
{
    inout(T) _wrap_getSource() inout @safe pure nothrow;
}

This hit a compiler bug which took some time to minimize. But now that the unittests are running, there's a broken test to look into.


Covariance and Multiple Interface Inheritance
anime
he_the_great

(The names used for interfaces, classes, functions may not depict good design; they have been chosen only to make conveying the capability more clear)

dlang
interface AAAAA { aaaaa aaA(); }
interface aaaaa {}

class AaAAaA : AAAAA {
    aaaaa aaA() {
        return aaaaa.init;
    }
}

class aAA : AaAAaA, aaaaa {
}

Excuse me. I think my computer just sneezed.

Interesting thing happened today, I was fighting with C# about type safety. And it seems the Lang.Next talk was released which had some discussion about contra/covariance and why have it.

I prefer static typing. I want the compiler to yell when using something wrong and I lean on that ability heavily. People love REPL from dynamic languages because they get to try things and see how they work, types allow for the that same principle. Making some modifications to a type in terms of structure and behaviors expected the compiler will explain when someone isn't upholding their end of the bargain. And this gives the opportunity to either re-evaluate the design choice being made or to update types to match the changes.

For that reason I want structure my code in a way that the compiler will validate a contract is being fulfilled. This means casts are not an option, such an operation tells the compiler to ignore what it knows and assume the object will fulfill the rule of the type desired. Covariance for function return types was the needed feature to achieve these goals, C# was missing it. I looked into parameterizing my types, but that didn't quit fit into the whole; I've settled on explicit interface implementation, which just adds boiler plate and I don't have mixins to solve that problem.

D, however, does provide such a feature let's dive into it.

dlang
class A {
    A foo() { return A.init; }
}

class B : A {
    override B foo() { return B.init; }
}

The idea here is to provide covariance on return type, and is not valid in C#. Notice that class B has overridden foo() of class A, but it does not adhere to the contract that foo() returns A. Instead it returns B, as we know that class B is_a A it is type safe to make this change. When calling a.foo() where the type of 'a' is known to be class A and the object is of class B, the returned type will be A.

Hopefully the example I've contrived will depict why this is so great. Please don't consider this a design pattern, understand why it works and if coming across a reason this applies then feel free to be frustrated that the language you're coding in doesn't support it.

dlang
interface LoanOffice {
    LoanItem getItemOfLoan();
}
interface LoanItem {}

A LoanOffice is going to provide a method that obtains the item that has a loan on it. I'm thinking financial loan, so LoanItem may just be paperwork about the loan. LoanOffices are really dumb as they only ever give out a single loan, they may do some other useful things like restock staples but lets concentrate on that single loan.

dlang
interface CarDealer : LoanOffice {
    Car getItemOfLoan();
}

interface CamperDealer : LoanOffice {
    Camper getItemOfLoan();
}
interface Car : LoanItem {}
interface Camper : LoanItem {}

Here are some specific types of LoanOffices, each one will use covariance to provide a very specific LoanItem. Basically the same as the A and B class example but using interfaces.

dlang
class RVDealer : CarDealer, CamperDealer {
    interface Truck : Car {}
    class RV : Truck, Camper {}
    override RV getItemOfLoan() {
        return RV.init;
    }
}

Now the first implementation of a CarDealer and a CamperDealer. Someone selling an RV is going to have a LoanOffice which provides a Car LoanItem and it provides a Camper LoanItem since the RV is both of these. I also threw in the Truck interface which for the sake of argument is a Car. The Truck could go further down the path of covariance had there actually been requirements for being a Car.

dlang
void handleCarLoan(Car c) {}
void handleHomeLoan(Camper d) {}

And here is a simplification of why I desired covariance. In this case I'm showing functions which take a specific LoanItem (my actually case was effectively working with the dealers themselves). An RVDealer should be able to provide a LoanItem to these two functions and there should be no reason to cast. An RVDealer already knows that the type it provides fulfills both contracts even if Car and Camper are distinct types.

dlang
void main() {
    auto rvDealer = new RVDealer();
    handleCarLoan(rvDealer.getItemOfLoan());
    handleHomeLoan(rvDealer.getItemOfLoan());

D allows calling of these two functions because it has verified that the types will fulfill all needed contracts. C# would require a cast eliminating the compile-time checks desired. Note: had I instead stored my RVDealer into a CarDealer or LoanOffice variable the compiler would not allow both of these calls to succeed. This is because the type information says that getItemOfLoan() will return a Car and LoanItem respectively but not the Camper.

dlang
    void handleCarLoan(RVDealer.Truck c) {}
    handleCarLoan(rvDealer.getItemOfLoan());
}

And in case you were wondering, the handleCarLoan() can be specialized to our Truck interface and our RVDealer will give us the type to satisfy that requirement.

Conclusion

The ability to use covariance for function return types helps greatly in eliminating type casts and boiler plate. I wouldn't state it has a frequent need but it provides options and logically follows the contracts of the interface.

I'd also like to say that many may notice that all I have is bad things to say about other languages. Well, C# is actually a really good language which I enjoy programming it. The main issues I run into is that it isn't D, the idioms in D have become a part of the way I look to solve a problem. When solving a problem I'm looking for maximizing compiler checks, representing the problem, and eliminating boiler plate/code duplication. And there just isn't any reason for me to write about how great C# is, everyone already knows it.


Explaining Language Design (Part 8: Essential Complexity)
anime
he_the_great
Finally Scott finishes the talk about essential complexity. This is essentially a recap of Part 2 for D.

dlang
void main() {
    struct Point {
        int x, y;
    }

    Point p;
    static assert(is(typeof(p.x) == int));

    const Point* cp = &p;
    static assert(is(typeof(cp.x) == const int));
}

Scott claims that 'cp.x' could lagitimately be consider of type 'int' or 'const int' and shows C++ which allows for obtaining either:

dlang
decltype(cp.x) // int
decltype((cp.x)) // const int&

As mentioned in Part 2, const is transitive in D. So there is only one right answer, cp.x is 'const int.' Again you can use std.traits.Unqual to get the unqualified type and cp.x can still be assign to a new variable of type int.

Conclusion

The choice for const to infest the entire type has removed some complexity of the language, but it has also made working with const more of a challenge (especially since it can't be used the same as it is in C++ or other languages).

  1. Part 1: Default Initialization
  2. Part 2: Const Inference
  3. Part 3: Lambdas in C++
  4. Part 4: Lambdas in D
  5. Part 5: Type Inference
  6. Part 6: Inheritance
  7. Part 7: Algorithm Complexity
  8. Part 8: Essential Complexity

Explaining Language Design (Part 7: Algorithm Complexity)
anime
he_the_great

In the next portion of the talk Scott goes over library consistency. Certainly D has some that people will come across, but I'll continue with the examples given by Scott. I also tend to be blind to most of the inconsistencies.

dlang
void main() {
    int[] v;
    import std.algorithm : sort;
    sort(v); // O(n lg n)

    import std.container : DList;
    DList!int li;
    version(none) sort(li);
    // Error, not random access

In the very beginning D starts out the same as C++. A random access container is sorted in O(n lg n) and a list isn't random access preventing the ability to sort. This keeps sort a constant complexity.

dlang
    sort(v).contains(10); // O(lg n)

Utilizing sort in D provides a thin wrapper around the container. It provides a function 'contains' which will do a binary search for an item and tells you if it was found. Since a list can't be sorted there is no binary search.

dlang
    import std.algorithm : canFind;
    li[].canFind(10); // O(n)
    sort(v).canFind(10); // O(n)
}

As there is no binary search on a list there is another function call 'canFind' found in std.algorithm which will do a O(n) romp to locate a requested element. We can still sort your array and call canFind, which will still be O(n).

Scott goes into some naming items for deleting elements from a container and stable sort. I'm not going into detail here, but as it stands std.container is very detailed about naming and complexity. These obviously are not enforceable, but give direction if a container needs to be created. The module is also very sparse when it comes to actual container implementations.

Conclusion

I think D is in a fairly good position. There are some issues with where to find things. While searching and sorting is found in std.algorithm binary search is actually in std.range.SortedRange (luckily that is mentioned in the sort docs. There is also std.range.assumeSorted if sorting has already been done. Part of the reason useful functions are scattered throughout the different modules is because the modules are generic.

I also think that binary search is a little out of place, I'd expect some generic search function which will operate with the best complexity possible, then have binarySearch and linearSearch; the binarySearch could only operate on a SortedRange. I wouldn't mind if the names were still contains and canFind, just think that placing contains on SortedRange is odd. Specifically I'd love to see a binaryFind which provides the same functionality as std.algorithm.find with binary search.

  1. Part 1: Default Initialization
  2. Part 2: Const Inference
  3. Part 3: Lambdas in C++
  4. Part 4: Lambdas in D
  5. Part 5: Type Inference
  6. Part 6: Inheritance
  7. Part 7: Algorithm Complexity
  8. Part 8: Essential Complexity

Explaining Language Design (Part 6: Inheritance)
anime
he_the_great

This next post needs to take from essential complexity as this provides the explanation for the inheritence issue.

C++
template<typename T>
class Base {
public:
    void doBaseWork();
};

template<typename T>
class Derived: public Base<T> {
public:
    void doDerivedWork() {
        doBaseWork();
    }
};

C++ is doing eager error reporting. As we read this we can say, "yes Derived should be able to call doBaseWork()." However, we don't have enough information at this time.

C++
template<typename T>
class Base<int> { };

Now it is no longer allowed to call doBaseWork() if T is an int. This also applies to typos.

C++
template<typename T>
class Derived: public Base<T> {
public:
    void doDerivedWork() {
        dBseWrk();
    }
};

C++ will not allow this to compile because it can't validate that dBseWrk() exists while parsing the template. The language provides a means to get by this early lookup by using 'this->dBseWrk()' instead.

dlang
class Base(T) {
    public void doBaseWork() {}
}

class Derived(T) : Base!T {
    public void doDerivedWork() {
        doBaseWork();
    }
}

class Derived2(T) : Base!T {
    public void doDerivedWork() {
        dBseWrk();
    }
}
void main() {}

D has chosen to do later lookup. As Scott mentions, this means the error for calling dBseWrk() is pushed onto the library user instead of the library writer. There is no early lookup in D, it waits for instantiation when it has all the specializations and can choose/verify the appropriate calls. I also recommend using unittest blocks to get instances of your templates.

C++ doesn't solve the problem of offloading the error onto the user, it only makes the library writer be explicit when they are doing so. And even then, it is through reuse of a syntax which in no other context does it mean evaluate later.

Conclusion

D has chosen the side of design which makes the language easier to explain and understand. Quite simply, the above templates compile because they do not exist (no attempt was made to instantiate them).

D is also positioned to do a better job of identifying incorrect usage within a template, possibly through a static analyzer. In D the module is a compilation unit, it is not possible for an outside module to introduce a specialized template and hijack the behavior. By viewing a single module one knows all of the specializations and most likely do some type analysis to decide if call doBaseWork() will always be valid (don't even need to know the imported symbols).

  1. Part 1: Default Initialization
  2. Part 2: Const Inference
  3. Part 3: Lambdas in C++
  4. Part 4: Lambdas in D
  5. Part 5: Type Inference
  6. Part 6: Inheritance
  7. Part 7: Algorithm Complexity
  8. Part 8: Essential Complexity

?

Log in

No account? Create an account