Skip to content

Instantly share code, notes, and snippets.

@wongjiahau
Last active March 18, 2021 10:02
Show Gist options
  • Save wongjiahau/03a2e51b1a0f8f8ef4089eb1a65ef2b2 to your computer and use it in GitHub Desktop.
Save wongjiahau/03a2e51b1a0f8f8ef4089eb1a65ef2b2 to your computer and use it in GitHub Desktop.
Lambda + Let expression Unified Syntax

Lambdas using Let Notation

Introduction

As we all know, let expression is actually just a syntactic sugar for immediately invoked lambda expression (IILA). Actually not entirely correct, as pointed out in this comment.

For example, in Haskell:

let x = a in b

is the same as

(\x -> b) a 

In the quest of minimising syntax, we can remove let expression, but it's obviously bad for UX because IILA is unreadable.

So why not the other way round, remove lambda expression and replace it with extended let expression?

Let's see.

Abstraction

Lambda abstraction:

\x -> b

can be written as let-expression that is not instantiated with any value:

let x; b

And so:

\x -> \y -> b

is equivalent to:

let x; let y; b

Application

Application in extended let-expression is equivalent to lambda. For example in lambda:

(\x -> b) a

can be written as follows in let-expression:

(let x; b) a

Argument's default value / Optional arguments

Since Haskell does not have a built-in mechanism to define argument's default value, we can still achieve the same effect with the used of Maybe type.

sayHello :: Maybe Int -> [String]
sayHello (Just count) = replicate count "Hello"
sayHello None = ["Hello"]

In this case it's acceptable to pass in None explicitly, but in case where we have a lot of arguments with default values, the call-site will become cluttered with noise.

In extended let-expression, we can easily introduce a keyword, say overridable to allow arguments with default values. For example,

sayHello =
  let overridable count = 1;
  replicate count "Hello"
  
sayHello -- ["Hello"]
sayHello 2 -- ["Hello", "Hello"]

But of course, overridable arguments must only appear after all mandatory arguments.

Summary

As we can see above, with one simple extension to let-expression, we can completely eliminate the necessity of the lambda syntax.
Namely the uninstantiated let-expression, let var: type; which allow us to introduce function abstraction.

Also, we can see that by simply introducing a minimal syntax, say the overridable keyword, we can allow function argument's default value without cluttering the language grammar.

Implications

From the UX perspective, the implication of using extended let-expression to replace lambda syntax is the reduction of choice, which can ultimately leads to better experience.
And hopefully, from the point of view of programming language designers, this kind of syntactical unification can be useful for those looking to minimise their language grammar.

Some Examples

let rec factorial = 
  let x: int;
  if x <= 1 then 
    1 
  else 
    x * factorial (x - 1)
let average = 
  let xs: int list;
  let sum = fold (let acc; let x; acc + x) 0 xs;
  sum / length xs
@wongjiahau
Copy link
Author

Then I also realised that this idea can be further extended to support a do-notation like syntax, for example:

let x =<bind> y; z 

Can be translated as:

bind(y, x => z)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment