|
| 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 |
0 commit comments