From 31b3b1e858d98a4c834658dca4d8be5fabaeea1b Mon Sep 17 00:00:00 2001 From: Chad Selph Date: Tue, 19 Mar 2024 16:44:56 -0700 Subject: [PATCH 1/3] first pass at cheatsheet for scala --- cheatsheets/gleam-for-scala-users.md | 1319 ++++++++++++++++++++++++++ 1 file changed, 1319 insertions(+) create mode 100644 cheatsheets/gleam-for-scala-users.md diff --git a/cheatsheets/gleam-for-scala-users.md b/cheatsheets/gleam-for-scala-users.md new file mode 100644 index 00000000..aaf840c6 --- /dev/null +++ b/cheatsheets/gleam-for-scala-users.md @@ -0,0 +1,1319 @@ +--- +layout: page +title: Gleam for Scala users +subtitle: Hello typesafe object-oriented functional programmers! +--- + +- [Comments](#comments) +- [Variables](#variables) + - [Match operator](#match-operator) + - [Variables type annotations](#variables-type-annotations) +- [Functions](#functions) + - [Exporting functions](#exporting-functions) + - [Function type annotations](#function-type-annotations) + - [Referencing functions](#referencing-functions) + - [Labelled arguments](#labelled-arguments) +- [Operators](#operators) +- [Constants](#constants) +- [Blocks](#blocks) +- [Data types](#data-types) + - [Strings](#strings) + - [Tuples](#tuples) + - [Lists](#lists) + - [Maps](#maps) + - [Numbers](#numbers) +- [Flow control](#flow-control) + - [Case](#case) + - [Piping](#piping) + - [Try](#try) +- [Type aliases](#type-aliases) +- [Custom types](#custom-types) + - [Records](#records) + - [Algebraic Data Types](#algebraic-data-types) + - [Opaque custom types](#opaque-custom-types) +- [Type Parameters](#type-parameters) +- [Modules](#modules) + - [Imports](#imports) + - [Named imports](#named-imports) + - [Unqualified imports](#unqualified-imports) +- [Architecture](#architecture) + +## Comments + +### Scala + +In Scala, comments are written with a `//` prefix. + +```scala +// Hello, Joe! +``` + +Multi line comments may be written like so: + +```scala +/* + * Hello, Joe! + */ +``` + +In Scala, above `trait`, `object`, `class`, `def` declarations +there can be `scaladoc` like so: + +```scala +/** + * a very special trait. + */ +trait Foo {} + +/** + * A Bar class + */ +class Bar { + + /** + * + * @param s describes the s param + */ + def quux(s: String) = {} + +} + +``` + +Documentation blocks (scaladoc) are extracted into generated API +documentation and use Markdown format. + +### Gleam + +In Gleam, comments are written with a `//` prefix. + +```gleam +// Hello, Joe! +``` + +Comments starting with `///` are used to document the following function, +constant, or type definition. Comments starting with `////` are used to +document the current module. + +```gleam +//// This module is very important. + +/// The answer to life, the universe, and everything. +const answer: Int = 42 + +/// A main function +fn main() {} + +/// A Dog type +type Dog { + Dog(name: String, cuteness: Int) +} +``` + +`//` comments are not used while generating documentation files, while +`////` and `///` will appear in them. + +## Variables + +Scala has mutable and immutable variable bindings; in Gleam, all data is immutable but +variable names can be reassigned in the same scope. + +### Scala + +```scala +val size = 50 +val size = size + 100 // compile error +val size = 1 // compile error + +var size2 = 50 +size2 = size2 + 100 +size2 = 1 +``` + +Scala uses `val` for immutable bindings and `var` for mutable bindings. +Lexical scoping can be done with braces or indentation, and values with the same name +can shadow each other. + +```scala + +def foo() { + val size = 50 + + def bar() { + val size = 100 + } +} + +``` + + +### Gleam + +Gleam has the `let` keyword before its variable names. + +```gleam +let size = 50 +let size = size + 100 +let size = 1 +``` + +### Match operator + +Both Scala and Gleam can use the left hand side of an `=` as a pattern. + +#### Scala + +```scala +val List(a, b) = List(1, 2) +// a == 1 +// b == 2 +``` + +#### Gleam + +In Gleam, `let` and `=` can be used for pattern matching, but you'll get +compile errors if there's a type mismatch, and a runtime error if there's +a value mismatch. For assertions, the equivalent `let assert` keyword is +preferred. + +```gleam +let #(a, _) = #(1, 2) +// a = 1 +// `_` matches 2 and is discarded + +let assert [] = [1] // runtime error +let assert [y] = "Hello" // compile error, type mismatch +``` + +Asserts should be used with caution. + +### Variables type annotations + +#### Scala + +Scala is a strongly typed language with pretty good type inference. +Type annotations are usually optional. It may be useful for documentation +and improving compile times for complex inferences. + +```scala +val someList: List[Int] = List(1, 2, 3) +val someString: String = "Foo" +``` + + +#### Gleam + +In Gleam type annotations can optionally be given when binding variables. + +```gleam +let some_list: List(Int) = [1, 2, 3] +let some_string: String = "Foo" +``` + +Gleam will check the type annotation to ensure that it matches the type of the +assigned value. It does not need annotations to type check your code, but you +may find it useful to annotate variables to hint to the compiler that you want +a specific type to be inferred. + +## Functions + +### Scala + +In Scala, you can define methods with the `def` keyword. +The return value is the value of the last expression. + +```scala +def hello(name: String = "Joe"): String = { + if (name == "Joe") { + "Welcome back, Joe!" + } else { + s"Hello $name" + } +} + +``` + +For methods with no arguments, you may omit the parenthesis + +```scala + +def noArgs = "Surprise." + +``` + +Anonymous functions can also be defined and be bound to variables. + +```scala +val x = 3 +val anonFn = (y: Int) => x * y // Creates a new scope +anonFn(3) // 6 +``` + +### Gleam + +Gleam's functions are declared like so: + +```gleam +fn sum(x, y) { + x + y +} +``` + +Gleam's anonymous functions have the same basic syntax. + +```gleam +let mul = fn(x, y) { x * y } +mul(1, 2) +``` + +### Exporting functions + +#### Scala + +In Scala, class methods are public by default but can be made private or protected. + +```scala +object Foo { + // this is public + def sum(x: Int, y: Int): Int = x + y + + // this is private + private def mul(x: Int, y: Int): Int = x * y +} +``` + +#### Gleam + +In Gleam, functions are private by default and need the `pub` keyword to be +marked as public. + +```gleam +// this is public +pub fn sum(x, y) { + x + y +} + +// this is private +fn mul(x, y) { + x * y +} +``` + +### Function type annotations + +#### Scala + +Method parameters in Scala require type annotations. The return type is +usually optional but encouraged for public methods. + +```scala +object Foo { + // int return type will be statically inferred + def sum(x: Int, y: Int) = x + y + + def mul(x: Int, y: Int): Boolean = { + x * y // compile error, required Int found Boolean + } +} +``` + +#### Gleam + +Functions can **optionally** have their argument and return types annotated in +Gleam. These type annotations will always be checked by the compiler and throw +a compilation error if not valid. The compiler will still type check your +program using type inference if annotations are omitted. + +```gleam +fn add(x: Int, y: Int) -> Int { + x + y +} + +fn mul(x: Int, y: Int) -> Bool { + x * y // compile error, type mismatch +} +``` + +### Labelled arguments + +Both Scala and Gleam have ways to give arguments names and in any order. +Both are resolved at compile time, so there is no runtime performance cost. + +#### Scala + +When calling a method, arguments can be passed: + +- positionally, in the same order of the method declaration +- by name, in any order + +```scala +// Some imaginary replace function +def replace(inside: String, each: String, replace: String) = ??? + +// Calling with positional arguments: +replace(",", " ", "A,B,C") + +// Calling with named arguments: +replace(inside = "A,B,C", each = ",", replace = " ") +``` + +Parameters with default values can be omitted: + +```scala +def toCsvRow(items: List[String], separator: String = ","): String = items.mkString(separator) + +toCsvRow(List("a", "b", "c")) // a,b,c +toCsvRow(List("a", "b", "c"), "\t") // a\tb\tc +toCsvRow(List("a", "b", "c"), separator = "|") // a|b|c +``` + +Using parameter names in an external library may cause your code to +break if a newer version of that library changes the name of their +parameters as it may not be apparent to library authors their parameter +names are part of the public API if they didn't expect users to +pass them by name. + +#### Gleam + +In Gleam arguments can be given a label as well as an internal name. +Contrary to Scala, the name used at the call-site does not have to match +the name used for the variable inside the function. + +```gleam +pub fn replace(inside str, each pattern, with replacement) { + todo +} +``` + +```gleam +replace(",", " ", "A,B,C") +replace(inside: "A,B,C", each: ",", with: " ") +``` + +## Operators + +| Operator | Scala | Gleam | Notes | +|--------------------|---------------------------|---------------------------|-----------------------------------------------| +| Equal | `==` | `==` | In Gleam both values must be of the same type | +| Reference equality | `eq` | | | +| Not equal | `!=` | `!=` | In Gleam both values must be of the same type | +| Greater than | `>` | `>` | In Gleam both values must be **Int** | +| Greater than | `>` | `>.` | In Gleam both values must be **Float** | +| Greater or equal | `>=` | `>=` | In Gleam both values must be **Int** | +| Greater or equal | `>=` | `>=.` | In Gleam both values must be **Float** | +| Less than | `<` | `<` | In Gleam both values must be **Int** | +| Less than | `<` | `<.` | In Gleam both values must be **Float** | +| Less or equal | `<=` | `<=` | In Gleam both values must be **Int** | +| Less or equal | `<=` | `<=.` | In Gleam both values must be **Float** | +| Boolean and | `&&` | `&&` | In Gleam both values must be **Bool** | +| Logical and | `&&` | | Not available in Gleam | +| Boolean or | || | || | In Gleam both values must be **Bool** | +| Logical or | || | | Not available in Gleam | +| Boolean not | `!` | `!` | In Gleam both values must be **Bool** | +| Add | `+` | `+` | In Gleam both values must be **Int** | +| Add | `+` | `+.` | In Gleam both values must be **Float** | +| Subtract | `-` | `-` | In Gleam both values must be **Int** | +| Subtract | `-` | `-.` | In Gleam both values must be **Float** | +| Multiply | `*` | `*` | In Gleam both values must be **Int** | +| Multiply | `*` | `*.` | In Gleam both values must be **Float** | +| Divide | `/` | `/` | In Gleam both values must be **Int** | +| Divide | `/` | `/.` | In Gleam both values must be **Float** | +| Remainder | `%` | `%` | In Gleam both values must be **Int** | +| Concatenate | `+` | `<>` | In Gleam both values must be **String** | +| Pipe | | |> | Gleam's pipe can chain function calls | + +### Notes on operators + +- `==` by default uses Java's `Object::equals` method. Scala case classes + will automatically implement `equals` for you. You can enable strong type + checking on equals with `import scala.language.strictEquality` +- Scala operators are short-circuiting as in Gleam. +- In Scala, everything is an object, so operators are not special, they are + simply just methods with symbolic names. + +## Constants + +### Scala + +In Scala, there's no special feature for constants; since `val`s are +immutable bindings, they can be declared in any scope. + +```scala +object TheQuestion { + val TheAnswer: Int = 42 +} + +TheQuestion.TheAnswer // 42 +``` + +### Gleam + +In Gleam constants can be created using the `const` keyword. + +```gleam +// the_question.gleam module +const the_answer = 42 + +pub fn main() { + the_answer +} +``` + +They can also be marked public via the `pub` keyword and will then be +automatically exported. + +## Blocks + +### Scala + +In Scala, blocks can use either curly braces or indentation. + + +```scala +def main() = { + val x = { + someFunction(1) + 2 + } + // Parenthesis are used to change precedence of arithmetic operators + // Although curly braces would work here too. + val y = x * (x + 10) + y +} +``` + +or using indentation in Scala 3 + +```scala +def main() = + val x = + someFunction(1) + 2 + val y = x * (x + 10) + y +``` + +### Gleam + +In Gleam curly braces, `{` and `}`, are used to group expressions. + +```gleam +pub fn main() { + let x = { + some_function(1) + 2 + } + // Braces are used to change precedence of arithmetic operators + let y = x * {x + 10} + y +} +``` + +Like in Scala, in Gleam function blocks are always expressions, so are `case` +blocks or arithmetic sub groups. Because they are expressions they always +return a value. + +For Gleam the last value in a block's expression is always the value being +returned from an expression. + +## Data types + +### String character encoding + +On the JVM, Scala uses Java's String implementation which are internally stored as ASCII +or UTF-16 sequences. Strings can be encoded or decoded to/from a specific encoding. +Strings are defined with double-quotes `"` or triple-double-quotes `"""`, the later +allows for mutli-line strings. + +Scala has string interpolation and allows for custom string interpolators. + +In Gleam all strings are UTF-8 encoded binaries. Strings use double quotes and can +span lines. Gleam strings do not allow interpolation, yet. Gleam however offers a +`string_builder` via its standard library for performant string building. + +#### Scala + +```scala +val what = "world" +"""Hello +world!""" +s"Hellø, $what!" +``` + +#### Gleam + +```gleam +"Hello +world" + +"Hellø, world!" +``` + + +### Tuples + +Tuples are very useful in Gleam as they're the only collection data type that +allows mixed types in the collection. They behave very similar to Scala's tuples, +but are 0-index instead of 1 indexed when accessing by index. + +#### Scala + +```scala +val myTuple = ("username", "password", 10) // Type is (String, String, Int) or Tuple3[String, String, Int] +val (_, pwd, _) = myTuple +println(pwd) // "password" +// Direct index access +println(myTuple._1) // "username" +``` + +#### Gleam + +```gleam +let my_tuple = #("username", "password", 10) +let #(_, pwd, _) = my_tuple +io.print(pwd) // "password" +// Direct index access +io.print(my_tuple.0) // "username" +``` + +### Lists + +Both Gleam and Scala use immutable linked-lists for their standard lists. + +#### Scala + +Scala's `List` is a linked-list with a cons operator (`::`). + +```scala +val list = List(2, 3, 4) +val alsoList = 2 :: 3 :: 4 :: Nil // equivalent +val list2 = 1 :: list // list of 1,2,3,4 +val 1 :: secondElement :: tail = list2 // secondElement = 2 +``` + +#### Gleam + +Gleam has a `cons` operator that works for lists destructuring and +pattern matching. In Gleam lists are immutable so adding and removing elements +from the start of a list is highly efficient. + +```gleam +let list = [2, 3, 4] +let list = [1, ..list] +let [1, second_element, ..] = list +[1.0, ..list] // compile error, type mismatch +``` + +### Maps + +Scala's Map is an immutable hashmap; there is no literal syntax for it but +there is some sugar to use `->` as a 2-tuple which can be used to define +keys and values. + +In Gleam, maps can have keys and values of any type, but all keys must be of +the same type in a given map and all values must be of the same type in a +given map. The type of key and value can differ from each other. + +There is no map literal syntax in Gleam, and you cannot pattern match on a map. +Maps are generally not used much in Gleam, custom types are more common. + +#### Scala + +```scala +Map("key1" -> "value1", "key2" -> "value2") +Map("key1" -> "1", "key2" -> 2) // compiles, but infers an unexpected type +val myMap: Map[String, String] = Map("key1" -> "1", "key2" -> 2) // type error +``` + + +#### Gleam + +```gleam +import gleam/map + +map.from_list([#("key1", "value1"), #("key2", "value2")]) +map.from_list([#("key1", "value1"), #("key2", 2)]) // Type error! +``` + +### Numbers + +Scala has 8-bit, 32-bit, and 64-bit signed integers (`Byte`, `Short`, `Int`, `Long`) +and also 32-bit and 64-bit floating point numbers (`Float`, `Double`). For arbitrary +sized numbers, there are `BigDecimal` and `BigInteger`. + +Gleam has both support `Integer` and `Float`. Integer and Float sizes for +both depend on the platform: JavaScript and Erlang. + +#### Scala + +Scala will automatically cast integers into floating point numbers when doing +math operations between them. + +```scala +1 / 2 // 0 +1.5 + 10 // 11.5 +``` + +#### Gleam + +```gleam +1 / 2 // 0 +1.5 + 10 // Compile time error +``` + +You can use the gleam standard library's `int` and `float` modules to convert +between floats and integers in various ways including `rounding`, `floor`, +`ceiling` and many more. + +## Flow control + +### Case + +Case is one of the most used control flow in Gleam. It can be seen as a switch +statement on steroids. It provides a terse way to match a value type to an +expression. It is also used to replace `if`/`else` statements. + +Scala and Gleam both support guards, destructuring and disjoint union matching. + +#### Scala + +Scala has both `if`/`else` and `match` expressions. + +```scala +def httpErrorImpl1(status: Int) = { + if (status == 400) { + "Bad request" + } else if (status == 404) { + "Not found" + } else if (status == 418) { + "I'm a teapot" + } else { + "Internal Server Error" + } +} + +def httpErrorImpl2(status: Int): String = { + status match { + case 400 => "Bad request" + case 404 => "Not found" + case 418 => "I'm a teapot" + case _ => "Internal Server Error" + } +} +``` + +Cases can also have guards, similar to Gleam +```scala +status match { + case 400 => "Bad request" + case 404 => "Not found" + case _ if status / 100 == 4 => "4xx" + case _ => "I'm not sure" +} +``` + +The `|` operator works just like in Gleam. + +```scala +number match { + case 2 | 4 | 6 | 8 => "This is an even number" + case 1 | 3 | 5 | 7 => "This is an odd number" + case _ => "I'm not sure" +} +``` + +#### Gleam + +The case operator is a top level construct in Gleam: + +```gleam +case some_number { + 0 -> "Zero" + 1 -> "One" + 2 -> "Two" + n -> "Some other number" // This matches anything +} +``` + +As all expressions the case expression will return the matched value. + +```gleam +let is_status_within_4xx = status / 100 == 4 +case status { + 400 -> "Bad Request" + 404 -> "Not Found" + _ if is_status_within_4xx -> "4xx" // This works as of now + // status if status / 100 == 4 -> "4xx" // This will work in future versions of Gleam + _ -> "I'm not sure" +} +``` + +if/else example: + +```gleam +case is_admin { + True -> "allow access" + False -> "disallow access" +} +``` + +if/elseif/else example: + +```gleam +case True { + _ if is_admin == True -> "allow access" + _ if is_confirmed_by_mail == True -> "allow access" + _ -> "deny access" +} +``` + +Exhaustiveness checking at compile time, which is in the works, will make +certain that you must check for all possible values. A lazy and common way is +to check of expected values and have a catchall clause with a single underscore +`_`: + +```gleam +case scale { + 0 -> "none" + 1 -> "one" + 2 -> "pair" + _ -> "many" +} +``` + +The case operator especially coupled with destructuring to provide native pattern +matching: + +```gleam +case xs { + [] -> "This list is empty" + [a] -> "This list has 1 element" + [a, b] -> "This list has 2 elements" + _other -> "This list has more than 2 elements" +} +``` + +The case operator supports guards: + +```gleam +case xs { + [a, b, c] if a >. b && a <=. c -> "ok" + _other -> "ko" +} +``` + +...and disjoint union matching: + +```gleam +case number { + 2 | 4 | 6 | 8 -> "This is an even number" + 1 | 3 | 5 | 7 -> "This is an odd number" + _ -> "I'm not sure" +} +``` + +### Piping + +In Gleam most functions, if not all, are data first, which means the main data +value to work on is the first argument. By this convention and the ability to +specify the argument to pipe into, Gleam allows writing functional, immutable +code, that reads imperative-style top down, much like unix tools and piping. + +#### Scala + +Scala does not have piping operator built-in, although it can be easily implemented +with extension methods and `import scala.util.chaining._` helpers. + +That being said, it isn't really idiomatic or necessary because with OOP, +the first "argument" to the function is the object itself, so it's usually +more clear to just call the next method directly on the result. + +```scala +List(1, 2, 3, 4) + .map(_ * 2) + .filter(_ % 3 == 0) + .reduce((acc, x) => acc + x) +``` + +#### Gleam + +```gleam +[1,2,3,4] +|> list.map( fn(x) { x * 2 } ) +|> list.filter( fn (x) { x % 3 == 0 }) +|> list.reduce(fn (acc, x) { acc + x }) +``` + +### Try + +Error management is approached differently in Scala and Gleam. + +#### Scala + +Scala uses the notion of exceptions to interrupt the current code flow and +pop up the error to the caller. + +An exception is raised using the keyword `throw`. + +```scala +def aFunctionThatFails() = { + throw new RuntimeException("an error") +} +``` + +The callee block will be able to capture any exception raised in the block +using a `try/except` set of blocks: + +```scala +// callee block +try { + println("this line will be executed and thus printed") + aFunctionThatFails() + println("this line will not be executed and thus not printed") +} catch { + case (e: Throwable) => println(e) +} +``` + +However, this is not considered idiomatic Scala in most cases. Many libraries +provide more functional approaches to error-handling. The standard library +also includes such a structure which can wrap code that throws exceptions +and return the success or exception as a value. + +```scala +import scala.util.{Try,Success,Failure} + +// Integer.parseInt is from the Java std library and +// it throws a java.lang.NumberFormatException +Try(Integer.parseInt("123")) match { + case Success(i) => println("We parsed an Int") + case Failure(err) => println("That wasn't an Int") +} + +``` + +Unlike Gleam, the "`ErrorType`" in a `Try` cannot be changed, it +is always the Java Exception base-type, `Throwable`. There is also +`Either` in Scala, with more generic sub-types `Left` and `Right`, +but the compiler still cannot prevent code you call from throwing an +exception, which may circumvent your error handling. + +Both `Either` and `Try` can use `for` comprehensions when chaining +together multiple operations in a row. + +```scala +def parseInt(s: String): Try[Int] = Try(Integer.parseInt(s)) + +for { + intANumber <- parseInt("1") + attemptInt <- parseInt("ouch") // Error will be returned + intAnotherNumber <- parseInt("3") // never gets executed + +} yield { + intANumber + attemptInt + intAnotherNumber +} +``` + +#### Gleam + +In Gleam, errors are always containers with an associated value. + +A common container to model an operation result is +`Result(ReturnType, ErrorType)`. + +A `Result` is either: + +- an `Error(ErrorValue)` +- or an `Ok(Data)` record + +Handling errors actually means to match the return value against those two +scenarios, using a case for instance: + +```gleam +case parse_int("123") { + Ok(i) -> io.println("We parsed the Int") + Error(e) -> io.println("That wasn't an Int") +} +``` + +In order to simplify this construct, we can use the `use` expression with the +`try` function from the `gleam/result` module. + +- either bind a value to the providing name if `Ok(Something)` is matched, +- or **interrupt the current block's flow** and return `Error(Something)` from + the given block. + +```gleam +let a_number = "1" +let an_error = Error("ouch") +let another_number = "3" + +use int_a_number <- parse_int(a_number) +use attempt_int <- parse_int(an_error) // Error will be returned +use int_another_number <- parse_int(another_number) // never gets executed + +Ok(int_a_number + attempt_int + int_another_number) // never gets executed +``` + +## Type aliases + +Type aliases allow for easy referencing of arbitrary complex types. +This feature works similarly in Scala and Gleam. + +### Scala + +```scala +type Headers = List[(String, String)] +``` + +Scala can also make the type alias `opaque` which prevents the call-site +usage from passing in the underlying type. + +```scala + +opaque type Meters = Double + +def toFeet(meters: Meters): Double = meters * 3.2 + +toFeed(1.0) // compile error + +``` + +### Gleam + +The `type` keyword can be used to create aliases. + +```gleam +pub type Headers = + List(#(String, String)) +``` + +## Custom types + +### Records + +Custom type allows you to define a collection data type with a fixed number of +named fields, and the values in those fields can be of differing types. + +#### Scala + +The simplest way to make a new datatype in Scala is with case classes. + +```scala +case class Person(name: String, age: Int) +val person = Person("Joe", 40) +person.name // Joe +``` + +#### Gleam + +Gleam's custom types can be used as structs. At runtime, they have a tuple +representation and are compatible with Erlang records (or JavaScript objects). + +```gleam +type Person { + Person(name: String, age: Int) +} + +let person = Person(name: "Joe", age: 40) +let name = person.name +``` + +An important difference to note is there is no Java-style object-orientation in +Gleam, thus methods can not be added to types. However opaque types exist, +see below. + +### Algebraic Data Types + + +In Gleam functions must always take and receive one type. To have a union of +two different types they must be wrapped in a new custom type. + +#### Scala + +```scala + +enum IntOrFloat { + case AnInt(i: Int) + case AFloat(f: Float) +} + +``` + +or in Scala 2: + +```scala +sealed trait IntOrFloat +case class AnInt(i: Int) extends IntOrFloat +case class AFloat(f: Float) extends IntOrFloat +``` + +Although for this particular example, we could just use a union type (in Scala 3) + +```scala +type IntOrFloat = Int | Float +``` + +#### Gleam + +```gleam +type IntOrFloat { + AnInt(Int) + AFloat(Float) +} + +fn int_or_float(X) { + case X { + True -> AnInt(1) + False -> AFloat(1.0) + } +} +``` + +### Opaque custom types + +In Scala, constructors can be marked as private and a factory methods can be +added onto a companion object. + +In Gleam, custom types can be defined as being opaque, which causes the +constructors for the custom type not to be exported from the module. Without +any constructors to import other modules can only interact with opaque types +using the intended API. + +This is not to be confused with Scala's "opaque types" which are type aliases. + +#### Scala + +There's many approaches to encapsulating an implementation in Scala, here is just one. + +```scala + +trait Point + +case class PointImpl private(x: Int, y: Int) extends Point + +object Point { + def spawn(x: Int, y: Int): Option[Point] = { + if (x >= 0 && x <= 99 && y >= 0 && y <= 99) { + Some(PointImpl(x, y)) + } else { + None + } + } +} + +Point.spawn(1, 2) // Returns a Point object +``` + + +#### Gleam + +```gleam +// In the point.gleam opaque type module: +pub opaque type Point { + Point(x: Int, y: Int) +} + +pub fn spawn(x: Int, y: Int) -> Result(Point, Nil) { + case x >= 0 && x <= 99 && y >= 0 && y <= 99 { + True -> Ok(Point(x: x, y: y)) + False -> Error(Nil) + } +} + +// In the main.gleam module +pub fn main() { + assert Ok(point) = Point.spawn(1, 2) + point +} +``` + +## Type Parameters + +Gleam and Scala's type parameters work similarly, +but Scala has more syntax around constraining parameters. + +Also unlike Scala, Gleam does not have higher kinded types. + +### Scala + +Generic parameters are defined between `[` `]`. + +```scala + +enum Tree[T] { + case Branch(left: Tree[T], right: Tree[T]) + case Leaf(value: T) +} + +val newTree: Tree[Int] = Branch(Leaf(1), Leaf(2)) + +``` + + +### Gleam + +Generic parameters are defined between `(` `)`. + +```gleam +type Tree(t) { + Branch(left: Tree(t), right: Tree(t)) + Leaf(value: t) +} + +const new_tree: Tree(Int) = + Branch(Leaf(1), Leaf(2)) +``` + + +## Modules + +Gleam has a more explicit module system where every file is a module. + +### Scala +In Scala we can use traits or objects to group types aliases, methods, +and constants into a namespace. + +```scala +package foo.bar.baz // this package could be defined in any file + +object MyModule { + def identity[T](t: T) = t +} + +``` + +```scala +package another.packge + +import foo.bar.baz.MyModule + +def main() = MyModule.identity(1) // 1 + +``` + + +### Gleam + +The closest thing Scala has that are similar to Gleam's modules +are companion objects: Collections of functions and constants grouped into a +static class. + +A Gleam module name corresponds to its file name and path. + +Since there is no special syntax to create a module, there can be only one +module in a file and since there is no way name the module the filename +always matches the module name which keeps things simple and transparent. + +In `/src/foo/bar.gleam`: + +```gleam +// Creation of module function identity +// in module bar +pub fn identity(x) { + x +} +``` + +Importing the `bar` module and calling a module function: + +```gleam +// In src/main.gleam +import foo/bar // if foo was in a directory called `lib` the import would be `lib/foo/bar`. + +pub fn main() { + bar.identity(1) // 1 +} +``` + +### Imports + +#### Scala + +Classes in the same package do not need to be imported. +Otherwise, all imports are done from the full package name. + +#### Gleam + +Imports are relative to the app `src` folder. + +Modules in the same directory will need to reference the entire path from `src` +for the target module, even if the target module is in the same folder. + +Inside module `src/nasa/moon_base.gleam`: + +```gleam +// imports module src/nasa/rocket_ship.gleam +import nasa/rocket_ship + +pub fn explore_space() { + rocket_ship.launch() +} +``` + +### Named imports + +#### Scala + +Scala allows imports to be renamed: + +```scala +import java.util.{List => JUList} +``` + +#### Gleam + +Gleam has as similar feature: + +```gleam +import unix/cat +import animal/cat as kitty +// cat and kitty are available +``` + +This may be useful to differentiate between multiple modules that would have the same default name when imported. + + +## Architecture + +To iterate a few foundational differences: + +1. Programming model: Multi-paradigm function/object-orientation VS strictly functional immutable + programming +2. Runtime environment + +### Programming model + +- Scala encourages immutable, functional style of code but offers escape + hatches and mutable types, as well as access to the Java standard library. +- This can allow performance-critical code to be written in the most efficient + ways, but it can also lead to too many coding styles across the ecosystem + and even in a single codebase. +- In Gleam, there are no such escape hatches +- Scala inherits many of the warts from Java-compatability, such as nullability and exceptions +- Scala has several different approaches to concurrent applications, including + several the actor model implementations, multi-threading, and asynchronous programming. + Libraries written for one style do not necessarily interact well with libraries written + for another style. +- Gleam benefits from Erlang/Beam Processes, a concurrently model similar + to Project Loom on the JVM. This allows for writing in a "direct" coding + style without sacrificing scalability. +- Probably the biggest difference between Gleam and Scala's object-oriented features. In Scala, + everything is an object, and as such two values of different types maybe have a least-upper-bound. + This has the consequence of a more complicated and slower type inference system. +- Scala is a large language with many other features for which there are no Gleam equivalent, such as: + * Implicit parameters + * Extension methods + * Traits + * Singleton objects + * Type classes + * By-name parameters + + +### Runtime environment + +- Gleam runs on Erlang/BEAM and Scala runs on the JVM +- As a generalization, the JVM has better performance for computation but Erlang/BEAM + will have better concurrency. +- Both Scala and Gleam can compile to Javascript to run in the browser, NodeJS, Deno, etc. +- Scala can also compile to native code. From dce277f1d0ba4e075b29309c8e52f7b7c1991842 Mon Sep 17 00:00:00 2001 From: Chad Selph Date: Tue, 19 Mar 2024 16:46:56 -0700 Subject: [PATCH 2/3] add link to scala-cheatsheet from docs --- documentation.md | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation.md b/documentation.md index 694d4816..1abc8047 100644 --- a/documentation.md +++ b/documentation.md @@ -52,6 +52,7 @@ layout: page - [Gleam for PHP users](/cheatsheets/gleam-for-php-users) - [Gleam for Python users](/cheatsheets/gleam-for-python-users) - [Gleam for Rust users](/cheatsheets/gleam-for-rust-users) +- [Gleam for Scala users](/cheatsheets/gleam-for-scala-users) ## Deployment From e47ade1ecf9051d68eed64727f32f2820fe40402 Mon Sep 17 00:00:00 2001 From: Chad Selph Date: Sun, 17 Nov 2024 22:20:20 -0800 Subject: [PATCH 3/3] PR feedback (less explaining scala, remove foo/bar/baz usage) --- cheatsheets/gleam-for-scala-users.md | 246 +++++---------------------- 1 file changed, 40 insertions(+), 206 deletions(-) diff --git a/cheatsheets/gleam-for-scala-users.md b/cheatsheets/gleam-for-scala-users.md index aaf840c6..754485a1 100644 --- a/cheatsheets/gleam-for-scala-users.md +++ b/cheatsheets/gleam-for-scala-users.md @@ -56,33 +56,6 @@ Multi line comments may be written like so: */ ``` -In Scala, above `trait`, `object`, `class`, `def` declarations -there can be `scaladoc` like so: - -```scala -/** - * a very special trait. - */ -trait Foo {} - -/** - * A Bar class - */ -class Bar { - - /** - * - * @param s describes the s param - */ - def quux(s: String) = {} - -} - -``` - -Documentation blocks (scaladoc) are extracted into generated API -documentation and use Markdown format. - ### Gleam In Gleam, comments are written with a `//` prefix. @@ -101,13 +74,6 @@ document the current module. /// The answer to life, the universe, and everything. const answer: Int = 42 -/// A main function -fn main() {} - -/// A Dog type -type Dog { - Dog(name: String, cuteness: Int) -} ``` `//` comments are not used while generating documentation files, while @@ -124,29 +90,8 @@ variable names can be reassigned in the same scope. val size = 50 val size = size + 100 // compile error val size = 1 // compile error - -var size2 = 50 -size2 = size2 + 100 -size2 = 1 ``` -Scala uses `val` for immutable bindings and `var` for mutable bindings. -Lexical scoping can be done with braces or indentation, and values with the same name -can shadow each other. - -```scala - -def foo() { - val size = 50 - - def bar() { - val size = 100 - } -} - -``` - - ### Gleam Gleam has the `let` keyword before its variable names. @@ -167,14 +112,15 @@ Both Scala and Gleam can use the left hand side of an `=` as a pattern. val List(a, b) = List(1, 2) // a == 1 // b == 2 + +val List(c,d) = List(1) // runtime MatchError ``` #### Gleam In Gleam, `let` and `=` can be used for pattern matching, but you'll get -compile errors if there's a type mismatch, and a runtime error if there's -a value mismatch. For assertions, the equivalent `let assert` keyword is -preferred. +compile errors if there's a type mismatch. For assertions, the equivalent +`let assert` keyword is used. ```gleam let #(a, _) = #(1, 2) @@ -197,7 +143,6 @@ and improving compile times for complex inferences. ```scala val someList: List[Int] = List(1, 2, 3) -val someString: String = "Foo" ``` @@ -207,7 +152,6 @@ In Gleam type annotations can optionally be given when binding variables. ```gleam let some_list: List(Int) = [1, 2, 3] -let some_string: String = "Foo" ``` Gleam will check the type annotation to ensure that it matches the type of the @@ -233,14 +177,6 @@ def hello(name: String = "Joe"): String = { ``` -For methods with no arguments, you may omit the parenthesis - -```scala - -def noArgs = "Surprise." - -``` - Anonymous functions can also be defined and be bound to variables. ```scala @@ -273,10 +209,10 @@ mul(1, 2) In Scala, class methods are public by default but can be made private or protected. ```scala -object Foo { +object Math { // this is public def sum(x: Int, y: Int): Int = x + y - + // this is private private def mul(x: Int, y: Int): Int = x * y } @@ -307,7 +243,7 @@ Method parameters in Scala require type annotations. The return type is usually optional but encouraged for public methods. ```scala -object Foo { +object Math { // int return type will be statically inferred def sum(x: Int, y: Int) = x + y @@ -367,12 +303,6 @@ toCsvRow(List("a", "b", "c"), "\t") // a\tb\tc toCsvRow(List("a", "b", "c"), separator = "|") // a|b|c ``` -Using parameter names in an external library may cause your code to -break if a newer version of that library changes the name of their -parameters as it may not be apparent to library authors their parameter -names are part of the public API if they didn't expect users to -pass them by name. - #### Gleam In Gleam arguments can be given a label as well as an internal name. @@ -468,7 +398,6 @@ automatically exported. In Scala, blocks can use either curly braces or indentation. - ```scala def main() = { val x = { @@ -482,17 +411,6 @@ def main() = { } ``` -or using indentation in Scala 3 - -```scala -def main() = - val x = - someFunction(1) - 2 - val y = x * (x + 10) - y -``` - ### Gleam In Gleam curly braces, `{` and `}`, are used to group expressions. @@ -559,7 +477,7 @@ but are 0-index instead of 1 indexed when accessing by index. #### Scala ```scala -val myTuple = ("username", "password", 10) // Type is (String, String, Int) or Tuple3[String, String, Int] +val myTuple = ("username", "password", 10) val (_, pwd, _) = myTuple println(pwd) // "password" // Direct index access @@ -586,14 +504,14 @@ Scala's `List` is a linked-list with a cons operator (`::`). ```scala val list = List(2, 3, 4) -val alsoList = 2 :: 3 :: 4 :: Nil // equivalent val list2 = 1 :: list // list of 1,2,3,4 -val 1 :: secondElement :: tail = list2 // secondElement = 2 +val 1 :: secondElement :: _ = list2 // secondElement = 2 +1.0 :: list // compiles, but casts to List[AnyVal] ``` #### Gleam -Gleam has a `cons` operator that works for lists destructuring and +Gleam has a `..` prepend operator that works for lists destructuring and pattern matching. In Gleam lists are immutable so adding and removing elements from the start of a list is highly efficient. @@ -610,29 +528,27 @@ Scala's Map is an immutable hashmap; there is no literal syntax for it but there is some sugar to use `->` as a 2-tuple which can be used to define keys and values. -In Gleam, maps can have keys and values of any type, but all keys must be of +In Gleam, dicts can have keys and values of any type, but all keys must be of the same type in a given map and all values must be of the same type in a -given map. The type of key and value can differ from each other. +given dict. The type of key and value can differ from each other. -There is no map literal syntax in Gleam, and you cannot pattern match on a map. -Maps are generally not used much in Gleam, custom types are more common. +There is no dict literal syntax in Gleam, and you cannot pattern match on a Dict. +Dicts are generally not used much in Gleam, custom types are more common. #### Scala ```scala Map("key1" -> "value1", "key2" -> "value2") -Map("key1" -> "1", "key2" -> 2) // compiles, but infers an unexpected type -val myMap: Map[String, String] = Map("key1" -> "1", "key2" -> 2) // type error ``` #### Gleam ```gleam -import gleam/map +import gleam/dict -map.from_list([#("key1", "value1"), #("key2", "value2")]) -map.from_list([#("key1", "value1"), #("key2", 2)]) // Type error! +dict.from_list([#("key1", "value1"), #("key2", "value2")]) +dict.from_list([#("key1", "value1"), #("key2", 2)]) // Type error! ``` ### Numbers @@ -661,7 +577,7 @@ math operations between them. 1.5 + 10 // Compile time error ``` -You can use the gleam standard library's `int` and `float` modules to convert +You can use the gleam standard library's `gleam/int` and `gleam/float` modules to convert between floats and integers in various ways including `rounding`, `floor`, `ceiling` and many more. @@ -669,35 +585,20 @@ between floats and integers in various ways including `rounding`, `floor`, ### Case -Case is one of the most used control flow in Gleam. It can be seen as a switch -statement on steroids. It provides a terse way to match a value type to an -expression. It is also used to replace `if`/`else` statements. - -Scala and Gleam both support guards, destructuring and disjoint union matching. +Case is one of the most used control flow in Gleam, and it is very similar to the +`match` operator in Scala. Scala and Gleam both support guards, destructuring, +exhaustiveness checking and disjoint union matching. #### Scala -Scala has both `if`/`else` and `match` expressions. - ```scala -def httpErrorImpl1(status: Int) = { - if (status == 400) { - "Bad request" - } else if (status == 404) { - "Not found" - } else if (status == 418) { - "I'm a teapot" - } else { - "Internal Server Error" - } -} -def httpErrorImpl2(status: Int): String = { +def nameNumber(status: Int): String = { status match { - case 400 => "Bad request" - case 404 => "Not found" - case 418 => "I'm a teapot" - case _ => "Internal Server Error" + case 0 => "Zero" + case 1 => "One" + case 2 => "Two" + case _ => "Some other number" } } ``` @@ -724,10 +625,10 @@ number match { #### Gleam -The case operator is a top level construct in Gleam: +Gleam's syntax is a little shorter: ```gleam -case some_number { +case name_number { 0 -> "Zero" 1 -> "One" 2 -> "Two" @@ -735,52 +636,19 @@ case some_number { } ``` -As all expressions the case expression will return the matched value. +Exhaustiveness checking at compile time will make certain that you must check for +all possible values. A lazy and common way is to check of expected values and have a +catchall clause with a single underscore `_`: ```gleam -let is_status_within_4xx = status / 100 == 4 case status { 400 -> "Bad Request" 404 -> "Not Found" - _ if is_status_within_4xx -> "4xx" // This works as of now - // status if status / 100 == 4 -> "4xx" // This will work in future versions of Gleam + status if status / 100 == 4 -> "4xx" // This will work in future versions of Gleam _ -> "I'm not sure" } ``` -if/else example: - -```gleam -case is_admin { - True -> "allow access" - False -> "disallow access" -} -``` - -if/elseif/else example: - -```gleam -case True { - _ if is_admin == True -> "allow access" - _ if is_confirmed_by_mail == True -> "allow access" - _ -> "deny access" -} -``` - -Exhaustiveness checking at compile time, which is in the works, will make -certain that you must check for all possible values. A lazy and common way is -to check of expected values and have a catchall clause with a single underscore -`_`: - -```gleam -case scale { - 0 -> "none" - 1 -> "one" - 2 -> "pair" - _ -> "many" -} -``` - The case operator especially coupled with destructuring to provide native pattern matching: @@ -853,32 +721,6 @@ Error management is approached differently in Scala and Gleam. Scala uses the notion of exceptions to interrupt the current code flow and pop up the error to the caller. -An exception is raised using the keyword `throw`. - -```scala -def aFunctionThatFails() = { - throw new RuntimeException("an error") -} -``` - -The callee block will be able to capture any exception raised in the block -using a `try/except` set of blocks: - -```scala -// callee block -try { - println("this line will be executed and thus printed") - aFunctionThatFails() - println("this line will not be executed and thus not printed") -} catch { - case (e: Throwable) => println(e) -} -``` - -However, this is not considered idiomatic Scala in most cases. Many libraries -provide more functional approaches to error-handling. The standard library -also includes such a structure which can wrap code that throws exceptions -and return the success or exception as a value. ```scala import scala.util.{Try,Success,Failure} @@ -1040,14 +882,6 @@ enum IntOrFloat { ``` -or in Scala 2: - -```scala -sealed trait IntOrFloat -case class AnInt(i: Int) extends IntOrFloat -case class AFloat(f: Float) extends IntOrFloat -``` - Although for this particular example, we could just use a union type (in Scala 3) ```scala @@ -1175,7 +1009,7 @@ In Scala we can use traits or objects to group types aliases, methods, and constants into a namespace. ```scala -package foo.bar.baz // this package could be defined in any file +package one // this package could be defined in any file object MyModule { def identity[T](t: T) = t @@ -1186,7 +1020,7 @@ object MyModule { ```scala package another.packge -import foo.bar.baz.MyModule +import one.MyModule def main() = MyModule.identity(1) // 1 @@ -1205,24 +1039,24 @@ Since there is no special syntax to create a module, there can be only one module in a file and since there is no way name the module the filename always matches the module name which keeps things simple and transparent. -In `/src/foo/bar.gleam`: +In `one.gleam`: ```gleam // Creation of module function identity -// in module bar +// in module one pub fn identity(x) { x } ``` -Importing the `bar` module and calling a module function: +Importing the `one` module and calling a module function: ```gleam // In src/main.gleam -import foo/bar // if foo was in a directory called `lib` the import would be `lib/foo/bar`. +import one // if `one` was in a directory called `lib` the import would be `lib/one`. pub fn main() { - bar.identity(1) // 1 + one.identity(1) // 1 } ```