forked from orion/obsidian
205 lines
5.5 KiB
Markdown
205 lines
5.5 KiB
Markdown
|
## Applying
|
||
|
Functions are applied by placing expressions after the function name, separated by whitespace, e.g.
|
||
|
|
||
|
```javascript
|
||
|
Math.pow(2, 10)
|
||
|
```
|
||
|
equivalent would be
|
||
|
```haskell
|
||
|
Number.pow 2.0 10.0
|
||
|
```
|
||
|
|
||
|
## Defining
|
||
|
Parts of a function definition are:
|
||
|
- type signature (optional)
|
||
|
- 1 or more "implementations" of the type signature
|
||
|
- name
|
||
|
- 0 or more arguments
|
||
|
- function body
|
||
|
|
||
|
### Type Signature
|
||
|
Function type signatures follow the form `<name> :: <type>`, and are always the line immediately above the function implementations, e.g.
|
||
|
|
||
|
this `add` has 2 `Int` arguments and returns an `Int`.
|
||
|
|
||
|
```haskell
|
||
|
add2 :: Int -> Int -> Int
|
||
|
-- ...
|
||
|
```
|
||
|
|
||
|
### Name
|
||
|
function names can contain any alphanumeric character, `_`, `'` but the first character must be a lowercase alpha or `_`.
|
||
|
|
||
|
### Arguments
|
||
|
Arguments are space-separated, e.g.
|
||
|
|
||
|
the first [[Int]] argument is bound to `a`, the second to `b`.
|
||
|
```haskell
|
||
|
add :: Int -> Int -> Int
|
||
|
add a b = -- ...
|
||
|
```
|
||
|
|
||
|
### Body
|
||
|
The expression returned by the function, e.g.
|
||
|
|
||
|
```haskell
|
||
|
add a b = a + b
|
||
|
```
|
||
|
|
||
|
### Multiple Implementations
|
||
|
Functions may have multiple implementations that change behavior based on the shape of the arguments.
|
||
|
|
||
|
This is _similar_ to method overloading in OOP languages, but differs in that the number of arguments and type of the arguments must be the same for all implementations.
|
||
|
|
||
|
Functions can have any number of implementations, as long as all possible inputs are covered exhaustively.
|
||
|
|
||
|
e.g.
|
||
|
|
||
|
this implementation of [[Number]] division has 2 paths:
|
||
|
- when the denominator is zero, yields `Infinity`
|
||
|
- otherwise, performs the division
|
||
|
```haskell
|
||
|
div num 0.0 = infinity
|
||
|
div num den = num / den
|
||
|
```
|
||
|
|
||
|
this is equivalent to
|
||
|
```haskell
|
||
|
div num den =
|
||
|
if den == 0.0 then
|
||
|
infinity
|
||
|
else
|
||
|
num / den
|
||
|
```
|
||
|
|
||
|
### Guards
|
||
|
Single function implementations may have guards, which allows for conditional checks on inputs (as opposed to structural pattern matching)
|
||
|
|
||
|
This pattern often takes up less space than explicit [[if then else Expressions]]
|
||
|
|
||
|
Guard patterns are placed after the function arguments and take the form `| <Boolean expr> = <body>`. There can be any number of guard patterns, as long as all cases are exhaustively covered.
|
||
|
|
||
|
e.g.
|
||
|
|
||
|
Strings can't be structurally pattern matched, so in order to ask if a string starts with a substring we need to call a function like `Data.String.Utils.startsWith`.
|
||
|
|
||
|
We could do this with `if then else`:
|
||
|
|
||
|
```haskell
|
||
|
ensureLeadingSlash :: String -> String
|
||
|
ensureLeadingSlash str =
|
||
|
if String.Util.startsWith "/" then
|
||
|
str
|
||
|
else
|
||
|
"/" <> str
|
||
|
```
|
||
|
|
||
|
Alternatively, we could implement this with guard patterns:
|
||
|
```haskell
|
||
|
ensureLeadingSlash :: String -> String
|
||
|
ensureLeadingSlash str
|
||
|
| String.Util.startsWith "/" str = str
|
||
|
| otherwise = "/" <> str
|
||
|
```
|
||
|
|
||
|
When the first pattern `String.Util.startsWith "/" str` returns `true`, the function will use `... = str`. Otherwise, it will prepend `/` to `str`.
|
||
|
|
||
|
> [!info]
|
||
|
> `otherwise` is simply an alias for `true`, specifically for better-reading fallthrough guard patterns.
|
||
|
|
||
|
## Currying
|
||
|
Functions are curried in PureScript, meaning that "a function of 2 arguments" is actually "a function of 1 argument, returning a function of 1 argument."
|
||
|
|
||
|
This allows you to call many functions point-free, and think in terms of "building up to a conclusion" rather than "i need everything at once."
|
||
|
|
||
|
e.g.
|
||
|
|
||
|
```haskell
|
||
|
add :: Int -> Int -> Int
|
||
|
-- ...
|
||
|
|
||
|
add2 :: Int -> Int
|
||
|
add2 n = add 2 n
|
||
|
```
|
||
|
|
||
|
is equivalent to
|
||
|
```haskell
|
||
|
add :: Int -> Int -> Int
|
||
|
-- ...
|
||
|
|
||
|
add2 :: Int -> Int
|
||
|
add2 = add 2
|
||
|
```
|
||
|
|
||
|
Walking through this:
|
||
|
|
||
|
`add` has type `Int -> Int -> Int`
|
||
|
|
||
|
if we give `add` a single `Int` argument, it will return a function of type `Int -> Int`. This function is the "second half" of `add`, waiting for it's second argument. Since `Int -> Int` is the type of `add2`, we can simply say `add2 = add 2`.
|
||
|
|
||
|
> [!info]
|
||
|
> as a rule, any time a function's last argument is passed as the last argument to another function, you can remove both.
|
||
|
> ```haskell
|
||
|
> f a = g b c a
|
||
|
>
|
||
|
> \a -> g b c a
|
||
|
>
|
||
|
> f a = g $ h a
|
||
|
> ```
|
||
|
> can be written as
|
||
|
> ```haskell
|
||
|
> f = g b c
|
||
|
>
|
||
|
> g b c
|
||
|
>
|
||
|
> f = g <<< h
|
||
|
> ```
|
||
|
|
||
|
## Working with Functions
|
||
|
Functions being first-class citizens means that it's important that applying, extending and combining functions must be easy.
|
||
|
|
||
|
|
||
|
### Composition
|
||
|
The most common pattern by far; piping the output of a function into the input of the next.
|
||
|
|
||
|
```haskell
|
||
|
compose :: forall a b c. (b -> c) -> (a -> b) -> (a -> c)
|
||
|
compose g f a = g (f a)
|
||
|
```
|
||
|
|
||
|
`compose` (infix as `<<<`) accepts 2 functions; `f` which is `a -> b` and `g`; `b -> c`. `compose` returns a new function `a -> c` that "glues" the 2 functions together; piping the output of `f` into `g`.
|
||
|
|
||
|
e.g.
|
||
|
|
||
|
consider a function `normalizedPathSegments :: String -> Array String`
|
||
|
|
||
|
This function would normalize a file path, removing trailing / leading slashes and resolving relative paths, then split the path by its segments.
|
||
|
|
||
|
A very good approach would be to split this function into separate single-purpose components, e.g.
|
||
|
|
||
|
- `stripLeadingSlash :: String -> String`
|
||
|
- `stripTrailingSlash :: String -> String`
|
||
|
- `splitPath :: String -> Array String`
|
||
|
- `normalizePathSegments :: Array String -> Array String`
|
||
|
|
||
|
then define `normalizedPathSegments` like so:
|
||
|
|
||
|
```haskell
|
||
|
normalizedPathSegments :: String -> Array String
|
||
|
normalizedPathSegments =
|
||
|
normalizePathSegments
|
||
|
<<< splitPath
|
||
|
<<< stripTrailingSlash
|
||
|
<<< stripLeadingSlash
|
||
|
```
|
||
|
|
||
|
|
||
|
map map map
|
||
|
(a -> b) -> (f a) -> (f b)
|
||
|
(a -> b) -> (c -> a) -> (c -> b)
|
||
|
|
||
|
((a -> b) -> (c -> a) -> (c -> b))
|
||
|
-> ((c -> b) -> (a -> b))
|
||
|
-> ((c -> b) -> (c -> a))
|
||
|
|