Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basics rough draft #25

Merged
merged 9 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions public/documentation/basics/conditional-expressions.fsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
let number = 20

let fizzBuzz =
if number % 15 = 0 then "FizzBuzz"
elif number % 5 = 0 then "Buzz"
elif number % 3 = 0 then "Fizz"
else string number

printfn "%s" fizzBuzz
21 changes: 21 additions & 0 deletions public/documentation/basics/conditional-expressions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Conditional Expressions

Conditional expressions are a control flow mechanism to evaluate one of many expressions based on boolean values. If a boolean expressions evaluates to `true`, the associated expression will be evaluated.

```fsharp
let age = 20
let ageDescription = if age >= 18 then "Adult" else "Juvenile"
printfn "%s" ageDescription
```

The above conditional expression will evaluate to `"Adult"` if `age >= 18`, otherwise it will evaluate to `"Juvenile"`. Conditional expressions can include multiple conditions by including `elif` branches, which is an `else` branch with `if` condition.

```fsharp
let number = 20

let fizzBuzz =
if number % 15 = 0 then "FizzBuzz"
elif number % 5 = 0 then "Buzz"
elif number % 3 = 0 then "Fizz"
else string number
```
5 changes: 5 additions & 0 deletions public/documentation/basics/expressions.fsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
let ten =
let five = 5
five + five

let twenty = ten + ten
57 changes: 57 additions & 0 deletions public/documentation/basics/expressions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Expressions

The primary piece of F# syntax is an expression. An expression is simply, a block of code that when evaluated, produces a value.

_Let bindings_ allow you to bind the result of an expression to a name.

```fsharp
let number = 5
```

Everything on the right-hand side of the equal sign is an expression. The last expression within an expression block is what produces a result for the expression as a whole.

```fsharp
let ten =
let five = 5
five + five
```

As you may notice, the `ten` binding doesn't include a type annotation. This is because the F# compiler can infer the types of values from their usage. In this case, `ten` is inferred as an `int` although it can easily be several other numerical types. If the compiler doesn't have enough information to infer your desired type, you can add a type annotation to override it.

```fsharp
let ten: float = 10
```

By default, let bindings are immutable. Immutability is highly preferred as it ensures that values won't be changed unexpectedly and results in code that's easier to reason about.

In F#, code is evaluated from top to bottom which has some interesting side effects. You can only reference bindings, functions, or types that are defined above the current definition.

```fsharp
ten + ten
let ten = 10
// `ten` is defined below its usage which results in a compiler error.
```

The advantage of top-down evaluation is that the flow of your application's code is easier to reason about. Code is read and evaluated from top to bottom sequentially, from a higher to a lower level, from core components to specific implementation details. We can clearly understand and reason about our dependent modules, types, functions and their dependencies.

This top-down evaluation also applies to the ordering of files within a project. You can only use functions, types, and modules defined in other files if the file is ordered above the current definition.

```
1. Logic.fs
2. Program.fs
```

Here, any code in `Program.fs` can access modules, types, functions, and bindings in `Logic.fs`, but not the other way around. This is because `Logic.fs` is ordered above `Program.fs`.

F# relies on the level of indentation to determine the beginning and end of an expression. You may be familiar with this if you've programmed in languages with syntatic indentation before. When writing an expression block, the level of indentation must be consistent for each expression within that block.

```fsharp
let ten =
let five = 5 // <---
five + five // <---
// this block has consistent indentation levels.

let twenty = ten + ten
```

Here, the compiler can distinguish between the beginning and the end of the expression. Both of these lines are indented consistently with 4 spaces and are terminated by the subsequent non-indented expression.
1 change: 1 addition & 0 deletions public/documentation/basics/hello-world.fsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
printfn "Hello, World!"
7 changes: 7 additions & 0 deletions public/documentation/basics/hello-world.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Hello, World!

Here is a program that prints out the text `"Hello, World!"`.

In a normal F# program, this would be executed by using the command `dotnet run` on the command line.

Try changing the text being printed to `"Hello!"` and click the `Run` button at the top right to see what happens.
17 changes: 17 additions & 0 deletions public/documentation/basics/operators.fsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
let ten = 5 + 5
printfn "5 + 5 = %d" ten

let four = 8 - 4
printfn "8 - 4 = %d" four

let five = 10 / 2
printfn "10 / 2 = %d" five

