I have worked with many programming languages during my career. I've used ActionScript 3 for Flash games. I've used Java for backend and Android games. I've used Scala for the backend. I've used JavaScript for NodeJS backend, React web applications, and React Native mobile applications. I've written a million simple scripts in Python.
But no matter what language I used, I had a feeling that the syntax of this language is too verbose, full of excesses, noise, and a syntactic boilerplate that interferes with understanding the code. So I had decided to create my own programming language.
First, I defined a few parameters to measure language quality. So, in my opinion, the perfect language should be:
- Well readable
- Laconic
- Consistent
- Pure and beautiful
I took a few programming languages that I'd been familiar with and started to improve them by reducing the unneeded syntax. But whatever language I'd taken, in the end, I always got LISP.
It's right. LISP is the most consistent and laconic programming language. But nowadays, Common Lisp and all its offsprings (except Clojure maybe) are more toys to play with than a language to use in production. Moreover, LISP has one awful disadvantage in terms of beauty - too many parentheses.
If today you need to choose a language for business purposes, most probably you will take JavaScript. It has a giant friendly community and package manager NPM with tons of well-made libraries. What's more important, JavaScript itself is a perfectly designed language that allows writing code in two different ways: functional and OOP.
I prefer to write code in JavaScript in a purely functional style.
So my code looks like this:
const incrementNumbers = numbers => numbers.map(number => number + 1)
const takeNumbersGreaterThan = threshold => numbers => numbers.filter(number => number > threshold)
const func = (numbers, threshold) => {
const incrementedNumbers = incrementNumbers(numbers)
const filteredNumbers = takeNumbersGreaterThan(threshold)(incrementedNumbers)
return filteredNumbers
}
The code above doesn't make any real sense. It can be written much simpler but it's a great example of what's wrong in JavaScript when you write code in a functional way. It has too many syntax noises like const and return.
So I like LISP and JavaScript but both of them have disadvantages. LISP has too many parentheses and JavaScript has too many syntax noises. What to do?
So I decided to merge LISP and JavaScript. I took syntax from LISP but reduced the number of parentheses using meaningful tabs like in Python. And I took the platform from JavaScript so my language is being transpiled to JS so it has full interop with it and most operators in my language work just like in JavaScript.
So meet Una - the universal language of unified symmetries.
Syntax
Application order
The most important thing you should know about Una is how application order works.
You can set the application order in two different ways:
- wrap up expression with parentheses
- move expression to the next line with additional indentation
Let's look at the example. We won't use real operators, just letters.
Here we apply a
to b
:
a b
Here we apply a
to the result of application of b
to c
:
a (b c)
This expression we can also write using indentation:
a
b c
I think the underlying idea is pretty obvious but let's look at more complicated example:
a (b (c d)) (e (f g))
It can be writte like this:
a
b (c d)
e (f g)
or even like this:
a
b
c d
e
f g
Assignment
The most used operator in any programming language is assignment =
. Because of Una is pure functional language =
is not really assignment but only declaration of a constant.
= name 'John'
This operator takes its second parameter and assigns it to the first one. If there're more parameters, at first it applies the second parameter to the rest of them and then assigns the result to the first one. Sounds complicated but it's simple. It just means that we can write assigning expression with parantheses:
= z (calculate x y)
or without:
= z calculate x y
Arithmetical operators
Una has all basic arithmetical operators that work the same as in JavaScript:
-
+
- addition -
-
- subtraction -
*
- multiplication -
/
- division -
%
- modulo
Example:
= a (+ 1 2)
= b (- 2 1)
= c (* 3 2)
= d (/ 4 2)
= e (% 5 2)
Comparison operators
Una has all basic comparison operators that work the same as in JavaScript.
= a (== 1 1)
= b (~= 1 '1')
= c (!= 1 '1')
= d (!~= 1 '2')
= e (> 2 1)
= f (>= 2 1)
= g (< 1 2)
= h (<= 1 2)
The only thing you should mention that ==
in Una is the strict comparison like ===
in JavaScript. For unstrict comparison you should use ~=
.
Logical operators
The same with logical operators. They're a little bit different in Una:
= a (& true false)
= b (| true false)
= c (! true)
Conditional operators
Una has two conditional operators.
Ternary conditional operator works just like in JavaScript:
= value
? (> 2 1) "Greater" "Less"
Returnable conditional operator ?!
is used in sync/async functions and sync/async computations to return value by some condition. For example, following code in function will return "One"
if number
equals 1
:
?! (== number 1) "One"
Collections
Una has two collection types: array ::
and object :
.
Here's an example of creating a array of numbers
= numbers :: 1 2 3
Here's an example of creating an object of user:
= user :
name 'John'
age 13
parents :
mother :
name 'Alice'
age 42
father :
name 'Bob'
age 39
Just like in JavaScript you can deconstruct objects and arrays
= numbers :: 1 2 3
= (:: one two three) numbers
console.log one
= user : (name 'John') (age 12)
= (: name) user
console.log name
And also just like in JavaScript when creating objects and array you can use already declared consts:
= a 1
= numbers :: a 2 3
= name 'John'
= user :
name
age 13
To get a field from map or element from array you can use .
:
= list :: 1 2 3
= map : (a 1) (b 2)
console.log (. list 0)
console.log (. map 'a')
Also .
is used to call methods on any object.
You can do it like this:
= numbers :: 1 2 3
= incrementedNumbers
numbers.map (-> x (+ x 1))
or like this:
= numbers :: 1 2 3
= incrementedNumbers
.map numbers (-> x (+ x 1))
Symmetries
The best feature of Una is arrow symmetries.
Sync symmetry
Right sync arrow ->
is function. First parameter is function parameters. Last parameter is return of the function. All parameters between are simple code lines.
= sum -> (x y)
+ x y
= onePlusTwo -> ()
= one 1
= two 2
+ one two
Calling function is just an application of it to parameters:
= a (sum 1 2)
= b sum 1 2
= c
sum 1 2
= d sum
1
2
To call parameterless function just use ()
= randomNumber
Math.random ()
These functions can be used as lambda functions and be passed as a parameter to another function or can be returned as value from another function.
Left sync arrow <-
is immediatly invoked function. So it allows to isolate some part of code and run it.
In following example result immediatly calculates as 3
.
= result <-
= a 1
= b 2
+ a b
It's pretty good when you need to calculate something based on conditions:
<-
?! (== value 0) "Zero"
?! (== value 1) "One"
? (< value 10) "Less than ten" "More than ten"
Async symmetry
Right async arrow -->
is async function.
= getUserPosts --> user
database.loadPosts user.postIds
Left async arrow <--
is await.
= checkIfUserIsAdmin --> userId
= user <-- (database.loadUser userId)
== user.role 'admin'
Error symmetry
Right error arrow |->
is try-catch operator. First parameter is catch function. Other parameters are try lines. Unlike JavaScript try-catch
operator |->
in Una always returns some value and it doesn't have finally
block.
|->
<-
= getName null
getName ()
-> error
console.log error
'John'
If you need to run async code in try catch user <--
instead of <-
in try or -->
instead ->
in catch:
|->
<--
getNameAsync ()
--> error
console.log error
"John"
Left error arrow <-|
is throwing error.
= addOneToNumber -> number
?! (isNaN number)
<-| "number is not valid"
+ number 1
Module symmetry
Una modules are fully compatiable with JavaScript. You can import JavaScript modules to Una and you can import Una modules to JavaScript.
Right module arrow =->
is import.
If you pass modules: 'require'
to babel plugin options it works as require
.
If you pass modules: 'import'
or pass nothing to babel plugin options it works as import
.
=-> './index.css'
=-> 'react' React
=-> 'react' (: createElement)
=-> 'react' React (: createElement)
Left module arrow <-=
is export.
If you pass modules: 'require'
to babel plugin options it works as modules.export =
.
If you pass modules: 'import'
or pass nothing to babel plugin options it works as export
.
Default module export:
<-= a
Constant export:
<-= = a 1
Chaining symmetry
Right chainging arrow |>
is chaining by last parameter.
If you want to use such functional programming libraries as rambda
you will find |>
operator very useful.
In following example phone
constant equals 'IPHONE'
:
=-> 'ramda' R
= electronics ::
:
title ' iPhone '
type 'phone'
= phones |>
electronics
R.find
R.propEq 'type' 'phone'
R.prop 'title'
R.toUpper
R.trim
Left chainging arrow <|
is chaining by last parameter.
Because of Lisp-like application order it's hard to do chains with default JavaScript array methods. Look how ugly it looks:
= sum .reduce
.filter
.map (:: 1 2 3) (-> x (+ x 1))
-> x (> x 2)
-> (x y) (+ x y)
0
With <|
it can be rewritten as:
= sum <| (:: 1 2 3)
.map (-> x (+ x 1))
.filter (-> x (> x 2))
.reduce (-> (x y) (+ x y)) 0
React
There's no JSX in Una. So to work with React instead of JSX you should use React.createElement, where first parameter is component, second parameters is passing props, and the rest of parameters are children.
=-> 'react' React
= (: (createElement e)) React
= Component -> ((: count name))
e div (: (style (: backgroundColor 'red')))
e div : count
e div : name
For styling I recommend to use styled-components
. I will make code much cleaner. Here's the short example of React app with styled components
:
=-> './index.css'
=-> 'react' React
=-> 'react-dom' ReactDOM
=-> './styles' S
= (: (createElement e)) React
= App -> ((: name))
= (:: count setCount) (React.useState 0)
e S.Container :
e S.Hello (: (color 'green')) 'Hello, '
e S.Name : name
e S.IncrementCount
: (onClick (-> () (setCount (+ count 1))))
'Press me'
e S.Count : count
ReactDOM.render
e App (: (name 'John'))
document.getElementById 'root'
In the example above :
without arguments is just an empty object.
Afterword
So you can look at the documentation and find more examples in the Github repo of Una. Try to run examples, write your own code in Una and feel free to share your feedback. It's a lot left to do but I fell I'm on the right way.
Top comments (4)
Cool project!
Awesome project, definitely checking una out
Really cool!
Awesome. Noice