
Felix - a fast scripting language - nmcfarl
http://felix-lang.org
======
haberman
The web page rubs me the wrong way. How can you claim to be the fastest
anything without a single benchmark? How can you claim to be a "scripting
language" when you're statically-typed and compile to C++? What does
"scripting language" even mean then? How can you say things like "it will be a
bit slow the first time but subsequent runs will load much faster than any
VM." _Any_ VM? Are you really "much faster" than:

    
    
      $ time lua empty.lua 
    
      real	0m0.005s
      user	0m0.002s
      sys	0m0.002s
    
      $ time ./luajit empty.lua 
    
      real	0m0.005s
      user	0m0.001s
      sys	0m0.002s
    

Maybe there's cool stuff going on here but I can't get past being annoyed at
these over-hyped claims.

~~~
zem
are you referring to the "The fastest scripting language on Earth" tagline? i
saw that as a way to say "look, this is a compiled language that you can use
as a scripting language" with a touch of whimsical humour.

~~~
haberman
Interesting. If that's the way it is intended I missed it completely; I'd
definitely be much more receptive to that line of meaning.

~~~
Yttrill
Python and Lua are both compiled languages, almost all languages are compiled
these days. They compile to bytecode rather than native machine code, then run
an interpreter which may well do on-the-fly compilation to machine code (JIT).
Bytecode target makes the compiler platform independent. Felix does the same,
except the "bytecode" is ISO Standard C++.

~~~
stcredzero
_Python and Lua are both compiled languages, almost all languages are compiled
these days._

For some strange reason, certain misconceptions in CS/programming have half-
lives measured in several decades, such as this annoying distinction between
interpreters/VMs/compiled languages. We're at least 20 years out from
interpreters being meaningfully distinct from VMs and compiled languages.

~~~
haberman
> We're at least 20 years out from interpreters being meaningfully distinct
> from VMs and compiled languages.

Compiled languages generally don't support eval. That's a pretty meaningful
difference.

~~~
stcredzero
_> Compiled languages generally don't support eval._

Python is compiled to bytecode. It supports eval. So does Smalltalk. Lua would
fall into this category as well, and a whole bunch of others. I don't think
"compiled" means what you think it means anymore, which is my whole point.

~~~
haberman
> I don't think "compiled" means what you think it means anymore, which is my
> whole point.