let twenty = 10 * 2
printfn "10 * 2 = %d" twenty

let remainder = 10 % 2
printfn "10 %% 2 = %d" remainder

let squared = 10.0 ** 2.0
printfn "10.0 ** 2.0 = %f" squared
33 changes: 33 additions & 0 deletions public/documentation/basics/operators.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Operators

Operators are special functions that can be applied to several types. Unlike other functions, operators can be infixed (placed between two parameters).

The arithmetic operators include: `+`, `-`, `/`, `*`, `%`, `**`.

```fsharp
let ten = 5 + 5 // add two numbers.
let four = 8 - 4 // subtract two numbers.
let five = 10 / 2 // divide two numbers.
let twenty = 10 * 2 // multiply two numbers.
let remainder = 10 % 2 // remainder after division.
let squared = 10.0 ** 2.0 // 10 to the power of 2
```

The comparison operators include: `=`, `<>`, `>`, `<`, `>=`, `<=`. These operators compare two values and return a boolean result: `true` or `false`.

```fsharp
let equals = 5 = 5 // is 5 equal to 5?
let notEquals = 5 <> 5 // is 5 not equal to 5?
let greaterThan = 5 > 5 // is 5 greater than 5?
let lessThan = 5 < 5 // is 5 less than 5?
let greaterThanOrEqualTo = 5 >= 5 // is 5 greater than or equal to 5
let lessThanOrEqualTo = 5 <= 5 // is 5 less than or equal to 5
```

Although these operators are used with numbers here, some of them apply to multiple types, like `+`, `=`, and `<>`.

```fsharp
let helloWorld = "Hello, " + "World!"
let areStringsEqual = "Hello" = "Hello"
let areStringsNotEqual = "Hello" <> "Hello, World!"
```
10 changes: 10 additions & 0 deletions public/documentation/basics/patterns-and-match-expressions.fsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
let number = 15

let fizzBuzz =
match number with
| number when number % 15 = 0 -> "FizzBuzz"
| number when number % 5 = 0 -> "Buzz"
| number when number % 3 = 0 -> "Fizz"
| number -> string number

printfn "%s" fizzBuzz
62 changes: 62 additions & 0 deletions public/documentation/basics/patterns-and-match-expressions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Patterns and Match Expressions

Pattern matching allows you to match a value against patterns, which act as rules for their transformation. These patterns can bind values to names and deconstruct or decompose values into their constituent parts.

To demonstrate pattern matching, let's get started with a pattern you're already familiar with: the _variable_ pattern. This pattern allows you to bind a value to a name like so: `let five = 5`. That's right, you've been using the variable pattern the whole time! Just like how everything on the right-hand side of the equals sign in a binding is an expression, the left-hand side is always a pattern.

```fsharp
let <pattern> = <expression>
```

This is limited as the pattern needs to be exhaustive. Exhaustivity means that every possible value is accounted for by the given pattern. Because the variable pattern will bind any value to a name, it will always match against a value and is therefore exhaustive. However, not all patterns are exhaustive and you may want to attempt to match a value against a set of patterns. You can use match expressions to do this. You can declare a match expression like so:

```fsharp
let number = 10
let result =
match number with
| 10 -> "The number is Ten"
| number -> $"The number is not ten, but instead: {number}"
```

Here you can see two patterns in action: the _constant_ and _variable_ patterns. The _constant_ pattern will match a value against a constant value like `10` or `"Hello, World!"`. This match expression is exhaustive as the last branch utilizes the _variable_ pattern which will always match against the value.

When you don't care about the value but still require exhaustivity, you can use the _wildcard_ pattern to discard it.

```fsharp
let number = 10
let result =
match number with
| 10 -> "The number is Ten"
| _ -> $"The value was discarded"
```

A branch in a match expression can also include a conditional expression. This is often used in conjunction with the _variable_ pattern to check if the bound value passes a conditional check.

```fsharp
let number = 10
let result =
match number with
| number when number % 2 = 0 -> $"{number} is even"
| number -> $"{number} is odd"
```

Two patterns can lead to the same expression being evaluated using the _OR_ pattern.

```fsharp
let number = 10
let result =
match number with
| pattern1
| pattern2 -> ""
| _ -> ""
```

You can require that a value matches against two or more patterns using the _AND_ pattern.

