اولین روز که با Haskell آشنا شدم، یکی از افرادی که برای مدت زیادی الگوی خودم قرار داده بودمش این دو خط بالا رو کشید که نمایانگر learning curve که قراره انتظارش رو بکشم بود. من همون روز میدونستم که قراره چقدر از عمرم رو پای این زبان بگذارم.
واقعا شیرین ترین زبانی هست که باهاش کد زدم.
به احترام ایشون من از دست خط اشون اسکرین شات گرفتم و ترجیح دادم که اینجا توی این کانال بمونه.
حالا که دارم صحبت میکنم دوتا مصاحبه Haskell موفق داشتم و یک پیشنهاد کار.
خیلی خوشحالم ازین بابت که ایشون من رو توی این مسیر قرار دادن. علیرغم ارتباط کمی که با ایشون دارم همیشه در یاد من خواهند موند.
واقعا شیرین ترین زبانی هست که باهاش کد زدم.
به احترام ایشون من از دست خط اشون اسکرین شات گرفتم و ترجیح دادم که اینجا توی این کانال بمونه.
حالا که دارم صحبت میکنم دوتا مصاحبه Haskell موفق داشتم و یک پیشنهاد کار.
خیلی خوشحالم ازین بابت که ایشون من رو توی این مسیر قرار دادن. علیرغم ارتباط کمی که با ایشون دارم همیشه در یاد من خواهند موند.
2. Starting Out
1.
2.
3. To get an element out of a list by index, use
4. When comparing lists, they are compared in
5. Some exceptions cannot be caught on compile time. e.g.
6. Use
7.
8. List comprehension are derived from set comprehensions in mathematics.
9. Tuples care about other tuples.
10. Tuple are heterogeneous.
1.
: cons operator2.
[1,2,3] is actually just syntactic sugar for 1:2:3:[]3. To get an element out of a list by index, use
!!.4. When comparing lists, they are compared in
lexicographical order. (نظم واژگانی)5. Some exceptions cannot be caught on compile time. e.g.
head []6. Use
null [] instead of [] == [].7.
Ranges are a way of making lists that are arithmetic sequences of elements that can be enumerated.8. List comprehension are derived from set comprehensions in mathematics.
let foo = [OUTPUT FUNCTION | INPUT(s), PREDICATE(s)]9. Tuples care about other tuples.
10. Tuple are heterogeneous.
3. Types and Typeclasess
1. Type inference: If we write a number, we don't have to tell Haskell it's a number. Haskell can
2. Given
3. Type variable: means that
4. Functions that have type variables are called polymorphic functions.
5. Type Class: A typeclass is a sort of interface that defines some behavior. If a type is a part of a typeclass, that means that it supports and implements the behavior the typeclass describes.
6. Class constraint, everything before
Notes
- Get better understanding of
1. Type inference: If we write a number, we don't have to tell Haskell it's a number. Haskell can
infer the type itself.2. Given
:t foo, :: is read as has type of.3. Type variable: means that
a can be of any type. (Generics).4. Functions that have type variables are called polymorphic functions.
5. Type Class: A typeclass is a sort of interface that defines some behavior. If a type is a part of a typeclass, that means that it supports and implements the behavior the typeclass describes.
6. Class constraint, everything before
=> is called class constraints.Notes
- Get better understanding of
type vs typeclass. manually define it!4. Syntax in functions
1. Order is important when specifying patterns and it's always best to specify the most specific ones first and then the more general ones later.
2. When making patterns, we should always include a catch-all pattern so that our program doesn't crash if we get some unexpected input.
3.
4. Guards: A guard is basically a boolean expression which tests some property of value is true or false.
5. Guards can accept multiple parameters.
6. In order to prevent code duplication you can use where to bind some values to your guards.
7. Where can be splitted into multiple lines.
8. Where allow pattern matching.
9. Where can be used to nicely in conjunction with list comprehensions as output function.
10. Let general form is:
11. General form for case expression:
- Where binding: Where bindings are a syntactic construct that let you bind to variables at the end of a function and the whole function can see them, including all the guards.
- Let bindings: Let bindings let you bind to variables anywhere and are expressions themselves, but are very local, so they don't span across guards.
- Where vs. Let: The difference is that let bindings are expressions themselves. where bindings are just syntactic constructs
Note
1. What does it mean when we say let bindings are expressions?
2. What does it mean when we say where bindings are syntactic constructs?
1. Order is important when specifying patterns and it's always best to specify the most specific ones first and then the more general ones later.
2. When making patterns, we should always include a catch-all pattern so that our program doesn't crash if we get some unexpected input.
3.
as patterns: are a handy way of breaking something up according to a pattern and binding it to names whilst still keeping a reference to the whole thing. capital all@(x:xs) = "The first letter of " ++ all ++ " is " ++ [x]4. Guards: A guard is basically a boolean expression which tests some property of value is true or false.
5. Guards can accept multiple parameters.
6. In order to prevent code duplication you can use where to bind some values to your guards.
7. Where can be splitted into multiple lines.
8. Where allow pattern matching.
9. Where can be used to nicely in conjunction with list comprehensions as output function.
10. Let general form is:
let <bindings> in <expression>11. General form for case expression:
case expression of pattern -> result
pattern -> result
pattern -> result
- Where binding: Where bindings are a syntactic construct that let you bind to variables at the end of a function and the whole function can see them, including all the guards.
- Let bindings: Let bindings let you bind to variables anywhere and are expressions themselves, but are very local, so they don't span across guards.
- Where vs. Let: The difference is that let bindings are expressions themselves. where bindings are just syntactic constructs
Note
1. What does it mean when we say let bindings are expressions?
2. What does it mean when we say where bindings are syntactic constructs?
Rust Generics
- concrete type vs. genetic type
- type parameter
- generic type parameter
- Generic functions read as: the function largest is generic over some type T.
- restricting the types valid for T
- how to specify constraints on generic types?
- How to implement a method only for
- Is there any performance downside upon using generics?
#Rust
- concrete type vs. genetic type
- type parameter
- generic type parameter
- Generic functions read as: the function largest is generic over some type T.
- restricting the types valid for T
- how to specify constraints on generic types?
- How to implement a method only for
f32 instances? impl Point<f32> vs impl Point<T>.- Is there any performance downside upon using generics?
monomorphization#Rust
# Rust module system
What is a module?
A module is a collection of items: functions, structs, traits, impl blocks, and even other modules.
Visibility
By default, the items in a module have private visibility, but this can be overridden with the
Only the public items of a module can be accessed from outside the module scope.
What is a module?
A module is a collection of items: functions, structs, traits, impl blocks, and even other modules.
Visibility
By default, the items in a module have private visibility, but this can be overridden with the
pub modifier.Only the public items of a module can be accessed from outside the module scope.
Box<T>
The most straightforward smart pointer is a box. Boxes allow you to store data on the heap rather than the stack. What remains on the stack is the pointer to the heap data.
Cases you would use Box smart pointer:
- When you have a type whose size can’t be known at compile time and you want to use a value of that type in a context that requires an exact size
- When you have a large amount of data and you want to transfer ownership but ensure the data won’t be copied when you do so
- When you want to own a value and you care only that it’s a type that implements a particular trait rather than being of a specific type
#Rust #smartpointer
The most straightforward smart pointer is a box. Boxes allow you to store data on the heap rather than the stack. What remains on the stack is the pointer to the heap data.
Cases you would use Box smart pointer:
- When you have a type whose size can’t be known at compile time and you want to use a value of that type in a context that requires an exact size
- When you have a large amount of data and you want to transfer ownership but ensure the data won’t be copied when you do so
- When you want to own a value and you care only that it’s a type that implements a particular trait rather than being of a specific type
fn main() {
let b = Box::new(5);
println!("b = {b}");
}use crate::List::{Cons, Nil};
fn main() {
let list = Cons(1, Cons(2, Cons(3, Nil)));
}#Rust #smartpointer
Computing the Size of a Type
Non-Recursive
Given
To determine how much space to allocate for a
Recursive
Contrast this with what happens when Rust tries to determine how much space a recursive type like the
Because Rust can’t figure out how much space to allocate for recursively defined types, the compiler gives an error with this helpful suggestion:
# Rust
Non-Recursive
Given
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}To determine how much space to allocate for a
Message `value, Rust goes through each of the variants to see which variant needs the most space. Rust sees that `Message::Quit `doesn’t need any space, `Message::Move needs enough space to store two i32 values, and so forth. Because only one variant will be used, the most space a Message value will need is the space it would take to store the largest of its variants.Recursive
Contrast this with what happens when Rust tries to determine how much space a recursive type like the
List enum needs. The compiler starts by looking at the Cons variant, which holds a value of type i32 and a value of type List. Therefore, Cons needs an amount of space equal to the size of an i32 plus the size of a List. To figure out how much memory the List type needs, the compiler looks at the variants, starting with the Cons variant. The Cons variant holds a value of type i32 and a value of type List, and this process continues infinitelyBecause Rust can’t figure out how much space to allocate for recursively defined types, the compiler gives an error with this helpful suggestion:
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle
|
2 | Cons(i32, Box<List>),
| ++++ +
enum List {
Cons(i32, Box<List>),
Nil,
}
use crate::List::{Cons, Nil};
fn main() {
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
}# Rust
On one hand, I have always had this tendency to don't skip leg days!
On the other hand, the whole point of life is how you manage trade-offs! To me, writing a code that I don't fully understand, is sort of a leg day; hate to skip it. However, an engineer, at its most shallow trait layers, must be capable of examining the cost he is gonna face. Everything is cost though.
And now I've got this urge to move on to web3 integration in Rust to handle a project that is so close to its deadline. I have to put off the above bookmark 🤦🏻♂️
On the other hand, the whole point of life is how you manage trade-offs! To me, writing a code that I don't fully understand, is sort of a leg day; hate to skip it. However, an engineer, at its most shallow trait layers, must be capable of examining the cost he is gonna face. Everything is cost though.
And now I've got this urge to move on to web3 integration in Rust to handle a project that is so close to its deadline. I have to put off the above bookmark 🤦🏻♂️
It's Sep 22 23:36 and I have witnessed the most weird debugs of my life.
OT was hammering the keyboard for almost 5 hours which we were surprised by. It's a funny and ridiculous bug in the Jalali Calendar of a famous CRM.
Apart from the actual bug, the funny part is, that this bug would never have occurred if OT had not been testing it today cause 22 Sep was the edge case to produce this bug, let's just imagine, for a sec, what would it take for a customer of this CRM to realize this bug? let alone the report? Who would believe such a bug would exist out of nowhere?
Moral story: some bugs live for a day, seize the day and fix them 😂
OT was hammering the keyboard for almost 5 hours which we were surprised by. It's a funny and ridiculous bug in the Jalali Calendar of a famous CRM.
Apart from the actual bug, the funny part is, that this bug would never have occurred if OT had not been testing it today cause 22 Sep was the edge case to produce this bug, let's just imagine, for a sec, what would it take for a customer of this CRM to realize this bug? let alone the report? Who would believe such a bug would exist out of nowhere?
Moral story: some bugs live for a day, seize the day and fix them 😂
https://github.com/pycub/thorax
I just finished the 0.1.0 version of Thorax.
Thorax is a tiny tool in the large system of a healthcare project. HL7 information needs to be encrypted/decrypted and here is the Thorax.
Tomorrow is a great day in which I'm going to develop this sunshine further.
(Writing test is a must!)
I just finished the 0.1.0 version of Thorax.
Thorax is a tiny tool in the large system of a healthcare project. HL7 information needs to be encrypted/decrypted and here is the Thorax.
Tomorrow is a great day in which I'm going to develop this sunshine further.
(Writing test is a must!)
GitHub
GitHub - pycub/thorax: A microservice for asymmetric cryptography using RSA
A microservice for asymmetric cryptography using RSA - pycub/thorax
A development container allows you to use a container as a full-featured development environment. It can be used to run an application, to separate tools, libraries, or runtimes needed for working with a codebase, and to aid in continuous integration and testing.
https://containers.dev
https://containers.dev
Cargo Watch watches over your project's source for changes, and runs Cargo/Shell commands when they occur.
- run
- run
https://crates.io/crates/cargo-watch
- run
cargo test after each change:cargo watch -x test
- run
cargo run after each change:cargo watch -x run
OPTIONS:
-x Cargo command(s) [default: check]
-s Shell command(s)
https://crates.io/crates/cargo-watch
Let’s talk a little bit about pytest and Django.
To me the why behind using pytest is:
- empowering fixture mechanism
- assertion introspection
- parameterized tests
- rich test discovery
If you don’t know how exactly these items are getting to work you better take a close look at pytest’s source code. It’s cool and awesome to make you a better Python code reviewer.
Great! So let’s put pytest working in integration with Django. An interesting tool called ‘pytest-django’ bridges the use of pytest in conjunction with Django.
Two major tools I use often:
1. django_db_setup fixture that is automatically invoked before any tests that require access to the database. It is session-scoped which means it only gets fired once for the entire test session.
2. pytest.mark.django_db This is used to mark a test function as requiring the database. It will ensure the database is set up correctly for the test. Each test will run in its transaction which will be rolled back at the end of the test.
With these tools in your pocket, you can use django_db_setup to pre-populate your test database and use pytest.mark.django_db to run each of your tests within its transaction and roll back the changed data. It's super interesting to me!!
Later on, when I get adequate time, I will talk about how marks are different from fixtures.
And remember, as an engineer nothing can help you like tests. Embrace them!
To me the why behind using pytest is:
- empowering fixture mechanism
- assertion introspection
- parameterized tests
- rich test discovery
If you don’t know how exactly these items are getting to work you better take a close look at pytest’s source code. It’s cool and awesome to make you a better Python code reviewer.
Great! So let’s put pytest working in integration with Django. An interesting tool called ‘pytest-django’ bridges the use of pytest in conjunction with Django.
Two major tools I use often:
1. django_db_setup fixture that is automatically invoked before any tests that require access to the database. It is session-scoped which means it only gets fired once for the entire test session.
2. pytest.mark.django_db This is used to mark a test function as requiring the database. It will ensure the database is set up correctly for the test. Each test will run in its transaction which will be rolled back at the end of the test.
With these tools in your pocket, you can use django_db_setup to pre-populate your test database and use pytest.mark.django_db to run each of your tests within its transaction and roll back the changed data. It's super interesting to me!!
Later on, when I get adequate time, I will talk about how marks are different from fixtures.
And remember, as an engineer nothing can help you like tests. Embrace them!
Rust Type system
When reading the official Rust reference, you will be faced with a word item. To fully grasp the idea behind the Rust type system, you need to understand what are items in the first place.
- entirely determined at compile-time
- generally remain fixed during execution
- and may reside in read-only memory
A very good question to ask yourself is: what does type tell me?
- the interpretation of the memory holding it
- the operations that may be performed on the value
Huh? Isn't `interpretation of the memory` fancy?
I'm a true believer in simplicity, however, there are times when nice people out there tend to point to a complex concept with a name. `interpretation of the memory` is one of them. Lemme put the light on it.
The phrase "interpretation of the memory" refers to how the raw bytes stored in memory are understood and treated by the program. This concept is fundamental!
At the lowest level, computer memory is just a sequence of bytes. These bytes don't inherently have any meaning - they're just numbers from 0 to 255.
The type system provides rules for how to interpret these raw bytes as meaningful data. Different types have different memory layouts.
In essence, "interpretation of the memory" refers to the rules and mechanisms that Rust uses to ensure that the raw bytes in memory are consistently and correctly understood as the data types they represent, maintaining safety and correctness throughout the program's execution.
Next time a geek talking about type system, you know what are they talking about :)
When reading the official Rust reference, you will be faced with a word item. To fully grasp the idea behind the Rust type system, you need to understand what are items in the first place.
An item is simply any declaration that could appear globally in a program or module, such as a fn, struct, or use.
items are:- entirely determined at compile-time
- generally remain fixed during execution
- and may reside in read-only memory
A very good question to ask yourself is: what does type tell me?
- the interpretation of the memory holding it
- the operations that may be performed on the value
Huh? Isn't `interpretation of the memory` fancy?
I'm a true believer in simplicity, however, there are times when nice people out there tend to point to a complex concept with a name. `interpretation of the memory` is one of them. Lemme put the light on it.
The phrase "interpretation of the memory" refers to how the raw bytes stored in memory are understood and treated by the program. This concept is fundamental!
At the lowest level, computer memory is just a sequence of bytes. These bytes don't inherently have any meaning - they're just numbers from 0 to 255.
The type system provides rules for how to interpret these raw bytes as meaningful data. Different types have different memory layouts.
In essence, "interpretation of the memory" refers to the rules and mechanisms that Rust uses to ensure that the raw bytes in memory are consistently and correctly understood as the data types they represent, maintaining safety and correctness throughout the program's execution.
Next time a geek talking about type system, you know what are they talking about :)
Today, a nerd colleague interested in Rust saw a code I wrote for AdventOfCode2024. He asked me about Ok(()) in the return of function.
As this topic is something I really wanted to write a mini blog for, here we go then:
In Rust, the
enum is used to represent either a successful computation
or an error
It's a super common pattern for error handling.
How can this help me handle my errors?
Take a look at the following code:
First, I should explain what dyn Error means.
It's a trait object, allowing us to handle any error that implements the Error trait without knowing the specific type. super cool huh?
However, trait objects can't be used directly as return types because they have dynamic sizes. That's where Box comes in. By boxing the trait object, we get a managed pointer to it, which has a known size and can be returned.
So, after all these explanations, we can defer why we use
This pattern of wrapping values and chaining operations might remind functional programming enthusiasts of something familiar. In functional languages like Haskell, we often work with monadic structures that allow us to seamlessly chain computations while managing potential errors. The
As this topic is something I really wanted to write a mini blog for, here we go then:
In Rust, the
Result<T, E>
enum is used to represent either a successful computation
Ok(T)
or an error
Err(E)
It's a super common pattern for error handling.
How can this help me handle my errors?
Take a look at the following code:
fn foo() -> Result<(), Box<dyn Error>> {
Ok(())
}First, I should explain what dyn Error means.
It's a trait object, allowing us to handle any error that implements the Error trait without knowing the specific type. super cool huh?
However, trait objects can't be used directly as return types because they have dynamic sizes. That's where Box comes in. By boxing the trait object, we get a managed pointer to it, which has a known size and can be returned.
So, after all these explanations, we can defer why we use
Ok(()) instead of () right? It's about making a tuple (), a Result enum by wrapping the Ok.This pattern of wrapping values and chaining operations might remind functional programming enthusiasts of something familiar. In functional languages like Haskell, we often work with monadic structures that allow us to seamlessly chain computations while managing potential errors. The
Result type in Rust provides a similar elegant approach to error handling, letting us compose functions that can gracefully propagate and handle errors.