Try typing these out and see what happens (click to insert):
fold (+), 0, [1 to 100]
is-prime = (x) -> x > 1 and all (x %), [2 to sqrt x] do -> [1 to 100] |> sum . filter is-prime
html <- $.get window.location.href alert "This page has #{$ html .text!.split '' .length} characters!"
LiveScript is a language that compiles to JavaScript. It has many features that assist in functional programming. Comparing to JavaScript or CoffeeScript, LiveScript syntax is noise-less and more flexible.
Type next!
to go to the next step.
Almost everything in LiveScript is an expression:
2 * (10 + 27 / 3)
if 2 * 2 == 4 then "two times two is four" else "impossible"
true and false
String interpolation works with any expression.
"This window's width is: #outer-width and this document's title is: #{document.query-selector "title" .text-content}"The language supports multi-line strings.
Type next!
to go to the next step. or
back!
to go to the previous step.
We define a variable by x = 42
JavaScript var
is not needed.
You can use dashes in the name of your variables and functions:
planet-name = 'Saturn'
. This is equivalent and will be compiled to
planetName = 'Saturn';
Defining functions is very lightweight:
add = (x, y) -> x + y
We don't need paranthesis for invoking functions:
add 5, 18
You can also call functions infix using backticks:
5 `add` 18
Some examples:
double = (x) -> x * 2
double-small-number = (x) -> if x < 100 then x * 2 else x
hello = (name) -> "Hello #name"
The result of the last expression inside a function body will be returned as its output. You can use return
to force returns earlier:
fact = (n) -> return undefined if n < 1 return 1 if n == 1 n * fact n - 1
You can use list comprehension to produce new lists:
[1 to 10]
or
[i * 2 for i in [1 to 10]]
Filtering: [i if odd i for i in [1 to 10]]
Nested comprehensions produce a flat list:
[i * j for i in [1 to 10] for j in [-10 to -1]]
For example here is the list of all Pythagorian triples that are less than 10:
[[a, b, c] for a in [1 to 10] for b in [1 to 10] for c in [1 to 10] when a*a + b*b == c*c ]
This is a tuple: color = ['white', 0xffffff]
You can use pattern matching to extract values from a tuple, list or an object.
[name, hex] = color "Hexadecimal value for color #name is: 0x#{hex.to-string 16}"
This is a list: list = ['A', 'B', 'C', 'D', 'E', 'F']
You can use pattern matching on the lists:
[head, ...rest] = ['A', 'B', 'C', 'D', 'E', 'F']
Two lists can be concatenated using ++
operator (one-to-ten = [1,2,3,4,5] ++ [6,7,8,8,10]
).
[head, ...rest] = ['A', 'B', 'C', 'D', 'E', 'F'] [head] ++ rest
[head] ++ rest
is the original list.
Pattern matching is especially useful for self documenting the code.
Using everything we know so far and a little bit of recursion we can implement the all famous quicksort function:
quicksort = ([x, ...xs]:list) -> return [] if list.length == 0 smaller-sorted = quicksort [a for a in xs when a <= x] bigger-sorted = quicksort [a for a in xs when a > x] smaller-sorted ++ [x] ++ bigger-sortedTry it out:
quicksort [7, 91, 22, 5, 2, 1, 83]
Here's a simple object literal:
saturn = name: 'saturn' mass: 568 orbital-period: 29 distance-from-sum: min: 9.05 max: 10.12Pattern matching also works on objets:
{distance-from-sun: {min, max}} = saturn "Perihelion: #min AU, Aphelion: #max AU"
You can use pattern matching to import certain properties from a node module:
{pi, sin} = require 'prelude-ls' sin pi/2prelude.ls is LiveScript standard library, we are goting to explore it a little bit later.
A curried function can be called with less arguments that defined with, and then it will return a partially applied function. This means that it returns a function whose arguments are those which you didn't supply.
Curried functions are defined by long arrows -->
, for example:
add = (x, y) --> x + y
, now
add5 = add 5
is a function that takes a number and adds 5 to it: add5 20
Binary infix operators can also be partially applied:
ten-times = (10 *)
devide-by-ten = (/ 10)
If the function takes only one parameter, you can ignore the parameter in arguments list and use the special keyword it
to refernece the parameter in the body of the function:
square = -> it * it
We can truncate a decimal number to different precisions.
truncate = (precision, i) --> ((i * 10**precision) - (i * 10**precision) % 1) / (10**precision)
truncate
takes two arguments: precision (that is the number of digits after decimal point) and the actual input that's the number that needs to be truncateed: truncate 2, pi
Now we can easily create different versions of the truncate function:
truncate0 = truncate 0
that returns a whole number.
truncate1 = truncate 1
that truncates with one decimal point; etc.
Now take a look at this example that returns a list of numbers that are π truncateed with different precisions:
[0 to 6].map(-> truncate it).map(-> it pi)OK what's going on here? Let's break this expression down to understand it:
[0 to 6]
creates a list of numbers from 0 to 6.
[0 to 6].map(-> truncate it)
creates a list of curried truncate functions. Each item in this list is the truncate function with a different precision. For example [0 to 6].map(-> truncate it)[2] == truncate 2
map(-> it pi)
applies every curried truncate functions to π.
Note that in each step it
refers to one element of the list that was produced in the previous step. it
in [0 to 6].map(-> truncate it)
is a number (0 to 6) and it
in map(-> it pi)
is a function.
Once we get used to LiveScript and its standard library (prelude.ls), we will omit these it
s using the curried form of map
from prelude.ls:
[0 to 6] |> map truncate |> map (<| pi)
Here's a function that takes no argument and always returns 5: five = -> 5
. Invoke this function by: five!
Use do
to invoke an anonymous closure automatically:
do -> area = screen.width * screen.height alert "Your screen area is #area pixels"Note the scope of
area
variable: it is not accesible outside the anonynous closure.
This is a colsure that remembers the number of times that it was invoked:
say-hello = do -> i = 0 (greetings) -> i := i + 1 "#i - Hello #{greetings}"We use
:=
to modify a variable that is defined in the outer scope.
Composing allows you to create new functions by composing them out of a series of functions. If f
and g
are two functions then
f . g
or f << g
is equivalent to f(g(x))
and f >> g
is equivalent to g(f(x))
For example, given: f = (3 *)
and g = (10 +)
(f << g) 5
is equivalent to
f(g(5))
or
(3 *)(10 + 5) = (3 *) 15 = 45
(f >> g) 5
is equivalent to
g(f(5))
or
(10 +)(3 * 5) = (10 +) 15 = 25
For an example take the length function:
length = (s) -> s.length
that returns the length of its input string. We can write its type with this notation:
length :: String -> Number
And odd function that returns true if its input is an odd number:
odd = (i) -> i % 2 == 1
; its type is:
odd :: Number -> Boolean
Now the type of is-length-odd = (length >> odd)
is:
is-length-odd :: String -> BooleanIts input is the input of the first function and its output is the output of the second function.
Generally:
f :: a -> b g :: b -> c (f >> g) :: a -> c
We grew up learning that the arguments of a function are palced at its right side: Sin(θ)
; but in LiveScript
we can pipe an argument to a function by |>
from its left side:
[1 to 10] |> sum
<|
evaluates its right side and pipes the result to the function at its left:
sum <| map sqrt, [1 to 10]
For example, the following snippet generates a list of 10 random integers between 0 and 100:
[1 to 10] |> map (-> Math.round <| Math.random! * 100)
We pipe the list of numbers between 1 to 10 to map function.
map invokes the anonynous function (-> Math.round <| Math.random! * 100)
for each item in the list.
The function ignores its input (the number), produces a random floating point number between 0 and 100 (Math.random! * 100
) and rounds it.
Backcalls allow you to unnest callbacks. They are defined using arrows pointed to the left.
The content length of this page:
html <- $.get window.location.href ; alert html.length
Using backcalls is a way for avoiding callback hell in serial asynchronous sequences:
html <- $.get window.location.href $script = $ '<html/>' .html html .find "script[src*='libs/']:first" content <- $.get ($script .attr 'src') alert "The content size of the first external script is #{content.length}"
prelude.ls is the recommended base library for LiveScript.
It defines many utility functions for working with lists and objects.
We've already seen map
and filter
:
[1 to 10] |> map (* 2) |> filter (> 10)Here we are doubling each integer between 1 and 10 and then filtering the result for numbers that are greater than 10.
concat-map
produces a flattened list;
The below snippet produces a list of lists using map:
[1 to 5] |> map (i) -> [1 to i]But it will produce a list of just numbers using concat-map:
[1 to 5] |> concat-map (i) -> [1 to i]
zip
zips two lists together! Producing a list of tuples:
alphabet = [65 to 90] |> map String.from-char-code alphabet `zip` [1 to 26]
You can pipe the output of the previous zip to a filter function to only select the even letters in the alphabet:
alphabet `zip` [1 to 26] |> (filter ([_, i]) -> even i) |> (map ([a, _]) -> a)You might go fancier and use a shorter expression that does the same job:
alphabet `zip` [1 to 26] |> filter (.1) >> even |> map (.0)
(.1) >> even
is a function composition.
(.1)
is a function that returns the second element of its input tuple (or array): (.1) [1, 2, 3]
Remember that alphabet `zip` [1 to 26]
is a list of tuples like [['A', 1], ['B', 2], ...]
. filter (.1) >> even
applies (.1) >> even
function on every element of this list. (.1) >> even
takes a tuple in its input, extracts its second element, and checks if it's even: for example:
(.1) >> even <| ['B', 2]
Fold has many names, JavaScript calls it reduce, C# programmers know it as aggregate. Fold is very powerful, every other function that operates on lists in prelude.ls can be written using folds.
For example here's how we sum all the elements inside an array:
fold ((acc, a) -> acc + a), 0, [1 to 10]fold takes three parameters:
fold :: ((acc, a) -> acc), acc, [a]
Since (+)
is a function, we can shorten our sum expression to:
fold (+), 0, [1 to 10]So we can define sum function like:
sum = fold (+), 0
sum
takes an array, sums it elements and returns the sum: sum [1 to 100]
It takes some time to fully appreciate the power of fold, if you have not used functional programming before. prelude.ls comes with a tons of other utility functions!
We just scratched the surface of LiveScript in this tutorial. Check out http://livescript.net for full documentation.
Cheers!