This commit is contained in:
Orion Kindel 2024-09-24 17:52:08 -05:00
parent d74fc46755
commit 3ee2a9bbbd
Signed by untrusted user who does not match committer: orion
GPG Key ID: 6D4165AE4C928719
11 changed files with 356 additions and 33 deletions

View File

@ -11,8 +11,12 @@
"id": "6465d16034124b8f",
"type": "leaf",
"state": {
"type": "graph",
"state": {}
"type": "markdown",
"state": {
"file": "Language/Functions/Defining/Guard Clause.md",
"mode": "source",
"source": true
}
}
}
]
@ -81,6 +85,7 @@
"state": {
"type": "backlink",
"state": {
"file": "Language/Functions/Defining/Guard Clause.md",
"collapseAll": false,
"extraContext": false,
"sortOrder": "alphabetical",
@ -97,6 +102,7 @@
"state": {
"type": "outgoing-link",
"state": {
"file": "Language/Functions/Defining/Guard Clause.md",
"linksCollapsed": false,
"unlinkedCollapsed": true
}
@ -118,7 +124,9 @@
"type": "leaf",
"state": {
"type": "outline",
"state": {}
"state": {
"file": "Language/Functions/Defining/Guard Clause.md"
}
}
}
]
@ -142,6 +150,24 @@
},
"active": "6465d16034124b8f",
"lastOpenFiles": [
"Language/Functions/Defining/Pattern Matching.md",
"Language/Functions/Applying.md",
"Language/Functions/Composition.md",
"Language/Functions/Currying.md",
"Language/Functions.md",
"Terminology/Point-free.md",
"Language/Infix Operators/Common Operators/Applying & Composing Functions.md",
"Language/Functions/Defining.md",
"Language/Expressions/case .. of.md",
"Language/Expressions/Lambda Functions.md",
"Language/Functions/Defining/Guard Clause.md",
"Language/Expressions/let .. in ...md",
"Language/Expressions/if .. then .. else ...md",
"Language/Expressions/do notation.md",
"Classes/Functor/Apply.md",
"Classes/Functor/Applicative.md",
"Classes/Bind/Monad.md",
"Classes/Bind/Bind.md",
"Classes/Alternative/Alt.md",
"Classes/Alternative/Plus.md",
"Monads/Transformers/MaybeT.md",
@ -151,31 +177,13 @@
"Monads/Transformers/StateT.md",
"Monads/Transformers",
"Classes/Alternative/Alternative.md",
"Classes/Functor/Applicative.md",
"Classes/Functor/Apply.md",
"Classes/Functor/Functor.md",
"Classes/Collapsing",
"Classes/Bind",
"Classes/Functor",
"Classes/Alternative",
"Language/Infix Operators/Common Operators/Data.md",
"Classes/Traversable.md",
"Data/Collections/NonEmptyArray.md",
"Data/Collections/NonEmptyList.md",
"Data/String.md",
"Data/Collections.md",
"Data/Collections/HashMap.md",
"Data/Product",
"Data/Sum",
"Data/Collections/HashSet.md",
"Data/Collections/Set.md",
"Data/Collections/List.md",
"Data/Collections/Map.md",
"Data/Collections",
"Language/Row Types.md",
"Language/Row Types/Variant.md",
"Language/Row Types/Record.md",
"Data/Product/Tuple.md",
"Language/Row Types",
"Classes/Math",
"Untitled 1.canvas",

View File

@ -0,0 +1,23 @@
lambda functions are anonymous functions (as opposed to [[Functions|top-level functions]]) that can be written anywhere:
```haskell
\ <params> -> <body>
```
```haskell
prependHello a = "hello, " <> a
appendName = \a -> a <> "henry!"
str = appendName $ prependHello ""
-- hello, henry!
```
their parameters can destructure their arguments:
```haskell
let
getFoo = \{foo} -> foo
in
getFoo {foo: "hello"}
-- "hello"
```

View File

@ -1,4 +1,4 @@
Pattern matching
Structural pattern matching expression
```haskell
case <expr> of
@ -18,7 +18,7 @@ where `arm` is:
- array literals, containing patterns
- wildcard `_`
### Guard Clauses
## Guard Clauses
Case arms can have [[Functions/Defining/Guard Clause|guard clauses]] like [[Functions|function]] definitions:
```haskell
@ -28,7 +28,22 @@ case _ of
| otherwise -> Nothing
```
### Examples
## Anonymous expression
Underscore can be placed in the expression position to write an [[Lambda Functions|anonymous function]] that immediately case matches on its argument:
```haskell
case _ of
Foo -> true
Bar -> false
-- equivalent to
\a -> case a of
Foo -> true
Bar -> false
```
## Examples
Match on `Int` when zero
```haskell

View File

@ -0,0 +1,209 @@
Do notation is sugar for [[Bind|bind]] `>>=`, allowing imperative blocks that use bind as the driver of the "and-then" behavior for the specific type being returned.
```haskell
main :: Effect Unit
main = do
log "hello, world!"
log "foo bar baz"
-- logs:
-- hello, world!
-- foo bar baz
```
Do blocks are opened with the `do` keyword and support only a few operations:
- [[#bind|"await" with <-]]
- [[#let-bindings|define local variables]]
- [[#eval and ignore|evaluate an expression but discard its output]]
- [[#return|return an expression]]
statements in do blocks are evaluated line-by-line top-to-bottom, just like any other imperative language.
## bind
The left-arrow `<-` in `do` blocks is analogous to the `await` keyword in JS, C# & Java, and the question mark operator in Rust.
```haskell
intPositive :: Int -> Maybe Int
intPositive n
| n > 0 = Just n
| otherwise = Nothing
f :: String -> Maybe Int
f s = do
-- if `s` can't be parsed, returns Nothing early
num <- Int.fromString s
-- if `num` is <= 0, returns Nothing early
pos <- intPositive num
Just pos
```
This desugars to:
```haskell
f s =
Int.fromString s
>>= ( \num ->
intPositive num
>>= (\pos -> Just pos)
)
```
## let-bindings
Related to [[let .. in ..]], You can define new local variables in the middle of do blocks that can depend on previous bindings:
```haskell
do
a <- Just 1
let
b = a + 1
c = b + 1
Just c
-- evaluates to (Just 3)
```
> [!attention]- Unexpected or mismatched indentation
> A common gotcha with let-bindings in do blocks is only indenting once after trying to insert a line break after the equals:
> ```haskell
> do
> a <- Just 1
> let b =
> a + 1
> Just b
> ```
>
> ```text
> [ERROR 1/1 ErrorParsingModule] ...
>
> 4 a + 1
>
>
> Unable to parse module:
> Unexpected or mismatched indentation
> ```
>
> This happens because oneline let bindings
> ```haskell
> do
> let a = b
> ..
> ```
> are identical to
> ```haskell
> do
> let
> a = b
> ..
> ```
> So when you break after the equals and only indent once:
> ```haskell
> do
> let a =
> b
> ..
> ```
> what you're actually writing is
> ```haskell
> do
> let
> a =
> b
> ..
> ```
## eval and ignore
When an expression returns `m Unit`, you don't have to write an arrow:
```haskell
guard :: Boolean -> Maybe Unit
guard true = Just unit
guard false = Nothing
do
n <- Int.fromString "123"
-- when `n > 0`, this resolves to `Just unit` and
-- the execution continues.
-- If this is Nothing, the block short-circuits
-- and returns Nothing.
guard (n > 0)
Just n
```
If you want to discard a value other than unit, you need to use the discard syntax `_ <- x`
```haskell
main :: Effect Unit
main = do
fooTxt <- File.createWriteStream "foo.txt"
-- Stream.writeString returns a Boolean
-- indicating whether the stream needs us
-- to wait for the `drain` event before writing
-- more, but we don't care here.
_ <- Stream.writeString UTF8 "foo" fooTxt
pure unit
```
## return
the last statement in a do block is the expression the do block will return, and can't be a let-binding or bind statement:
```haskell
-- block immediately resolves with `Just ""`
do
Just ""
```
```haskell
-- block resolves with `Just foo`
do
foo <- getFoo
Just foo
```
```haskell
-- block resolves with `Just "foo"`
do
do
do
do
Just "foo"
```
## Examples
`do` in [[Maybe]] allows us to return early with empty values.
In this example, `getApiUrlPort` will return Nothing if:
- environment variable `API_URL` is unset
- `API_URL` is not a valid URL
- `API_URL` is a valid URL, but the port isn't set
```haskell
lookupEnv :: String -> Maybe String
urlFromString :: String -> Maybe URL
urlPort :: URL -> Maybe Int
getApiUrlPort = do
apiUrlString <- lookupEnv "API_URL"
apiUrl <- urlFromString apiUrlString
urlPort apiUrl
```
`do` in [[Either]] allows us to return early with errors.
```haskell
lookupEnv :: String -> Either String String
urlFromString :: String -> Either String URL
urlPort :: URL -> Maybe Int
getApiUrlPort = do
apiUrlString <- lookupEnv "API_URL"
apiUrl <- urlFromString apiUrlString
liftMaybe ("no port set in URL " <> apiUrlString)
$ urlPort apiUrl
```
> [!tip]- `liftMaybe`
> liftMaybe allows us to turn `Maybe a` into any `m a` as long as `m` supports [[MonadThrow|throwing]] errors.
>
> In the above example, if `urlPort` is `Nothing`, it will return `Left "no port set in ..."`.
>
> ```haskell
> liftMaybe :: forall m e a. MonadThrow e m => e -> Maybe a => m a
> ```

View File

@ -0,0 +1,5 @@
Conditional expression, similar to ternary expressions in imperative languages:
```haskell
if a == b then "foo" else "bar"
```

View File

@ -0,0 +1,33 @@
`let .. in ..` is an expression with some scoped variables, separated by newlines:
```haskell
let
a = 1
b = 2
in
a + b
```
> [!tip]
> there is a second local let-binding syntax, `where`:
> ```haskell
> a + b
> where
> a = 1
> b = 2
> ```
### Examples
Recursive Fibonacci generator, using a helper function `go`
```haskell
fib n =
let
go ns a b =
if Array.length ns == n then
ns
else
go (ns <> [a]) b (a + b)
in
go [] 1 1
```

View File

@ -1 +1,3 @@
The backbone of functional programming. Functions can be constants, transform [[Data Structures|data]], perform [[Effect|effects]] and much more.
The backbone of functional programming. Functions can be constants, transform [[Data Structures|data]], perform [[Effect|effects]] and much more.
Functions can be [[Defining|defined]] as top-level [[Modules|module]] items, [[Lambda Functions|lambda functions]], [[Infix Operators|infix operators]] or created by [[Currying|partially applying]] functions.

View File

@ -1,6 +1,6 @@
[[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."
This allows you to call many functions [[Point-free|point-free]], and think in terms of "building up to a conclusion" rather than "i need everything at once."
e.g.
@ -27,7 +27,7 @@ Walking through this:
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]
> [!tip]
> 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

View File

@ -1,14 +1,14 @@
Single [[Functions|Functions]] [[Defining/Pattern Matching|implementations]] may have 1 or more guard clauses.
> [!info]
> [!tip]
> [[case .. of]] expressions can also use guard clauses.
These allow functions to switch on boolean expressions based on the inputs, and can often take up less space than explicit [[Expressions/if .. then .. else ..]] expressions.
These allow functions to switch on boolean expressions based on the inputs, and can often take 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.
### Example
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`.
[[String]]s can't be structurally pattern matched aside from the entire string. 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`:
@ -31,7 +31,7 @@ ensureLeadingSlash str
When the first pattern `String.Util.startsWith "/" str` returns `true`, the function will use `... = str`. Otherwise, it will prepend `/` to `str`.
> [!tip]
> [!tip] `otherwise`
> `otherwise` is simply an alias for `true`, specifically for better-reading fallthrough guard patterns.
Much less often, but occasionally useful is the ability to structurally pattern match in a guard clause:

View File

@ -1,7 +1,17 @@
[[Functions]] may have multiple implementations that change behavior based on the shape of the arguments.
> [!note]
> You can always replace this with a [[case .. of|case expression]].
> [!tip]- [[case .. of]]
> Pattern matching in function definitions can always be rewritten as [[case .. of]].
> ```haskell
> isA "a" = true
> isA _ = false
> ```
> can be rewritten as
> ```haskell
> isA a = case a of
> "a" -> true
> _ -> false
> ```
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.

View File

@ -0,0 +1,18 @@
Written without explicit arguments and application, e.g.
```haskell
foo a b = bar a b
```
can be written point-free as
```haskell
foo = bar
```
Rewriting
```haskell
\a -> g (f a)
```
as
```haskell
g << f
```
is also referred to as point-free.