See my other reply (<http://news.ycombinator.com/item?id=5012218>). "Compiled
language" is an informal term that usually means "to machine code." If you
take it to mean "compiles to any kind of IR at all," then basically all
languages are compiled and the term is meaningless. But that's not how the
term is generally used -- for example, see the Wikipedia article
(<http://en.wikipedia.org/wiki/Compiled_language>).

There is a real difference between languages that can meaningfully be compiled
directly to machine code and those that can only JIT-compile type-specialized
traces/functions (with guards that fall back to the interpreter if the
assumptions do not hold).

~~~
stcredzero
_> If you take it to mean "compiles to any kind of IR at all," then basically
all languages are compiled and the term is meaningless._

That's my point. It's a _misnomer_. It's the same thing as calling technology
with the ability to parse context free grammars "regexes." It's a common usage
that pollutes the precise meaning of technical terms. (And at the same time,
generates misconceptions based on those technical terms.)

 _> There is a real difference between languages that can meaningfully be
compiled directly to machine code and those that can only JIT-compile type-
specialized traces/functions_

Well, not so much as you'd think. In theory, the ability to do things like
eval cuts off a lot of direct compilation to machine code, but in practice, we
know this isn't necessarily true.

~~~
haberman
> That's my point. It's a misnomer.

I disagree (as do the books sitting on my shelf), but I'm not really
interested in debating this point of terminology.

> Well, not so much as you'd think.

No, really there is. Trying to deny this isn't insightful, it's myopic. Take
the C function:

    
    
      int plus2(int x) { return x + 2; }
    

You can directly compile this into the following machine code, which needs no
supporting runtime:

    
    
      lea    eax,[rdi+0x2]
      ret
    

Now take the equivalent function in Python:

    
    
      def plus2(x):
        return x + 2
    

Yes, it's true that this "compiles" (internally) to the following byte-code:

    
    
      3           0 LOAD_FAST                0 (x)
                  3 LOAD_CONST               1 (2)
                  6 BINARY_ADD          
                  7 RETURN_VALUE
    

The question is: can you execute this byte-code without implementing an entire
Python interpreter? The answer is no, because the BINARY_ADD opcode has to
handle the case where "x" is an object that implements an overloaded operator
__add__(). In this case, the user's __add__() method can be arbitrary Python
code, and therefore requires an entire Python interpreter to fully and
generally execute.

I expect you will want to talk about the limited circumstances where you can
specialize this function ahead-of-time, thereby avoiding the fully-general
implementation. This would again be missing the point. Python and C are
different beasts, and this difference has far-reaching consequences on their
implementations. Trying to draw an equivalence between all "compiled
languages" does a disservice to people who are trying to understand the
differences between them.

------
srean
@nmcfarl thanks for resubmitting with a different url. I find the home page a
bit baity but the tutorial page <http://felix-lang.org/web/tutorial.fdoc> is
great.

Depending on how you look, it is C++ speed OCaML, or perhaps more correctly
C++ in a fully type-inferred (unfortunately, nowadays almost everything gets
called type-inferred. Hence the added qualification "fully"), ML like
language.

It does whole program optimization. It uses a mix of lazy and eager evaluation
strategies for speed.

I believe it can generate Python modules too, thanks to how well it interacts
with C. The details have to be gleaned from the mailing list though.

~~~
nmcfarl
I was actually using submissions as a poor man’s substitute for HN search :)

I found the tutorial ( <http://felix-lang.org/web/tutorial.fdoc> ) to be a bit
info sparse when I had no clue what I was looking at. Once I’d poked a round a
bit it was much better, and started answering questions I was having - mainly
how FFI is handled: <http://felix-lang.org/web/nutut/intro/intro_01.fdoc>

A very cool project.

------
michaelfeathers
While I enjoy reading about new and interesting languages (note: Felix has
been around for over ten years), I really wish that language designers would
put example code for something like FizzBuzz right on the front page to give
people a flavor of the syntax.

I can't remember how many times in my life I've done a link expedition through
a website or docs just to see a simple programming example.

~~~
Yttrill

      for var i in 1 upto 15 do 
        println$ 
          match i % 3, i % 5 with
          | 0,0 => "Fizz-Buzz"
          | 0,_ => "Fizz"
          | _,0 => "Buzz"
          | _ => str i
          endmatch
        ;  
      done
    

Did I get the job?

------
watmough
They are smoking crack if they think anyone will use a scripting language that
doesn't have a functioning REPL.

My goto-languages for quick development are Perl 5, Clojure and Javascript.

All 3 are adequately fast for real tasks. All are cross-platform, and all 3
support a REPL that allows doing real work interactively.

These conditions are the absolute minimum to be viable as a scripting or
sketch/prototyping language.

~~~
asimjalis
What REPL do you use for Perl?

~~~
prakashk
As aflott does, I use the built-in debugger as REPL for most simple uses.

I also use re.pl from Devel::REPL (<https://metacpan.org/module/Devel::REPL>).

------
raphinou
I've kept an eye on felix-lang the last several months, and the project seems
to be progressing continuously.

I'm curious though: has it been used in production? I'd be _very_ interested
in reading real use stories, with up and down sides!

Also, how is the community doing, and what about contributors? Do both groups
grow?

~~~
zem
i've been keeping a vaguely interested eye on it for years, because it does
look interesting. it has failed to make it to the front of my "learn this
language next" priority queue due to the fact that i can't find anyone using
it for _anything_ , not even a hobby project on github. also, the
documentation is a bit sparse.

one killer feature that would make me start learning it immediately is good,
well-documented qt bindings (i'm sure it is possible, since felix compiles
down to c++, but with the sparse documentation i have no idea how i would go
about getting it up and running, and insufficient motivation to first learn
the language and then figure out how to do it).

------
jheriko
See fridgescript (<http://code.google.com/p/fridgescript/>) - because you are
going through C/C++ it is faster in its domain (x87 floating point work) if
given good clean code. Its not big or clever - just that C/C++ kills its
performance a little with restrictions like struct layout rules and standard
library math functions being rubbish... (note the compiler uses almost no
optimisation strategies and the language is very specific and almost useless
though)

That being said, I like the idea very much, I just object to the claim of
fastest and the value the claim implies. Beating C is very easy if you know
some very basic things about the restrictions placed on it by standards... :)

------
TylerE
Why do language designers continue to insist on semi-colon terminated lines?

~~~
reinhardt
Perhaps to dissuade the lowest common denominator of programmers that get hung
up on such superficial trivialities.

~~~
TylerE
But it's not triviality - it's noise - Signal: Good - Noise: Bad, got it?

If you REALLY need end of line markers, any half-way competent text editor can
display them for you.

~~~
Kiro
You obviously have no idea what signal-to-noise ratio is about if you think
semicolons are noise. Who are you trying to impress?

~~~
pekk
End of line is already encoded in source files as \n. Redundancy isn't noise,
but it is redundant.

~~~
seanmcdirmid
If end of line always means end of statement, by all means get rid of semi
colons. If some non-trivial algorithms is used to infer end-of-statement
boundaries that is more complicated than detecting end-of-line, then...there
will be subtle problems.

I went through this transition before in Scala, the error messages got a lot
worse after semi-colon inference was added. I wound up telling people to add
semi-colons to their code when they were scratching their head at some sort of
parse/type error message.

------
nmcfarl
This is interesting, but the home page is the most coherent and readable on
the site. Better resources look to be:

The Facebook group: <http://www.facebook.com/groups/243958412369802/>

And google group: [https://groups.google.com/forum/?fromgroups#!forum/felix-
lan...](https://groups.google.com/forum/?fromgroups#!forum/felix-language)

------
PuerkitoBio
Looks very interesting, well done to whoever did this. After an admittedly
quick and superficial glance at the tutorial, I'm not fan of chapter 6 though.
Why so many ways to write calls? With no idiomatic syntax, it means I have to
know all these variations if I hope to understand Felix code (since it is ok
for anyone to choose whichever style).

~~~
andrewflnr
I just don't understand why there are both gen and proc functions. Why have a
separate kind of thing for functions that don't return anything?

~~~
egonschiele
Functions aren't supposed to have side effects and procs are. It makes sense
since functions without side effects would allow for some nice compiler
optimizations. The part that scares me is that the compiler doesn't enforce no
side effects. This means you need to be extra careful when using someone
else's code.

~~~
andrewflnr
Yeah, I got that, but gen's and proc's both have side effects. Why?

~~~
Yttrill
Basically this is to support "expression" like syntax such as people are used
to in C, for example i++ and x=y are expressions with side effects in C and a
lot of C idioms depend on such things. This kind of thing was originally not
allowed but I personally found it clumsy to manually split out the imperative
parts from the functional parts, so I taught the compiler how to do it for me
:)

~~~
andrewflnr
But those expressions also return things. Why can't they be gen? Why would I
use a proc instead? It seems like I must be missing something, but I can't see
it.

------
mrcharles
I'll stick with Lua. I wouldn't give up the dynamic aspects of a language like
Lua just for a bit more speed. LuaJIT is more than enough, and if you are
doing the hard number crunching that makes speed an issue there's a good
chance it's not trivially harder just to write it in C++ to begin with.

~~~
dkersten
For raw number crunching, LuaJIT is plenty fast anyway (when you use the FFI
to instantiate native types), so unless you need something real special (SSE
perhaps), it may not even buy you that much to use C++ over LuaJIT.

~~~
iskander
Just a small clarificatation: the 2.x version of LuaJIT only compiles floating
point operations to SSE2 (<http://lua-
users.org/lists/lua-l/2010-03/msg00142.html>). I'm not sure, however, whether
it does vectorization or just operates on one float at a time.

------
andrewflnr
I like the array type syntax: an array of five ints is

    
    
      int*int*int*int*int === int^5
    

instead of something like int[5]. Lots of interesting little ideas.

~~~
jaipilot747
How is

    
    
        int*int*int*int*int

or even

    
    
        int^5
    

better than a plain int[5]? To me, the use of mathematical operators * and ^
causes dissonance.

~~~
munificent
If you're coming from a type theory background (which, granted, few are), this
operator is actually perfectly natural.

<http://en.wikipedia.org/wiki/Product_type>

~~~
lambdaphage
Aren't product types just tuples, whereas arrays correspond to list types? The
'^' operator can only construct arrays whose elements are of a single type,
right?

~~~
Yttrill
That's correct. However, somewhat problematically, Felix considers the tuple
int * int to actually be an array int ^ 2. It's not entirely clear this
"automatically applied isomorphism" is a good idea though. However it means
arrays "drop out" of the notion of a tuple as a special case.

Internally such arrays have a special representation which allows arrays of
100,000 values, something which could never be represented by a tuple type
(and still get reasonable compile times :)

------
hipjiveguy
how does this compare with haXe (haxe.org)?

I see that it's billed as a "C++ code generator" and as a "scripting
engine".... Does it generate C++ code that I could use without Felix
afterwards?

~~~
Yttrill
Yes, provided you collect all the required library headers and either sources
or binaries: the generated C++ still requires run time libraries and the top
level driver if it's a program.

There is actually an option of flx, --bundle=dirname, which puts the generated
C++ in a single directory, to make it simpler to ship the generated C++ to
another platform.

This does not do a full bundle, i.e. it doesn't package all the run time
support code as well. That's on the TODO list, so you could literally copy the
target directory to another machine and run "make" or something and only need
a C++ compiler to build it.

There's another aspect to your question: if by "use" you also mean "modify"
then the current state is that Felix generated C++ is a bit hard to read. The
code is "good enough" to add debugging prints but not much more. It would be
good to improve this so the code is more readable.

------
d0m
At first glance, it seems fairly complex. For instance, 4 different kinds of
variables, 3 different kinds of function. But I guess that's understandable if
it compiles to C++ and is looking to be highly efficient.

------
mitchi
Which high level optimisations are we talking about?

~~~
Yttrill
As a whole program analyser, Felix does a lot of optimisations. The most
important one is inlining. Felix pretty much inlines everything :)

When a function is inlined, there are two things you can do with the
arguments: assign them to variables representing the parameters (eager
evaluation) or just replace the parameters in the code with the arguments
(lazy evaluation).

Substitution doesn't just apply to functions: a sequence of straight line code
with assignments can be converted into an expression by replacing occurrences
of the variables with the initialising expressions.

For a small number of uses, substitution is the usually the most efficient.
For many uses, lifting the common expressions to a variable is more efficient.
If we're dealing with a function (in C++ the model is a class) for which a
closure is formed (in C++ the model is an object of the class) lazy evaluation
is very expensive because the argument itself must be wrapped in an object to
delay evaluation.

By default, Felix val's and function arguments use indeterminate evaluation
semantics, meaning the compiler gets to choose the strategy. This leads to
high performance, but it also means we need a way to enforce a particular
strategy: for example vars and var parameters always trigger eager evaluation.
This leads to some complication in the language.

Felix also does other optimisations, for example it does the usual self-tail
call optimisation. This one works best if you do inlining at the right point
to convert a non-self tail call (which cannot be represented for functions in
C) into a self-tail call (which is replaced by a goto).

Felix also does parallel assignment optimisation.

It ensures type-classes have zero cost (unlike Haskell which, by supporting
separate compilation, may have to pass dictionaries around).

There is quite a lot more: eliminating useless variables, functions, unused
arguments, etc. There are even user specified optimisations based on
semantics, such as

    
    
      reduce idem[T]  (x:list[T]) : list[T] = x.rev.rev => x;
    

which says reversing a list twice leaves the original list, so just get rid of
these two calls.

Actually one important aspect to the optimisation process: by default a
function is a C++ class with an apply() method. This allows forming a closure
(object). The object is usually allocated on the heap. However Felix "knows"
when it can get away with allocating such an object on the machine stack
instead (saving a malloc and garbage collection). Furthermore, Felix "knows"
when it can get away with a plain old C function, and generates one of those
instead if it can. And all of that occurs only if the function wasn't entirely
eliminated by inlining all the calls.

So although you should think of Felix functions and procedures as objects of
C++ classes allocated on the heap and garbage collected, any significant
program implemented with this model without optimisations would just drop
dead.

~~~
mitchi
These are very good optimisations indeed. Inlining adds a lot more speed than
people think. I don't understand why people want closures in their languages.
You basically want a function that cheats her own scope... I don't get where
the big deal is.

~~~
Yttrill
One needs closures, that is, functional values bound to some context, for
higher order functions (HOFs). For example in C++ much of STL was pretty
useless until C++11 added lambdas. Many systems provide for closures in C, by
requiring you register callbacks as event handlers, and these callbacks
invariably have an associated client data pointer. A C callback function
together with a pointer to arbitrary client data object is a hand constructed
closure.

The utility of closures derives from being able to split a data context into
two pieces and program the two halves separately and independently. For
example for many data structures you can write a visitor function which
accepts a closure which is called with each visited value. Lets say we have N
data structures.

Independently you can write different calculations on those values formed
incrementally one value at a time, such as addition, or, multiplication. Lets
say we have M operations.

With now you can perform N * M distinct calculations whilst only writing N + M
functions. You have achieved this because both halves of the computation are
functions: lambda abstraction reduces a quadratic problem to a linear one.

The downside of this is that the abstraction gets in the way of the compiler
re-combining the visitor HOF and the calculation function to generate more
efficient code.

There's another more serious structural problem though. The client of a HOF is
a callback. In C, this is very lame because functions have no state. In more
advanced languages they can have state. That's an improvement but it isn't
good enough because it's still a callback, and callbacks are unusable for
anything complex.

A callback is a slave. The HOF that calls it is a master. Even if the context
of the slave is a finite state machine, where there is theory which tells you
how to maintain the state, doing so is very hard. What you really want is for
the calculation part of the problem to be a master, just like the HOF itself
is. You want your calculation to read its data, and maintain state in the
first instance on the stack.

Many people do not believe this and answer that they have no problems with
callbacks, but these people are very ignorant. Just you try to write a simple
program that is called with data from a file instead of reading the file. An
operating system is just one big HOF that calls back into your program with
stream data, but there's an important difference: both your program and the
operating system are masters: your program reads the data, it isn't a function
that accepts the data as an argument.

Another common example of master/master programming is client/server paradigm.
This is usually implemented with two threads or processes and a communication
link.

Felix special ability is high performance control inversion. It allows you to
write threads which read data, and translates them into callbacks
mechanically.

Some programming languages can do this in a limited context. For example
Python has iterators and you can write functions that yield without losing
their context, but this only works in the special case of a sequential
visitor.

Felix can do this in general. It was actually designed to support monitoring
half a million phone calls with threads that could perform complex
calculations such as minimal cost routing. At the time no OS could come close
to launching that number of pre-emptive threads yet Felix could do the job on
a 1990's desktop PC (with a transaction rate around 500K/sec where a
transaction was either a thread creation or sending a null message).

~~~
mitchi
I'm learning a lot thank you. Felix is pretty impressive!

------
asimjalis
Out of curiosity: How did you generate the slides?

~~~
Yttrill
The webserver, which is written in Felix, translates a particular format
called "fdoc" to create the tutorial and slides, including embedded colourised
hyperlinked Felix and C++ code. The translation is to a mix of HTML5 and
Javascript. This link shows the source of the first slideshow:

<http://felix-lang.org/web/slides/language-overview.fdoc?text>

All the documentation and layout needs a lot more work. [BTW: I'm the primary
developer]

~~~
cpeterso
Yttrill, I see that Felix is 12+ years old. I remember reading about Felix on
Lambda the Ultimate a few years ago. :)

