اولین روز که با 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 🤦🏻♂️