Join 10350+ others. No spamming.
I promise!

Follow us at github.



one-more-minute/Lazy.jl

78

one-more-minute / Lazy.jl

Julia

Functional programming for Julia


READ ME

Lazy.jl

Gitter chat

Pkg.add("Lazy")

Lazy.jl provides Julia with the cornerstones of functional programming - lazily-evaluated lists and a large library of functions for working with them. It's also a repository for some neat macros, which might be useful to you even if you don't want lazy lists (see below).

Firstly, the canonical examples, in Julia:

using Lazy

# Note : prepends. Don't forget the semicolon!
# Fibonacci sequence defined in terms of itself:
fibs = @lazy 0:1:(fibs + drop(1, fibs));

take(20, fibs)
#> (0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181)

# isprime defined in terms of the prime numbers:
isprime(n) =
  @>> primes begin
    takewhile(x -> x<=sqrt(n))
    map(x -> n % x == 0)
    any; !
  end

# the prime numbers defined in terms of isprime:
primes = filter(isprime, Lazy.range(2));

take(20, primes)
#> (2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71)

If you've done any functional programming, you already know how to use Lazy.jl; just head down to the reference below to see what functions are available.

Intro to Laziness

For the unfamiliar, laziness just means that the elements of the list aren't actally calculated until you use them. This allows you to perform all sorts of magic, like working with infinite lists or lists of items from the future.

# Even square numbers:
> esquares = @>> Lazy.range() map(x->x^2) filter(iseven);
# first 5
> take(5, esquares)
List:
  4
  16
  36
  64
  100
# 99th
> esquares[99]
39204

But lazy lists aren't just for mathematical tricks; you can use them very practically for things like file IO. For example, you might represent the lines of a terabyte-large file with a lazy list; you can process the lines as any other list, letting the IO happen in the background.

# TODO: lineseq example
@>> "file.txt" lineseq foreach(println) # Will work no matter many lines file.txt has

The other thing that seperates lists from arrays is the huge amount of functionality that comes with most functional programming libraries, including Lazy.jl - if you know your way around them, most data manipulation becomes a simple case of chaining a few functions together. Even if you do ultimately need arrays for speed, you could do worse than to prototype with lists.

Macros

The threading macros will pipe values through functions, a bit like the |> operator but far more flexible. They can make code a lot cleaner by putting function calls in the order they are applied. The best way to understand them is by example:

# Just like x |> f etc.
@> x f == f(x)
@> x g f == f(g(x))
@> x a b c d e == e(d(c(b(a(x)))))

# Unlike |>, functions can have arguments - the value
# preceding a function will be treated as its first argument
@> x g(y, z) f == f(g(x, y, z))

@> x g f(y, z) == f(g(x), y, z)

# @>> does the exact same thing, but with value treated as the *last* argument.

@>> x g(y, z) f == f(g(y, z, x))

@>> x g f(y, z) == f(y, z, g(x))

# @as lets you name the threaded argmument
@as _ x f(_, y) g(z, _) == g(z, f(x, y))

# All threading macros work over begin blocks

@as x 2 begin
 x^2
 x+2
end == 6

Lazy.jl also provides a switch statement of sorts via a macro.

@switch x begin
  1; "x equals one!"
  2; "x equals two!"
  "x equals something else!"
end

However, it's a bit more general than a regular switch in that you can test more than just equality:

@switch isa(x, _) begin
  Integer; "x is an integer!"
  FloatingPoint; "x is a float!"
  "x is something else!"
end

@switch _ begin
  a > b;  "more than!"
  a < b;  "less than!"
  a == b; "equal!"       # Note that this level of enthusiasm is not mandatory.
end

Where _ is replaced by the value for testing in each case. The final expression, if there is one, is used as the default value; if there is no default and nothing matches an error will be thrown.

Function Reference

List # The abstract type that represents lazy lists

list(1,2,3) == (1 2 3)

prepend(1, list(2,3,4)) == 1:list(2,3,4) == (1 2 3 4)

# Most list handling functions have similar names
# to those in Clojure.

# Create a seq from any iterator or array
seq([1,2,3]) == seq(1:3) == (1 2 3)

# Infinite list of an element
constantly(x) == (x x x ...)
constantly(1) == (1 1 1 ...)

# Infinite list of function calls
repeatedly(f) == (f() f() f() ...)
repeatedly(rand) == (0.634 0.478 0.776 ...)

# Inifnitely repeat list
cycle(a) == (a... a... a... ...)
cycle([1,2,3]) == (1 2 3 1 2 3 1 2 3 1 ...)

# Repeatedly nest function calls
iterate(f, x) == (x f(x) f(f(x)) ...)
iterate(x->x^2, 2) == (2 4 16 256 65536 ...)

range(2) == (2 3 4 5 ...)
range(1, 5) == (1 2 3 4 5)
range(1, 5, 2) == (1 3 5)

list(1,2,3) * list(4,5,6) == (1 2 3 4 5 6)

first(list(1,2,3)) == 1
tail(list(1,2,3)) == (2 3)

flatten(list(1,2,list(3,4))) == (1 2 3 4)

riffle
interpose
take
drop
takelast
droplast
takenth
takewhile
dropwhile
# These work as for arrays, but are
# lazy where appropriate.
map, reduce, filter, reverse
lazymap
reductions
remove
dorun
foreach
distinct
groupby
partition
partitionby
splitat
splitby

# @lazy is the secret sauce that makes infinite definitions
# work; usually you can just wrap your list definition in it:
@lazy [1,2,3] == (1 2 3)
# Define a lazy recursive function
constantly(x) = @lazy x:constantly(x)

# Make a function map itself over lists
@listable exp
exp(range()) == (2.71 7.38 20.08 54.59 148.41 ...)

# Threading macros, see above
@>, @>>