Over those 12 years, what are some of the biggest changes (in the language or
its implementation) you have made? Where the changes mostly evolutionary?

<https://github.com/felix-lang/felix/commits/master?page=135>

~~~
Yttrill
This is a hard question to answer. In general the emphasis changed from
getting the compiler to work, and to generate efficient code, to using the
technology to implement a rich set of libraries (feedback into compiler), and
then writing some simple tools (feedback into libraries and compiler).

Probably the single biggest feature was the introduction of Haskell style type
classes as a way to systematically provide "generic" features like comparisons
and conversions to string which are de rigueur in dynamically typed scripting
languages.

Less obvious but quite important was switching the parser from Ocamlyacc to
Dypgen combined with OCScheme, which together put the grammar in user space,
allowing almost the entire grammar to be put in the library. A lot of new
features were added with very little or no change to the compiler by just
adding some EBNF grammar and suitable Scheme action code to the parser.

This can be very effective. For example, with only one extra term in the
compiler, I added a dynamic object system that looks a lot like Java with
objects and interfaces including "implements" and "extends" stuff. It just
uses records of closures (the compiler mod added record extension), but the
syntax is neat and almost immediately I used it to factor the webserver into
separately compiled plugins. I originally implemented this as a kind of joke,
to show off the expressive power in respect of Domain Specific Sub-Languages,
but not intended for use. The joke was on me :)

