Skip to content

Commit 2346a5e

Browse files
committed
Gleam use post
1 parent a8f6296 commit 2346a5e

File tree

8 files changed

+254
-75
lines changed

8 files changed

+254
-75
lines changed

.astro/types.d.ts

Lines changed: 64 additions & 57 deletions
Large diffs are not rendered by default.

astro.config.mjs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import sitemap from '@astrojs/sitemap'
55
import { remarkReadingTime } from './src/utils/remarkReadingTime.ts'
66
import remarkUnwrapImages from 'remark-unwrap-images'
77
import rehypeExternalLinks from 'rehype-external-links'
8+
import remarkSmartypants from 'remark-smartypants'
89
import remarkMath from 'remark-math'
910
import rehypeMathjax from 'rehype-mathjax/chtml'
1011
import expressiveCode from 'astro-expressive-code'
@@ -29,7 +30,12 @@ export default defineConfig({
2930
icon()
3031
],
3132
markdown: {
32-
remarkPlugins: [remarkUnwrapImages, remarkReadingTime, remarkMath],
33+
remarkPlugins: [
34+
[remarkSmartypants, { dashes: 'oldschool' }],
35+
remarkUnwrapImages,
36+
remarkReadingTime,
37+
remarkMath
38+
],
3339
rehypePlugins: [
3440
[
3541
rehypeExternalLinks,

bun.lockb

459 Bytes
Binary file not shown.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,20 @@
1818
"@astrojs/tailwind": "^5.1.0",
1919
"@astrojs/vercel": "^7.7.2",
2020
"@fontsource/iosevka": "^5.0.11",
21+
"@fontsource/jetbrains-mono": "^5.0.20",
2122
"@fontsource/source-sans-pro": "^5.0.8",
2223
"@vercel/analytics": "^1.3.1",
2324
"astro": "^4.13.0",
2425
"astro-expressive-code": "^0.33.5",
2526
"astro-icon": "^1.1.0",
2627
"clsx": "^2.1.1",
2728
"mdast-util-to-string": "^4.0.0",
29+
"punycode": "^2.3.1",
2830
"reading-time": "^1.5.0",
2931
"rehype-external-links": "^3.0.0",
3032
"rehype-mathjax": "^6.0.0",
3133
"remark-math": "^6.0.0",
34+
"remark-smartypants": "^3.0.2",
3235
"remark-unwrap-images": "^4.0.0",
3336
"sharp": "^0.33.4",
3437
"tailwind-merge": "^2.4.0",

src/content/post/gleam-use.mdx

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
---
2+
title: "The 'use' Expression in Gleam"
3+
description: "How can we emulate the behavior of Python's `with` and Rust `?` in Gleam?"
4+
publishDate: 2024-08-03 00:00
5+
---
6+
7+
Everybody who knows Python has used the `with` statement, most commonly when opening
8+
a file.[^1]
9+
It's quite powerful!
10+
11+
Gleam is a radically different type of language from Python.
12+
It can be seen as a purely functional version of Rust.
13+
We know that things are rather less obvious in purely functional languages.
14+
So the question here is, how can we emulate the behavior of Python's `with` statement?
15+
Another question is (due to the similarity of Rust and Gleam), how to emulate
16+
Rust's `?` syntax in Gleam?
17+
18+
## 'use' For Emulating the 'with' Statement in Python
19+
20+
In Python, this code
21+
22+
```python title="Python"
23+
f = open("filename.txt")
24+
ret = do_something_with_file(f)
25+
f.close()
26+
```
27+
28+
can be rewritten with the `with` statement, resulting in a nice, less verbose
29+
piece of code:
30+
31+
```python title="Python"
32+
with open("filename.txt") as f:
33+
ret = do_something_with_file(f)
34+
```
35+
36+
Conceptually, the `with` statement can be thought of as a higher-order function
37+
(a function that takes other functions) with a callback function as its last parameter.
38+
The block inside the statement (`ret = ...` above) is the body of the callback function.
39+
In code, this is:
40+
41+
```python title="Python"
42+
def file_action(filename: str, callback: Callable[[File], Any]):
43+
f = open(filename)
44+
ret = callback(f)
45+
f.close()
46+
return ret
47+
48+
ret = file_action("filename.txt", do_something_with_file)
49+
```
50+
51+
Assuming the functions `open`, `close` are available and behaving similarly to the
52+
above, we can rewrite it one-to-one in Gleam.
53+
(The type `rtype` below is a generic type.)
54+
55+
```gleam title="Gleam"
56+
fn file_action(filename: String, callback: fn(File) -> rtype) -> rtype {
57+
let f: File = open(filename)
58+
let ret: rtype = callback(f)
59+
close(filename)
60+
ret
61+
}
62+
```
63+
64+
What is the Gleam equivalent to the same code, written with the `with`
65+
statement then?
66+
It is this:
67+
68+
```gleam title="Gleam"
69+
use f <- file_action("filename.txt")
70+
let ret: rtype = do_something_with_file(f)
71+
```
72+
73+
We can think of the first line `use retval <- func(args)` as the direct counterpart
74+
of `with func(args) as retval`.
75+
Note that _all_ code under `use .. <- ..` will be included in the body of `callback`.[^2]
76+
If this is an undesirable behavior, we can make the scope similar
77+
to Python's by simply doing:
78+
(Note that in Gleam, everything is an expression!)
79+
80+
```gleam title="Gleam"
81+
let ret: rtype = {
82+
use f <- file_action("filename.txt")
83+
do_something_with_file(f)
84+
}
85+
86+
// Outside of the scope
87+
do_something_else(ret)
88+
```
89+
90+
## 'use' For Emulating '?' in Rust
91+
92+
Gleam is like a functional (garbage-collected) variant of Rust---they have many
93+
similarities.
94+
One of them is this: errors in Gleam and Rust are _return values_.
95+
So, there is no `throw-try-catch` as in Python or C-inspired languages.
96+
97+
The usual way we handle errors in Rust is by returning the type
98+
`Result<return_type, error_type>`.
99+
Then, if we don't want to do error handling in a function that calls a function
100+
with the `Result` type as the return value, we can simply return that error
101+
when it occurs.
102+
In code, this is:
103+
104+
```rust title="Rust"
105+
fn do_something_and_return_string() -> Result<String, Err> {
106+
// ...
107+
}
108+
109+
fn process_the_retval() -> Result<(), Err> {
110+
match do_something_and_return_string() {
111+
Ok(ret_val) -> // do something with the string ret_val
112+
Err(err_val) -> Err(err_val) // just propagate the err
113+
}
114+
}
115+
```
116+
117+
Notice that we must do the pattern matching `match` to handle the `Result` type.
118+
Notice also that if we don't want to handle the error here, we have that line
119+
`Err(err_val) -> Err(err_val)` which is quite verbose.
120+
121+
In Rust, this can be concisely rewritten with `?`:[^3]
122+
123+
```rust title="Rust"
124+
fn process_the_retval() -> Result<(), Err> {
125+
let ret_val: String = do_something_and_return_string()?;
126+
// do something with the string ret_val
127+
}
128+
```
129+
130+
Gleam also has the `Result` type and `use` can be used to do the same
131+
thing as in Rust.
132+
133+
```gleam title="Gleam"
134+
fn process_the_retval() -> Result(Nil, Err) {
135+
case do_something_and_return_string() {
136+
Ok(ret_val) -> // do something with the string ret_val
137+
Error(err_val) -> Error(err_val)
138+
}
139+
}
140+
```
141+
142+
In Gleam, we can use `result.map`
143+
to circumvent the need for pattern matching.[^4]
144+
145+
```gleam title="Gleam"
146+
fn callback(s: String) {
147+
// do something with the string ret_val
148+
}
149+
150+
fn process_the_retval() -> Result(Nil, Err) {
151+
result.map(do_something_and_return_string(), callback)
152+
}
153+
```
154+
155+
But this is just the higher-order function pattern that we have seen before
156+
in the previous section.
157+
So, we can rewrite it more concisely with `use`:
158+
159+
```gleam title="Gleam"
160+
fn process_the_retval() -> Result(Nil, Err) {
161+
use ret_val <- result.map(do_something_and_return_string())
162+
// do something with the string ret_val
163+
}
164+
```
165+
166+
Notice the similarity as the Rust code with `?`!
167+
168+
[^1]: https://realpython.com/python-with-statement/
169+
170+
[^2]: https://gleam.run/news/v0.25-introducing-use-expressions/
171+
172+
[^3]: https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#propagating-errors
173+
174+
[^4]: https://hexdocs.pm/gleam_stdlib/gleam/result.html#map

src/layouts/BlogPost.astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const { headings } = await post.render()
2828
<article class='flex-grow break-words' data-pagefind-body>
2929
<div id='blog-hero'><BlogHero content={post} /></div>
3030
<div
31-
class='prose prose-base mt-12 max-w-[50rem] font-sans leading-6 dark:prose-invert prose-headings:font-bold prose-headings:text-foreground prose-headings:before:absolute prose-headings:before:-ms-4 prose-th:before:content-none prose-inline-code:font-iosevka prose-inline-code:text-sm prose-inline-code:font-normal'
31+
class='prose prose-base mt-12 max-w-[50rem] font-sans leading-6 dark:prose-invert prose-headings:font-bold prose-headings:text-foreground prose-headings:before:absolute prose-headings:before:-ms-4 prose-th:before:content-none prose-inline-code:font-mono prose-inline-code:text-sm prose-inline-code:font-normal'
3232
>
3333
<slot />
3434
</div>

src/site.config.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { SiteConfig } from '@/types'
22
import type { AstroExpressiveCodeOptions } from 'astro-expressive-code'
33
import '@fontsource/iosevka'
4+
import '@fontsource/jetbrains-mono'
45
import '@fontsource/source-sans-pro'
56

67
export const siteConfig: SiteConfig = {
@@ -43,7 +44,7 @@ export const menuLinks: Array<{ title: string; path: string }> = [
4344
// https://expressive-code.com/reference/configuration/
4445
export const expressiveCodeOptions: AstroExpressiveCodeOptions = {
4546
// One dark, one light theme => https://expressive-code.com/guides/themes/#available-themes
46-
themes: ['catppuccin-latte', 'catppuccin-macchiato'],
47+
themes: ['light-plus', 'catppuccin-macchiato'],
4748
themeCssSelector(theme, { styleVariants }) {
4849
if (styleVariants.length >= 2) {
4950
const baseTheme = styleVariants[0]?.theme
@@ -57,10 +58,10 @@ export const expressiveCodeOptions: AstroExpressiveCodeOptions = {
5758
useDarkModeMediaQuery: true,
5859
styleOverrides: {
5960
uiLineHeight: 'inherit',
60-
codeFontSize: '0.845rem',
61+
codeFontSize: '0.8rem',
6162
codeLineHeight: '1.2rem',
6263
borderRadius: '4px',
6364
codePaddingInline: '1rem',
64-
codeFontFamily: '"Iosevka", monospace;'
65+
codeFontFamily: '"Jetbrains Mono", monospace;'
6566
}
6667
}

tailwind.config.js

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,6 @@ const config = {
3030
}
3131
},
3232
extend: {
33-
typography: {
34-
DEFAULT: {
35-
css: {
36-
'code::before': {
37-
content: '""'
38-
},
39-
'code::after': {
40-
content: '""'
41-
}
42-
}
43-
}
44-
},
4533
colors: {
4634
border: 'hsl(var(--border) / <alpha-value>)',
4735
input: 'hsl(var(--input) / <alpha-value>)',
@@ -85,7 +73,7 @@ const config = {
8573
fontFamily: {
8674
sans: ['Source Sans Pro', fontFamily.sans],
8775
serif: ['Times', fontFamily.serif],
88-
iosevka: ['Iosevka', fontFamily.mono]
76+
mono: ['Jetbrains Mono', fontFamily.mono]
8977
}
9078
}
9179
}

0 commit comments

Comments
 (0)