8 Brief introduction into Wolfram Language for Clojure developers
ns for-developers.wolfram-for-clojurians
(:require [scicloj.kindly.v4.kind :as k]
(:as wl]
[wolframite.core :as w :refer :all
[wolframite.wolfram :exclude [* + - -> / < <= = == > >= fn
Byte Character Integer Number Short String Thread]]))
(wl/start!)
:status :ok, :wolfram-version 14.1} {
8.1 What is Wolfram?
According to Wikipedia,
The Wolfram Language is a proprietary, general, very high-level multi-paradigm programming language developed by Wolfram Research. It emphasizes symbolic computation, functional programming, and rule-based programming and can employ arbitrary structures and data. It is the programming language of the mathematical symbolic computation program Mathematica.
Moreover, the Wolfram Language has the unique position of being not only a programming language but also a full-scale computational language, that incorporates vast amounts of computable knowledge and lets one broadly express things computationally.
“Symbolic” means that everything is a symbolic expression and you can manipulate these expressions themselves - somewhat reminiscent of how you can transform code with Clojure macros.
8.2 Pitfalls
In Wolfram, everything is global by default and you need to take care to avoid that, when necessary. w/Block
and w/Module
may be useful here.
8.3 Building blocks
8.3.0.1 Symbolic expressions
Expressions have the generic form head[arguments...]
, which becomes Wolframite (head arguments...)
Ex.: Plus[Power[x, 2], Times[3, Power[y, 3]]]
. Notice that we can use undefined symbols, since this is just a symbolic expression, not (yet) a computation. In Clj:
"Plus[Power[x, 2], Times[3, Power[y, 3]]]") (wl/->clj
+ (** x 2) (* 3 (** y 3))) (
And if we try to evaluate this:
'x 2) (w/* 3 (w/** 'y 3)))) (wl/eval (w/+ (w/**
+ (** x 2) (* 3 (** y 3))) (
we get the same expression back, because x and y aren’t defined (yet).
An expression’s head identifies the type of data or operation being represented - f.ex. List
or Plus
.
8.3.0.2 Functions
There are multiple ways to create a function.
The canonical way of defining a named function is using patterns (see Section 8.3.0.7): f[x_] := x^2
, which defines the fn f
.
To create an ad-hoc function, we can use Function
similar to Clojure’s fn
or anonymous function literals with body&
, where the body may use #, #1, #2, ...
or (Slot 1), (Slot 1), (Slot 2), ...
equivalent to Clojure’s %, %1, %2, ...
. Ex.: Map[# + 2&,{1,2,3}]
.
In Wolframite, you’ll typically use w/fn
or leverage the operator form of functions (see Section 8.3.0.9.1).
8.3.0.3 Lists
A Wolfram List {1, "hello", 3.14}
becomes a vector in Wolframite: [1, "hello", 3.14]
.
List access by indexing (from 1) via [[idx or a range a.k.a. Span]]
. Here we access the first element:
"{1,2,3}[[1]]") (wl/->clj
1 2 3] 1) (Part [
1 2 3] 1)) (wl/eval (Part [
1
Here we extract a sublist:
"{1,2,3}[[1 ;; 2]]") (wl/->clj
1 2 3] (Span 1 2)) (Part [
→
1 2 3] (Span 1 2))) (wl/eval (Part [
1 2] [
Many operations “thread” over lists, applying to each element:
1 2 3] 10)) (wl/eval (Plus [
11 12 13] [
8.3.0.4 Iterators simplify repetitive operations
Table[x^2, {x, 4, 20, 2}]
in Wolfram is equivalent to Clojure’s (map (fn [x] (math/pow x 2)) (range 4 20 2))
, while Table[x, n]
functions as (repeat n x)
in Clojure.
See also the List Manipulation reference.
8.3.0.5 Associations
Similar to Clojure maps, with a unique syntax using Rules (see Section 8.3.0.6). Fortunately, in Clojure we can just use maps:
"<|\"a\" -> x, \"b\" -> y|>") (wl/->clj
"a" x, "b" y} {
8.3.0.6 Rules
Rules, or rewrite rules, of the form key -> value
predate associations and are used where you’d have expected a map, often to define options to functions, as in here: Import["demo.csv.gz", {"Data", 1}]) ;; 3}, "HeaderLines" -> 1]
(Think of this as saying “when evaluating the operation, replace HeaderLines with a truthy value.)
8.3.0.7 Patterns
are used to transform symbolic expressions into other symbolic expressions. F.ex. here we replace anything that matches f[singleArg]
with the arg + 5:
"Replace[f[100], f[x_] -> x + 5]") (wl/eval
105
Here, _
a.k.a. Blank is a pattern that matches any expression and a double blank __
matches any sequence of expressions. We can name the captured value by prepending a name, as in x_
. There is also |
for alternatives, _h
to capture expressions with the head h
, :>
for delayed rules.
Notice that this provides one way to define what we would call functions. Function
and lambdas are another way.
8.3.0.8 Real-World Entities
Real-world entities are symbolic expressions representing information about concepts, things etc. such as countries and chemicals.
We can use entity[“Properties”] to find a list of properties and EntityValue[entity, "Population"]
to get the value of a property.
We have two ways to represent entities in Wolframite, which both may be useful:
def LA-expr (Entity "City" ["LosAngeles" "California" "UnitedStates"])) (
"Population")) (wl/eval (EntityValue LA-expr
3849297 "People") (Quantity
def LA-evaled (wl/eval LA-expr)) (
"Population")) (wl/eval (EntityValue LA-evaled
3849297 "People") (Quantity
To get entity properties, we need a small workaround - Wolfram allows someEntity["Properties"]
but in our case it would mean trying to use the list (Entity ...)
as a function, which wouldn’t work. So we construct an expression list explicitly:
take 3 (wl/eval (list (Entity "City" ["LosAngeles" "California" "UnitedStates"])
("Properties")))
"City" "ActiveHomeListingCount")
((EntityProperty "City" "AdministrativeDivision")
(EntityProperty "City" "AggravatedAssault")) (EntityProperty
8.3.0.9 Various
- Assignments -
=
and:=
;Module
for scoping - Applying Functions -
Map
with the shorthand/@
,Apply
with the shorthand@@
- Use
;
to separate different side-effecting operations, as(do ...)
would in Clojure (Wolframite:w/do
) - Booleans:
True
,False
(Wolframite:true
,false
) - String: “…”
- Note: Indices in Wolfram start from 1, not 0
8.3.0.9.1 The “operator” form of functions
Many functions are similar to Clojure transducers such as map
in the regard that you can invoke them without the data they are intended to operate on, and they will return a function that can be applied to the data later. This is called the “operator” form. Examples are Map
and AllTrue
.
See Functionals & Operators for more info.
8.3.0.10 Clojure <-> Wolfram
Clojure | Wolfram | Comments |
---|---|---|
apply | Apply | |
comp | Composition | |
count | Length | |
filter | Select | |
nth | Part | 1-based indexing |
map | Map | |
partial | operator form | (see above) |
reduce | Fold | |
take | Part |
8.3.0.11 Additional resources
Read more in the online booklet The Wolfram Language: Fast Introduction for Programmers, which we have borrowed heavily from.
The dense one-page Wolfram Language Syntax may also be of use, especially when reading Wolfram code.