~~~
cpeterso
Thanks for the background. Moving the grammar into "user space" from the
compiler is a particularly interesting feature!

------
eriksank
I find it definitely a nice language proposal, but there are a few issues, I
guess. A first one is that many of the distinctions introduced (such as
proc,fun,gen) are all optimization issues. I would appreciate if it were
possible to do that long after you're done with just making the program work.
The language may force the developer way too early in the process to think of
things that do not matter at that point. In the best case, it will matter
later on, when the program truly works. Another issue I have is that the
"simplifications" introduced initially lead to introducing lots and lots of
additional symbols such $ and #, just because the simplification was
apparently not leading to something simple. So, the language suffers from
enforcing a premature optimization mindframe as well as from overly complex
administrative simplification.

~~~
Yttrill
The proc/gen/fun distinction isn't entirely an optimisation issue, its a
semantic one, heavily related to the implementation model. In principle fun
and gen use the machine stack for return addresses whilst proc uses a heap
allocated list which allows cooperative multi-tasking using channels for
communication: such so-called spaghetti stacks are easy to swap. Machine
stacks can only be swapped in a conforming way in C/C++ by using pre-emptive
threads which is precisely what we're trying to avoid.

I said "entirely" above because semantics and optimisation are heavily
intertwined in any system.

The comment about "syntactic sugar" is valid: there are at least four
operators with different precedences all meaning "application of function to
arguments". However note that in most languages this is true anyhow: x + y is
really just add (x,y). Finding a good set of squiggles and marks that's
acceptable to many people isn't easy.

