Skip to content

Comment on Fexl - a Function Expression Languageparent

Comments

OK, I'll discuss what's different and leave "better" out of it.

1. In Fexl, there is no distinction between data and function. All data structures such as lists, pairs, etc. are represented as functions.

2. Fexl has a small grammar, about as small as I think is feasible for expressing arbitrary functions. You could actually omit these rules:

  exp => \ sym = term exp
  exp => \ sym == term exp
  exp => ; exp
But the resulting forms would be far more difficult to write and understand.

3. Fexl has a very simple compilation and evaluation strategy, reducing everything to combinatorial forms. There are no "environments" or "closures" or "contexts" being whipped around at run-time. The resulting Fexl executable program is about 35K in size.

4. Unlike other functional programming languages, Fexl does not rely on "pattern matching" for branching on the different possible forms of a piece of data. Instead, it simply calls that piece of data as a function, passing in the appropriate handlers for the various cases. For example, using excessively verbose function names here:

  list
      handle_empty_list
      \head\tail handle_first_item head; handle_remainder tail
5. Fexl doesn't really have a distinct concept for defining a name -- that's all handled normally by lambda calculus. However it does provide the syntactic shorthand "=". For example this function:
  \square = (\x mul x x)
  print (square 4)
Is equivalent to this function, which does not use the "=":
  (\square print (square 4)) (\x mul x x)
I'm not exactly sure if that's oh-so-different from other programming languages, but I'll venture a guess that many of those other languages use a symbol table to store function definitions at run-time, while Fexl does not.

6. Fexl never creates circular data structures in memory. Now because of lazy evaluation, you can create logically circular or infinite structures in Fexl, but these are always closed forms and never involve literal circularity in memory. Consequently it is possible to manage memory using reference counting -- and Fexl does that. Some may not like that, but it's simple and it gets the job done. The code even has a built-in assertion to ensure that all memory was properly reclaimed at the end of a run.

(I'm not certain that other languages create circular structures in memory, but I am certain that Fexl does not so I thought it worth mentioning.)

You can change R3RS Scheme a little to get a language whose types are all functions that defines cons in the same way as in the lambda calculus, but passing around symbols to describe types:

     (define cons (lambda (car cdr) (lambda (z) (z 'pair car cdr))))
     (define car (lambda (pair) (pair (lambda (type car cdr) car)))) ; we don't actually check type correctness in this simple example
     (define cdr (lambda (pair) (pair (lambda (type car cdr) cdr))))
     (define type-of (lambda (x) (x (lambda (type . rest) type))))
     (define pair? (lambda (x) (eqv? 'pair (type-of x))))
Similar techniques can be used to code Booleans, nil, &c.

Symbols in turn can be lifted out to be functions, whose type "symbol" is some particular function, say identity: here we need to have an atomic operation to define whether two functions are realised using the same closure, which is the usual meaning of eqv?.

While Scheme has mutable structures (e.g., set-car!) and circular structures, this alternate language would not.

Note that the Haskell 98 standard does not require implementations to let circular structures be defined, ghc does allow them (e.g., ones = 1:ones is a circular structure in ghc, but Haskell 98 would allow a Haskell to implement it as an infinite list).

That's interesting, but excluding the "reducing to combinatorial forms" thing, which only seems to be an implementation detail, can't this all be done in Scheme or another Lisp dialect? "no distinction between data and function" goes hand in hand with https://secure.wikimedia.org/wikipedia/en/wiki/Homoiconicity

You can certainly do this sort of thing in Lisp, using forms like:

  (defun square (x) ...)
  (lambda (x y) ...)
  (add 2 (add 3 (add 4 5)))
In Fexl you would see instead:
  \square = (\x ...)
  \x\y ...
  add 2; add 3; add 4 5
So at least Fexl has the virtue of being more compact in those cases. :) Also, in Fexl, whenever you see a name, it always refers to a function, unlike in Lisp, where names like "defun" and "lambda" and "prog" are meta-logical syntactic devices and cannot be defined as functions in their own right. And anything like "setq" or "setf" is strictly out of the question in Fexl.

You could of course implement lazy techniques in Lisp, even going so far as to write a Fexl interpreter if you like. I just wanted to see what happened if procedural, mutable, and meta-logical constructs were completely eliminated as possibilities in a language.

So, Haskell without types and monads basically? ;)

Definitely without the types, yes. However, monads are completely do-able in Fexl. I can write Fexl code that looks procedural and "side-effect-y", using the monadic technique so that you never actually see the state variable that's being chained through the functions. Monads are more a style of code than a feature of the language per se.

Personally I'm quite happy without the baggage of type declarations. As long as I build up functions systematically, I have very few problems with run-type type violations. Sure every once in a while I forget a semicolon or whatever and my function gets "out of synch" like a T-1 line gone out of phase. But it's usually pretty easy to see what went wrong.

Yeah, I know how monads work, what I mean was that according to the examples you don't seem to enforce purity(nothing like the IO monad).

And I've never found types to be a burden in Haskell, the type inference works great so that you can usually omit them if you can't be bothered, but I still include them most of the time because they help you reason about your code and see patterns.

Right, I don't enforce purity, but I do allow it. Ultimately there's a "string_put" function which (1) produces an actual side effect and (2) evaluates to the identity function. You can wrap monadic (monastic?) purity around that if you like.

Also, strict typing is pretty much impossible in Fexl, since I'm using combinators. You can't really assign a meaningful type to things like S, C, I, Y, etc. So yes, Fexl is very "loosey-goosey" that way. I also didn't want to bother with some ponderous PhD project like a "type inference engine" written into my ANSI-C interpreter. I figure if you want to do high level things like that, write those tools in Fexl itself (i.e. use meta-programming techniques).

AboutSource Built by g1lg1l

Hackerly is an independent reader for Hacker News, built on the public HN API. Not affiliated with Y Combinator.