From Esolang
Jump to navigation Jump to search

(from “Ⅎunctional”, may be called “turned F” in ASCII contexts or when lazy to type) is undoubtedly an essence of an equational functional language. Its idea is simple, so it probably was stumbled upon by many, including User:Oerjan and User:Arseniiv [1]. Currently, the description here is as the latter of them sees fit.

Overview

The program is a series of function definitions, each as simple as a sole expression involving function’s arguments and other functions. There could be a restriction to use only names already defined at this point, including (Ⅎ′) or excluding (Ⅎ″) the one currently defined; such a restriction doesn’t matter for language expressivity.

When program is run, a function named main is evaluated. Overall evalution strategy is call-by-need, unless a modification (for example, to allow full IO) has something like Haskell’s seq or $!. When there’s nothing to evaluate, the resulting expression is printed.

The #Trivia section elaborates on things implied by the context in which this language was conceived.

Examples

Simplest unterminating program in Ⅎ/Ⅎ′:

main = main.

and in Ⅎ″:

w x = x x. main = w w.

A boolean conjunction (0 ∧ 1 in the example):

0 then else = else.
1 then else = then.
and x y = x y 0.
main = and 0 1.

Syntax

There we’ll go technical for a bit, to allow maximal freedom to both possible users and implementers.

Lexical

Whitespace should contain U+20 SPACE, U+09 CHARACTER TABULATION, CR and LF characters at least, and all Unicode characters with property WSpace at most. Identifier characters should contain all printable non-space ASCII characters U+21…U+7E except .=() at least, and all of Unicode general categories L, M, N, P, S except .=() at most.

High-level

The grammar in EBNF:

Program = Definition+

Definition = Identifier Arguments "=" Expression "."

Arguments = Identifier*

Expression = ClosedExpression+

ClosedExpression = Identifier | "(" Expression ")"

If an argument is eponymous with an already defined function, that function should be shadowed out in the definition’s body.

Let expressions

For purposes of being a lesser tarpit, one could, without a trace of shame, add let … in … expressions:

Expression = LetExpression | ClosedExpression+

LetExpression = "let" Definition+ "in" Expression

Now go depend on context and incapsulate all you want!

Trivia

This section can be useful for implementers not familiar with modern equational functional languages like Haskell, F# or Scala.

Expressions of form f args are well-formed even if there are less arguments given than declared for f—this is called partial application and results in a function which waits for more arguments until it can proceed to evaluate. This feature allows to define various data structures, like tuples or lists, and values like natural numbers or booleans.

Call-by-need strategy is a practical lazy evaluation strategy. Suppose we have a definition for f x1 ... xn. When evaluating f arg1 ... argn, no argument is evaluated until needed—that is, until we proceed to evaluate an application of form (xi ...) from the definition, and after argi is evaluated, we replace all occurences of xi by the result—in this regard, this strategy is different from call by name. Of course a sane implementation wouldn’t construct huge terms by multiple replacements, it can store occurrences of each xi in expression as references to the same mutable object that holds unevaluated argi at first, and then holds the result.

Lazy evaluation allows one to define things like conditionals. For example, one can first define boolean values like this:

0 then else = else.
1 then else = then.

and then write c t e for if c then t else e. Then, t will not be evaluated if c is 0, and vice versa.

As mentioned above, an implementation can have means to control evaluation order, for example a seq primitive. When evaluating seq a b, the first argument is forced first, then the second is returned. The usual idiom is to place a variable as a which needs to be fully evaluated in b. If a doesn’t have any variables in common with b, doesn’t diverge or have side effects, then seq a b is fully equivalent to b, albeit evaluated slower due to forcing of a.

Now you can’t say the article is obscure.

Power

The language is obviously as powerful as combinatorial calculus, so is Turing-complete.

Furthermore, it is TC even if only one pair of parentheses is allowed, as one can define classic combinators

K x y = x
S x y z = (x z) y z

then expand main in this basis, and finally extract all parenthesized subexpressions there into separate definitions.

If parentheses are banned altogether, the language is a push-down automaton, as each function effectively turns into a pattern match on the head of a list of functions, and there's no way to group multiple list elements into a single element.

See also

Combinatory logic, the corresponding computational model.

References