Skip to content
David Jeske edited this page May 16, 2017 · 21 revisions

One way to understand a language is to compare it to its contemporaries. Since C++11 templates support type-inference and Structural Typing, it's a good comparison to make to understand something about Irken.

Before we get into the details, two of the biggest differences between the languages, and which we'd like to call out first, are..

  • C++11 is mature, while Irken still in it's infancy with few programs written in it
  • C++11 has extensive libraries and documentation, while Irken is sparsely documented with few libraries

In other words, Irken is very much still a research project, while C++11 has lots of 'real things' written in it. With that disclaimer aside...

Similarities

Both languages:

  • Are staticly typed, with parametric types
  • Can be compiled to native code
  • Support structural typing (though C++ has no polymorphic structural typing)
  • Support both functional and imperative programming
  • Support type inference (though Irken supports global type inference, while C++11 does not)
  • Support Exceptions
  • Support compile time macro expansion

Differences

  • Irken is a Scheme like s-expression language, C++11 is a C-like syntax
  • Irken is strongly typed (no implicit type conversions), C++11 supports implicit type conversions
  • Irken supports global type-inference, while C++11 supports inference over expressions and parametric functions
  • Irken is memory-safe and type-safe, C++11 is neither
  • Irken uses a tracing Garbage Collector for automatic memory management, while C++11 relies on manual memory management and reference counted smart pointers
  • Irken structural typing is runtime row-polymorphic, while C++11 template structural typing is compiled parametric
  • Irken has built-in variants with exhaustive match, while C++11 relies on libraries like boost::variant
  • Irken has pattern matching, while C++11 does not
  • Irken is stackless with stack frames in the heap, while C++11 uses a class C-stack, as a result..
    • Irken has extremely lightweight co-routines, while C++11 does not
    • Irken supports first-class continuations, while C++11 does not
  • C++11 has argument-type overloaded function dispatch, while Irken does not
  • C++11 has runtime dynamic casts, while Irken does not
  • C++11 has a module and namespacing system, while Irken does not
  • C++11 runtime polymorphism uses subtyping and a vtable, while Irken's Row Polymorphism uses a hashed lookup table

Irken has no NULL pointer exceptions

Like Rust, Irken does not allow NULL pointers. Instead, values which may not be present are represented by an explicit Option Variant Datatype. In Irken, variants must be unfolded using pattern matching, which exhaustively checks to make sure you handle all the cases. When the unfold of types is explicit, it is known as having isorecursive datatypes and is the source of Irken's safety from NULL pointer exceptions. Of course if for some reason you like NULL pointer exceptions, you can always make a function that unwraps and raises one if :no.

(datatype maybe   ;; Irken's option type
 (:yes 'a)
 (:no))

(define (do_something x)
   (match x with
     (:yes a) -> (something_with a)   ;; the pattern match binds 'a' to the value in the matching data
     (:no) -> (something_because_x_was_missing)
     ;; it is an error to omit the "no" case
   ))

Irken has Pattern Matching

We already saw an example of pattern matching in handling our maybe type above. However, it can do more. For example, it can match on specific literal values. In this case as well, the compiler will report an incomplete match error if there are any unhandled cases. Also, functions may be written in dense pattern matching form, where their arguments are implicitly present in the pattern arguments. An underscore (_) is used for elements whose value we don't care about and don't want to bind. For example:

;; normal "match" form...
(define (if_a_then_b_else_c a b c)
  (match a b c with
    #t b _ -> b
    #f _ c -> c
  )
)

;; dense pattern matching...
(define if_a_then_b_else_c  
    #t b _ -> b
    #f _ c -> c 
)

Type Inferenced Structural Typing over Parametrics

Let's start with an example both languages handle somewhat similarly, Type-Inferenced Structural Typing over Parametrics. Whew, that was a mouthful. That means, both languages can parametrically instantiate functions in an expression, to the types in the expression, without type declarations, and match field access 'structurally' over those types (without a conforming super type).

Let's look at the Irken code. What this does is define a structure with fields (a b c), and calls structurally parametric functions which individually access those fields.

(include "lib/basis.scm")

(define (fa x) 
   (begin 
       (printf x.a) 
       x
    ))

(define (fb x) 
   (begin 
       (printf x.b) 
       x
    ))
(define (fc x) 
   (begin 
       (printf x.c) 
       x
    ))

(fc (fb (fa {a="1" b="2" c="3"})))  ;; prints "123"
(fc {c="4"}) ;; prints "4"

C++11 can do the same trick, using auto and template functions.

#include <iostream>

auto fa = [] (auto x) {
  std::cout << x.a;
  return x;
};

auto fb = [] (auto x) {
  std::cout << x.b;
  return x;
};

auto fc = [] (const auto & x) {
  std::cout << x.c;
  return x;
};

int main(int argc, char **argv) {
  struct S { int a; int b; int c; };
  fc(fb(fa(S({1, 2, 3}))));

  struct G { int c; };
  fc(G({3}));
}

TODO:

  • add C++ function argument overloading example
  • add Existentially Quantified Row Polymorphism vs Interface Subtyping
  • add Boost pattern matching example