However the syntax is defined in the library, in user space, so you can add
your own grammar or design your own domain specific sub-language, and add your
favorite squiggles that way. Any contributions to making the standard syntax
simpler would be welcome: I'm constantly struggling with this issue because
I'm well aware syntax matters, especially early in learning a language.

~~~
eriksank
I have created a blog post explaining why I think the language has too many
problems to ever take off: [http://erik-poupaert.blogspot.com/2013/01/the-
felix-programm...](http://erik-poupaert.blogspot.com/2013/01/the-felix-
programming-language-or-how.html)

------
Toshio
Love at first sight, but the build script is broken.

<http://pastebin.com/raw.php?i=WzB5Jqyf>

~~~
Yttrill
It works with this:

~/felix>python3 Python 3.2.3 (v3.2.3:3d0686d90f55, Apr 10 2012, 11:25:50) [GCC
4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin

and also on Linux with this:

skaller@felix:~$ python3 Python 3.2.3 (default, Oct 19 2012, 20:10:41) [GCC
4.6.3] on linux2

I have been told that the script fails if you use a slightly different version
of Python:

    
    
      File "/usr/lib/python3.3/subprocess.py", line 906, in communicate
    

So it is an incompatibility in Python 3.3 subprocess module I think.

~~~
Toshio
Indeed, I tried it on a fully up-to-date installation of ArchLinux.

~~~
Yttrill
I am sorry about this problem. I have had a look and I understand what is
happening but not how to fix it. Python 3.2 does not provide a timeout in the
subprocess module. fbuild is using a module by Peter Astrand which derived a
new class and adds a timeout. Unfortunately Python 3.3 adds the exact same
argument to the API, but defaults it no None, which is clobbering the value
used in the derived class somehow, leading to timeout of None being added to a
floating point value.