```fsharp
let number = 10
let result =
match number with
| pattern1 & pattern2 -> ""
| _ -> ""
```
6 changes: 6 additions & 0 deletions public/documentation/basics/primitive-types.fsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
let int = 10
let float = 10.0
let char = 'a'
let string = "Hello, World!"
let boolean = true
let unit = ()
22 changes: 22 additions & 0 deletions public/documentation/basics/primitive-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Primitive Types

The fundamental types used in most F# programs are `int`, `float`, `bool`, `char`, `string`, and `unit`.
These types are called primitives, as they are basic data types that contain simple values and are the foundation for other, more complex types.

What do these primitive types represent?

- `int` represents a 32-bit numerical value, that is, a number without a decimal point.
- `float` represents a 64-bit double-precision floating-point numerical value, that is, a number _with_ a decimal point.
- `char` represents a Unicode character value, like an individual letter or emoji.
- `string` represents a sequence of `char` values, that is, a piece of text.
- `bool` represents a `true` or `false` value, often used in conditional logic.
- `unit`, when passed to a function, means that function has no arguments. When returned from a function, it indicates that the function has no useful return value. Often used when a function e.g. prints to the screen and does nothing else.

```fsharp
10 // int
10.0 // float
'a' // char
"Hello, World!" // string
true // bool
() // unit
```
3 changes: 3 additions & 0 deletions public/documentation/basics/string-formatting.fsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
let name = "John Doe"
let greeting = $"Hello, {name}!"
printfn "%s" greeting
37 changes: 37 additions & 0 deletions public/documentation/basics/string-formatting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# String Formatting

String formatting is the process of integrating additional values into string literals.

```fsharp
let name = "John Doe"
sprintf "Your name is %s" name // "Your name is John Doe"
```

Here, we specify that the string format contains a single string value, indicated by the `%s` format specifier.

Other common format specifiers include:
`%b` for boolean values.
`%d` for integer values.
`%f` for floating point values.
`%O` which uses the values string representation (calls `value.ToString()`).
`%A` which uses a structured plain-text representation.

The `sprintf`, `printf`, and `printfn` all take format specifiers like the ones above. The `sprintf` function will create a string value from a template while the `printf` and `printfn` functions will print values to the standard output, the latter adding a newline at the end.

```fsharp
printfn "Hello, %s!" name
```

Another method is to use interpolated strings which allow you to bake the values directly into a string literal.

```fsharp
let name = "John Doe"
$"Your name is {name}" // "Your name is John Doe"
```

These interpolated strings can also be type checked by providing a format before the template. This will result in a compiler error if the type of the value doesn't match the format specifier.

```fsharp
$"Your name is %s{name}" // this works.
$"Your name is %b{name}" // compiler error. %b = boolean
```
48 changes: 48 additions & 0 deletions public/documentation/table-of-contents.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,54 @@
{
"root": "cover.md",
"categories": [
{
"title": "Basics",
"route_segment": "basics",
"pages": [
{
"title": "Hello, World!",
"route_segment": "hello-world",
"fsharp_file": "basics/hello-world.fsx",
"markdown_file": "basics/hello-world.md"
},
{
"title": "Expressions",
"route_segment": "expressions",
"fsharp_file": "basics/expressions.fsx",
"markdown_file": "basics/expressions.md"
},
{
"title": "Primitive Types",
"route_segment": "primitive-types",
"fsharp_file": "basics/primitive-types.fsx",
"markdown_file": "basics/primitive-types.md"
},
{
"title": "String Formatting",
"route_segment": "string-formatting",
"fsharp_file": "basics/string-formatting.fsx",
"markdown_file": "basics/string-formatting.md"
},
{
"title": "Operators",
"route_segment": "operators",
"fsharp_file": "basics/operators.fsx",
"markdown_file": "basics/operators.md"
},
{
"title": "Conditional Expressions",
"route_segment": "conditional-expressions",
"fsharp_file": "basics/conditional-expressions.fsx",
"markdown_file": "basics/conditional-expressions.md"
},
{
"title": "Patterns and Match Expressions",
"route_segment": "pattern-matching",
"fsharp_file": "basics/patterns-and-match-expressions.fsx",
"markdown_file": "basics/patterns-and-match-expressions.md"
}
]
},
{
"title": "Functions",
"route_segment": "functions",
Expand Down
Loading