Elm

Личный сайт Go-разработчика из Казани

Elm is a functional reactive programming language that compiles to (client-side) JavaScript. Elm is statically typed, meaning that the compiler catches most errors immediately and provides a clear and understandable error message. Elm is great for designing user interfaces and games for the web.

1-- Single line comments start with two dashes. 2{- Multiline comments can be enclosed in a block like this. 3{- They can be nested. -} 4-} 5 6{-- The Basics --} 7 8-- Arithmetic 91 + 1 -- 2 108 - 1 -- 7 1110 * 2 -- 20 12 13-- Every number literal without a decimal point can be either an Int or a Float. 1433 / 2 -- 16.5 with floating point division 1533 // 2 -- 16 with integer division 16 17-- Exponents 185 ^ 2 -- 25 19 20-- Booleans 21not True -- False 22not False -- True 231 == 1 -- True 241 /= 1 -- False 251 < 10 -- True 26 27-- Strings and characters 28"This is a string because it uses double quotes." 29'a' -- characters in single quotes 30 31-- Strings can be appended. 32"Hello " ++ "world!" -- "Hello world!" 33 34{-- Lists, Tuples, and Records --} 35 36-- Every element in a list must have the same type. 37["the", "quick", "brown", "fox"] 38[1, 2, 3, 4, 5] 39-- The second example can also be written with two dots. 40List.range 1 5 41 42-- Append lists just like strings. 43List.range 1 5 ++ List.range 6 10 == List.range 1 10 -- True 44 45-- To add one item, use "cons". 460 :: List.range 1 5 -- [0, 1, 2, 3, 4, 5] 47 48-- The head and tail of a list are returned as a Maybe. Instead of checking 49-- every value to see if it's null, you deal with missing values explicitly. 50List.head (List.range 1 5) -- Just 1 51List.tail (List.range 1 5) -- Just [2, 3, 4, 5] 52List.head [] -- Nothing 53-- List.functionName means the function lives in the List module. 54 55-- Every element in a tuple can be a different type, but a tuple has a 56-- fixed length. 57("elm", 42) 58 59-- Access the elements of a pair with the first and second functions. 60-- (This is a shortcut; we'll come to the "real way" in a bit.) 61Tuple.first ("elm", 42) -- "elm" 62Tuple.second ("elm", 42) -- 42 63 64-- The empty tuple, or "unit", is sometimes used as a placeholder. 65-- It is the only value of its type, also called "Unit". 66() 67 68-- Records are like tuples but the fields have names. The order of fields 69-- doesn't matter. Notice that record values use equals signs, not colons. 70{ x = 3, y = 7 } 71 72-- Access a field with a dot and the field name. 73{ x = 3, y = 7 }.x -- 3 74 75-- Or with an accessor function, which is a dot and the field name on its own. 76.y { x = 3, y = 7 } -- 7 77 78-- Update the fields of a record. (It must have the fields already.) 79{ person | 80 name = "George" } 81 82-- Update multiple fields at once, using the current values. 83{ particle | 84 position = particle.position + particle.velocity, 85 velocity = particle.velocity + particle.acceleration } 86 87{-- Control Flow --} 88 89-- If statements always have an else, and the branches must be the same type. 90if powerLevel > 9000 then 91 "WHOA!" 92else 93 "meh" 94 95-- If statements can be chained. 96if n < 0 then 97 "n is negative" 98else if n > 0 then 99 "n is positive" 100else 101 "n is zero" 102 103-- Use case statements to pattern match on different possibilities. 104case aList of 105 [] -> "matches the empty list" 106 [x]-> "matches a list of exactly one item, " ++ toString x 107 x::xs -> "matches a list of at least one item whose head is " ++ toString x 108-- Pattern matches go in order. If we put [x] last, it would never match because 109-- x::xs also matches (xs would be the empty list). Matches do not "fall through". 110-- The compiler will alert you to missing or extra cases. 111 112-- Pattern match on a Maybe. 113case List.head aList of 114 Just x -> "The head is " ++ toString x 115 Nothing -> "The list was empty." 116 117{-- Functions --} 118 119-- Elm's syntax for functions is very minimal, relying mostly on whitespace 120-- rather than parentheses and curly brackets. There is no "return" keyword. 121 122-- Define a function with its name, arguments, an equals sign, and the body. 123multiply a b = 124 a * b 125 126-- Apply (call) a function by passing it arguments (no commas necessary). 127multiply 7 6 -- 42 128 129-- Partially apply a function by passing only some of its arguments. 130-- Then give that function a new name. 131double = 132 multiply 2 133 134-- Constants are similar, except there are no arguments. 135answer = 136 42 137 138-- Pass functions as arguments to other functions. 139List.map double (List.range 1 4) -- [2, 4, 6, 8] 140 141-- Or write an anonymous function. 142List.map (\a -> a * 2) (List.range 1 4) -- [2, 4, 6, 8] 143 144-- You can pattern match in function definitions when there's only one case. 145-- This function takes one tuple rather than two arguments. 146-- This is the way you'll usually unpack/extract values from tuples. 147area (width, height) = 148 width * height 149 150area (6, 7) -- 42 151 152-- Use curly brackets to pattern match record field names. 153-- Use let to define intermediate values. 154volume {width, height, depth} = 155 let 156 area = width * height 157 in 158 area * depth 159 160volume { width = 3, height = 2, depth = 7 } -- 42 161 162-- Functions can be recursive. 163fib n = 164 if n < 2 then 165 1 166 else 167 fib (n - 1) + fib (n - 2) 168 169List.map fib (List.range 0 8) -- [1, 1, 2, 3, 5, 8, 13, 21, 34] 170 171-- Another recursive function (use List.length in real code). 172listLength aList = 173 case aList of 174 [] -> 0 175 x::xs -> 1 + listLength xs 176 177-- Function calls happen before any infix operator. Parens indicate precedence. 178cos (degrees 30) ^ 2 + sin (degrees 30) ^ 2 -- 1 179-- First degrees is applied to 30, then the result is passed to the trig 180-- functions, which is then squared, and the addition happens last. 181 182{-- Types and Type Annotations --} 183 184-- The compiler will infer the type of every value in your program. 185-- Types are always uppercase. Read x : T as "x has type T". 186-- Some common types, which you might see in Elm's REPL. 1875 : Int 1886.7 : Float 189"hello" : String 190True : Bool 191 192-- Functions have types too. Read -> as "goes to". Think of the rightmost type 193-- as the type of the return value, and the others as arguments. 194not : Bool -> Bool 195round : Float -> Int 196 197-- When you define a value, it's good practice to write its type above it. 198-- The annotation is a form of documentation, which is verified by the compiler. 199double : Int -> Int 200double x = x * 2 201 202-- Function arguments are passed in parentheses. 203-- Lowercase types are type variables: they can be any type, as long as each 204-- call is consistent. 205List.map : (a -> b) -> List a -> List b 206-- "List dot map has type a-goes-to-b, goes to list of a, goes to list of b." 207 208-- There are three special lowercase types: number, comparable, and appendable. 209-- Numbers allow you to use arithmetic on Ints and Floats. 210-- Comparable allows you to order numbers and strings, like a < b. 211-- Appendable things can be combined with a ++ b. 212 213{-- Type Aliases and Custom Types --} 214 215-- When you write a record or tuple, its type already exists. 216-- (Notice that record types use colon and record values use equals.) 217origin : { x : Float, y : Float, z : Float } 218origin = 219 { x = 0, y = 0, z = 0 } 220 221-- You can give existing types a nice name with a type alias. 222type alias Point3D = 223 { x : Float, y : Float, z : Float } 224 225-- If you alias a record, you can use the name as a constructor function. 226otherOrigin : Point3D 227otherOrigin = 228 Point3D 0 0 0 229 230-- But it's still the same type, so you can equate them. 231origin == otherOrigin -- True 232 233-- By contrast, defining a custom type creates a type that didn't exist before. 234-- A custom type is so called because it can be one of many possibilities. 235-- Each of the possibilities is represented as a "type variant". 236type Direction = 237 North | South | East | West 238 239-- Type variants can carry other values of known type. This can work recursively. 240type IntTree = 241 Leaf | Node Int IntTree IntTree 242-- "Leaf" and "Node" are the type variants. Everything following a type variant is a type. 243 244-- Type variants can be used as values or functions. 245root : IntTree 246root = 247 Node 7 Leaf Leaf 248 249-- Custom types (and type aliases) can use type variables. 250type Tree a = 251 Leaf | Node a (Tree a) (Tree a) 252-- "The type tree-of-a is a leaf, or a node of a, tree-of-a, and tree-of-a." 253 254-- Pattern match variants in a custom type. The uppercase variants will be matched exactly. The 255-- lowercase variables will match anything. Underscore also matches anything, 256-- but signifies that you aren't using it. 257leftmostElement : Tree a -> Maybe a 258leftmostElement tree = 259 case tree of 260 Leaf -> Nothing 261 Node x Leaf _ -> Just x 262 Node _ subtree _ -> leftmostElement subtree 263 264-- That's pretty much it for the language itself. Now let's see how to organize 265-- and run your code. 266 267{-- Modules and Imports --} 268 269-- The core libraries are organized into modules, as are any third-party 270-- libraries you may use. For large projects, you can define your own modules. 271 272-- Put this at the top of the file. If omitted, you're in Main. 273module Name where 274 275-- By default, everything is exported. You can specify exports explicitly. 276module Name (MyType, myValue) where 277 278-- One common pattern is to export a custom type but not its type variants. This is known 279-- as an "opaque type", and is frequently used in libraries. 280 281-- Import code from other modules to use it in this one. 282-- Places Dict in scope, so you can call Dict.insert. 283import Dict 284 285-- Imports the Dict module and the Dict type, so your annotations don't have to 286-- say Dict.Dict. You can still use Dict.insert. 287import Dict exposing (Dict) 288 289-- Rename an import. 290import Graphics.Collage as C 291 292{-- Ports --} 293 294-- A port indicates that you will be communicating with the outside world. 295-- Ports are only allowed in the Main module. 296 297-- An incoming port is just a type signature. 298port clientID : Int 299 300-- An outgoing port has a definition. 301port clientOrders : List String 302port clientOrders = ["Books", "Groceries", "Furniture"] 303 304-- We won't go into the details, but you set up callbacks in JavaScript to send 305-- on incoming ports and receive on outgoing ports. 306 307{-- Command Line Tools --} 308 309-- Compile a file. 310$ elm make MyFile.elm 311 312-- The first time you do this, Elm will install the core libraries and create 313-- elm-package.json, where information about your project is kept. 314 315-- The reactor is a server that compiles and runs your files. 316-- Click the wrench next to file names to enter the time-travelling debugger! 317$ elm reactor 318 319-- Experiment with simple expressions in a Read-Eval-Print Loop. 320$ elm repl 321 322-- Packages are identified by GitHub username and repo name. 323-- Install a new package, and record it in elm-package.json. 324$ elm package install elm-lang/html 325 326-- See what changed between versions of a package. 327$ elm package diff elm-lang/html 1.1.0 2.0.0 328-- Elm's package manager enforces semantic versioning, so minor version bumps 329-- will never break your build!

The Elm language is surprisingly small. You can now look through almost any Elm source code and have a rough idea of what is going on. However, the possibilities for error-resistant and easy-to-refactor code are endless!

Here are some useful resources.

Go out and write some Elm!