├── .gitignore ├── CONTRIBUTING.md ├── README.md ├── banner.jpg ├── c++ └── map-operator[] │ ├── README.md │ └── map-operator[].cpp ├── c └── implicit-integer-conversions │ ├── README.md │ ├── balancing.c │ └── promotion.c ├── go ├── for-loop-vars │ ├── README.md │ ├── correct.go │ ├── main.go │ └── with-goroutines.go ├── json-decoding-zero-initialization │ ├── README.md │ └── main.go ├── typed-nil │ ├── README.md │ └── main.go └── unsafe-fat-pointers │ ├── README.md │ └── unsafe-fat-pointers.go ├── java ├── array-variance │ ├── README.md │ └── array-variance.java ├── tri-state-maps │ ├── README.md │ └── tri-state-maps.java ├── type-erased-generics │ ├── README.md │ └── type-erased-generics.java └── unicode-escapes │ ├── README.md │ └── unicode-escapes.java ├── js ├── array-sort │ ├── README.md │ └── array-sort.js ├── automatic-semicolons │ ├── README.md │ └── automatic-semicolons.js ├── comparison-of-three-numbers │ ├── README.md │ └── example.js ├── completion-records │ ├── README.md │ └── finally.js ├── indexof-undefined │ ├── README.md │ ├── indexof.js │ └── string-count.js ├── infinite-circular-reference │ ├── README.md │ └── infinite-circular-reference.js ├── our-nans │ ├── README.md │ └── our-nans.js ├── proxy-chain │ └── proxy-chain.js ├── schrodingers-regex │ ├── README.md │ └── regex.js └── typeof-null │ ├── README.md │ └── typeof-null.js ├── php ├── call-function-by-timestamp │ ├── README.md │ ├── TimestampFunctionCaller.php │ ├── call.php │ └── encode.php ├── type-juggling-string-to-number │ ├── README.md │ └── starts-with-number.php └── type-juggling │ ├── README.md │ └── typeJuggler.php ├── python ├── capturing-variables │ ├── README.md │ └── capturing_variables.py ├── initialize-attributes-from-function │ ├── initialize-attributes-from-function.py │ └── readme.md ├── mutable-default-args │ ├── mutable-default-args.py │ └── readme.md └── shadowing-assignment-to-globals │ ├── README.md │ └── shadowing-assignment-to-globals.py └── ts └── reference-covariance ├── README.md └── example.ts /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | 4 | node_modules 5 | vendor 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Tragic Methods 2 | 3 | ## Pull requests 4 | 5 | To start contributing, create a fork of this repo. When you have a working example you can create a pull request for the main branch. 6 | 7 | ## Naming your quirk 8 | 9 | Name your quirk as narrow as possible to avoid collisions with other possible quirk names. 10 | 11 | ```diff 12 | -- array-addition 13 | ++ array-addition-results-in-empty-string 14 | ``` 15 | 16 | ## Where to place your quirk 17 | 18 | There is a directory for each language in the root of this project. If there is no directory for you programming language, feel free to create one. 19 | 20 | Every quirk should be placed in its own subdirectory. If you're going to create a `js` quirk called `foo-bar` with a script called `example.js` you should place your script under `/js/foo-bar/example.js`. 21 | 22 | So the structure should be as follows: 23 | 24 | ```bash 25 | . 26 | ├── js 27 | │ ├── foo-bar 28 | │ │ ├── README.md 29 | │ │ └── example.js 30 | │ └── ... 31 | └── ... 32 | ``` 33 | 34 | ## Document your quirk 35 | 36 | > :warning: Scripts without documentation will not be accepted. 37 | 38 | Because we want to make people aware of the language quirks used in these examples 39 | we require them to be document in a `README.md` file that resides in the same directory 40 | as the scripts. 41 | 42 | ### Structure of the readme 43 | 44 | #### The title 45 | 46 | The first line of your readme should be a title, most of the times this will just be the directory name of your quirk. 47 | 48 | #### Information about the language 49 | 50 | Because programming languages are ever evolving, we would like to keep track of which version of the language you used to test your quirk. 51 | 52 | If more information is needed (E.g. compiler used), you can add that information here as well 53 | 54 | Example: 55 | ``` 56 | Version: C++ 17 57 | Compiler: GCC 11 58 | ``` 59 | 60 | #### Explanation of your quirk 61 | 62 | A detailed explanation of your quirk that allows the user to understand the quirk without actually going through the scripts attached. If needed, add some code snippets. 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![banner](./banner.jpg) 2 | 3 | # Tragic Methods 4 | A collection of scripts depicting the strange quirks of programming languages. 5 | 6 | ## Contributing 7 | 8 | If you would like to add a script to this collection, please read the [Contribution guide](./CONTRIBUTING.md) 9 | -------------------------------------------------------------------------------- /banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neemspees/tragic-methods/3a6553a81389369b835c00e6acb7c145d7eee823/banner.jpg -------------------------------------------------------------------------------- /c++/map-operator[]/README.md: -------------------------------------------------------------------------------- 1 | # std::map::operator[] 2 | Tested with: clang 15 3 | 4 | `std::map::operator[]` can be used to read a value from a map with the same syntax as in many 5 | other languages. However, in C++, this mutates the map if the key is not already present, 6 | inserting the key together with a default-constructed value. 7 | The same holds for `std::unordered_map` and `std::flat_map`. 8 | 9 | This can sometimes be useful: 10 | ```cpp 11 | for(const auto item: items) { 12 | counts[item]++; 13 | } 14 | ``` 15 | but also can easily lead to bugs. -------------------------------------------------------------------------------- /c++/map-operator[]/map-operator[].cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | using std::string; 5 | using std::map; 6 | using std::cout; 7 | 8 | void print_some_config_item(map &config) { 9 | cout << "config[foo] = " << config["foo"] << "\n"; 10 | } 11 | 12 | int main() { 13 | map(empty_config); 14 | print_some_config_item(empty_config); 15 | // Prints "Config has 1 item(s)" 16 | cout << "Config has " << empty_config.size() << " item(s)\n"; 17 | } 18 | -------------------------------------------------------------------------------- /c/implicit-integer-conversions/README.md: -------------------------------------------------------------------------------- 1 | # Implicit integer conversions 2 | 3 | C has the integer types `_Bool`, `char`, `short`, `int`, `long` and `long long`, 4 | in signed and unsigned variants. Their sizes depend on the C implementation. 5 | The other defined integer types like `time_t` or the `stdint.h` types 6 | are just defined as aliases. 7 | 8 | In expressions, values of types `char` and `short` automatically get promoted to `int`, 9 | `unsigned char` and `unsigned short` get promoted to `unsigned int`. 10 | 11 | If an expression of an unsigned type results in a value that doesn't fit in the type, 12 | it gets truncated; for a signed type however this results in undefined behavior, meaning 13 | that the C standart imposes no requirements on the behavior of the entire program. 14 | Anything goes, such as formating your entire hard drive. 15 | 16 | Operands to arithmetic expressions get converted to the same type (balancing). 17 | If one type is signed, the other is unsigned and both are of the same rank, 18 | they get converted to the unsigned type. -------------------------------------------------------------------------------- /c/implicit-integer-conversions/balancing.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | 5 | int main(void) { 6 | unsigned int c = 1; 7 | signed int d = -2; 8 | if(c + d > 0) 9 | puts("1 + -2 > 0"); 10 | } -------------------------------------------------------------------------------- /c/implicit-integer-conversions/promotion.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(void) { 5 | // On a 32 bit or 64 bit platform, this prints -25536 as expected. 6 | // On a 16-bit platform however, this triggers UB and therefore may do anything. 7 | int16_t a = 20000; 8 | int16_t b = a + a; 9 | printf("%d\n", b); 10 | } -------------------------------------------------------------------------------- /go/for-loop-vars/README.md: -------------------------------------------------------------------------------- 1 | # For loop vars 2 | 3 | When you use a for loop in Go, only the value of the variables used in the iteration is updated - the address in memory stays the same. This can lead to some gotchas. 4 | 5 | In the first example, I initialized an integer slice with a few numbers. In the second slice, I want to store pointers to each of the vars in the first slice. The instinctive way to go about it would be to iterate over it using range, and appending to the 2nd slice the address of the 2nd variable defined in the for loop. Doing so results in the following behavior: 6 | 7 | go run main.go 8 | 5 0xc000134000 9 | 5 0xc000134000 10 | 5 0xc000134000 11 | 5 0xc000134000 12 | 5 0xc000134000 13 | 5 0xc000134000 14 | 15 | Pretty much the same thing but with goroutines: 16 | 17 | go run with-goroutines.go 18 | 5 0xc000018078 19 | 5 0xc000018078 20 | 5 0xc000018078 21 | 5 0xc000018078 22 | 5 0xc000018078 23 | 5 0xc000018078 24 | 25 | The correct way to do this would be to create a copy before passing it to the append: 26 | 27 | go run correct.go 28 | 0 0xc000134000 29 | 1 0xc000134008 30 | 2 0xc000134010 31 | 3 0xc000134018 32 | 4 0xc000134020 33 | 5 0xc000134028 34 | 35 | This is a known issue. See discussion about it [here.](https://github.com/golang/go/discussions/56010) 36 | Affects Go versions prior to 1.22 -------------------------------------------------------------------------------- /go/for-loop-vars/correct.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | nums := make([]int, 6) 9 | for i := 0; i < 6; i++ { 10 | val := i 11 | nums[i] = val 12 | } 13 | 14 | toPopulate := make([]*int, 0) 15 | 16 | for _, num := range nums { 17 | num := num 18 | toPopulate = append(toPopulate, &num) 19 | } 20 | 21 | for _, val := range toPopulate { 22 | fmt.Printf("%d %v\n", *val, val) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /go/for-loop-vars/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | nums := make([]int, 6) 9 | for i := 0; i < 6; i++ { 10 | val := i 11 | nums[i] = val 12 | } 13 | 14 | toPopulate := make([]*int, 0) 15 | 16 | for _, num := range nums { 17 | toPopulate = append(toPopulate, &num) 18 | } 19 | 20 | for _, val := range toPopulate { 21 | fmt.Printf("%d %v\n", *val, val) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /go/for-loop-vars/with-goroutines.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | nums := make([]int, 6) 10 | for i := 0; i < 6; i++ { 11 | val := i 12 | nums[i] = val 13 | } 14 | 15 | var wg sync.WaitGroup 16 | 17 | for _, val := range nums { 18 | wg.Add(1) 19 | 20 | go func(arg *int) { 21 | defer wg.Done() 22 | fmt.Printf("%d %v\n", *arg, arg) 23 | }(&val) 24 | } 25 | 26 | wg.Wait() 27 | } 28 | -------------------------------------------------------------------------------- /go/json-decoding-zero-initialization/README.md: -------------------------------------------------------------------------------- 1 | # JSon decoding with zero-initialization 2 | 3 | In Go, all values are zero initialized. Additionally, JSON decoding ignores doesn't check for the presence of all values and ignores extra values. 4 | 5 | As a result, trying to parse JSON with the wrong schema can easily silently succeed and return a struct with zero-initialized fields. 6 | 7 | Tested with Go 1.22 8 | -------------------------------------------------------------------------------- /go/json-decoding-zero-initialization/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | type myvalue struct { 9 | First int `json:"first"` 10 | } 11 | 12 | func main() { 13 | var obj myvalue 14 | js = "{\"second\": \"whatever\"}" 15 | err = json.Unmarshal([]byte(js), &obj) 16 | if err != nil { 17 | fmt.Errorf("Error") 18 | return 19 | } 20 | // Prints {First:0} 21 | fmt.Printf("%+v", obj) 22 | } 23 | -------------------------------------------------------------------------------- /go/typed-nil/README.md: -------------------------------------------------------------------------------- 1 | Go interfaces values consist of two pointers: One identifying the concrete type, necessary for calling interface methods or casting, the other pointing to the actual data. 2 | 3 | In the example, `this_is_nil` has the concrete type `*int`, so storing it's value in an interface sets the interface's type pointer accordingly and stores `this_is_nil`'s value in the data pointer. 4 | 5 | Comparing this interface to nil checks whether both pointers are nil – so trying to dereference it will result in a nil pointer dereference even though you explicitly checked that the interface isn't nil. 6 | 7 | Last tested with Go 1.21 -------------------------------------------------------------------------------- /go/typed-nil/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | var this_is_nil *int = nil 9 | 10 | var x interface{} 11 | x = this_is_nil 12 | 13 | if x != nil { 14 | var x_as_int = x.(*int) 15 | // Segmentation fault: 16 | fmt.Println(*x_as_int) 17 | } 18 | } -------------------------------------------------------------------------------- /go/unsafe-fat-pointers/README.md: -------------------------------------------------------------------------------- 1 | # Unsafe Fat Pointers 2 | Go version: All, tested with 1.20 3 | 4 | Most operations in Go are memory safe. The `unsafe` package 5 | contains those that are not. There is however a surprising exeption 6 | to this: 7 | 8 | Interfaces, slices and strings are implemented via unsynchronized 9 | fat pointers. For strings this is not a problem as they are immutable, 10 | but when interaces or slices are mutated by one thread while being read 11 | by another, a torn read may be observed. An interface consists of a pointer 12 | to an object and a pointer to a vtable, so in this event a method belonging 13 | to one type will be called with the data of another. 14 | 15 | This can cause arbitrary badness, from simply crashing to silently corrupting 16 | data to calling a function at an address chosen by an attacker. Similarly, 17 | slices consist of length, capacity and data pointers, so unsynchronized 18 | access may result in e.g. writing beyond the end of the slice. 19 | -------------------------------------------------------------------------------- /go/unsafe-fat-pointers/unsafe-fat-pointers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type I interface{ method() string } 6 | type A struct{ s string } 7 | type B struct { 8 | u uint32 9 | s string 10 | } 11 | 12 | func (a A) method() string { return a.s } 13 | func (b B) method() string { return b.s } 14 | 15 | func main() { 16 | a := A{s: "…"} 17 | b := B{u: ^uint32(0), s: "…"} 18 | var i I = a 19 | go func() { 20 | for { 21 | i = a 22 | i = b 23 | } 24 | }() 25 | for { 26 | if s := i.method(); len(s) > 3 { 27 | // Crashes with a segmentation fault 28 | fmt.Println(s) 29 | return 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /java/array-variance/README.md: -------------------------------------------------------------------------------- 1 | # Array variance 2 | 3 | Java arrays are covariant, meaning that `A[]` is a subclass of `B[]` 4 | if `A` is a subclass of `B`. This is unsound: You can insert objects 5 | of type `B` into a `B[]`, but not into an `A[]`. Because of this, 6 | Java needs to throw a `java.lang.ArrayStoreException` at run time, 7 | even though the code passes type-checking. 8 | -------------------------------------------------------------------------------- /java/array-variance/array-variance.java: -------------------------------------------------------------------------------- 1 | // This class doesn't do anything, but it's 2 | // necessary because Java doesn't support freestanding functions 3 | class Variance { 4 | public static void main(String[] args) { 5 | // This compiles without warnings, but throws an ArrayStoreException at runtime 6 | Object[] objects = new Integer[1]; 7 | objects[0] = new Object(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /java/tri-state-maps/README.md: -------------------------------------------------------------------------------- 1 | # Tri-State Maps 2 | 3 | Java version: Tested with 1.19 4 | 5 | Java divides values into primitives and objects. The former are incompatible with 6 | generics. As a result, primitives have to be boxed, that is wrapped in an object. 7 | For convenience Java does this automatically. For example, in `Map.of("foo", 42)`, 8 | the `42` is of type `int`, but gets silently casted to `Integer` before `Map.of` gets 9 | called. Java also automatically unboxes them: When an `Integer` is provided where an 10 | `int` is needed, it calls `Integer.intValue`. 11 | 12 | Another difference between primitives and object types is that object types are nullable, 13 | meaning that the value may instead be `null` (named the "billion-dollar-mistake" by its 14 | inventor). 15 | 16 | As a result, the Java standard library cannot directly express a map where each entry 17 | contains an `int` (or other primitive), instead for each possible key the map may have 18 | three states: It doesn't contain the key, it maps the key to an integer, or it maps the 19 | keys to `null`. 20 | 21 | Therefore using the result of `Map.get`, even after checking whether the key is present 22 | in the map (or alternatively by iterating over the map), and using the result as an `int` 23 | (or other primitive) can result in a `java.land.NullPointerException` for a method call 24 | not visible anywhere in the source code. 25 | -------------------------------------------------------------------------------- /java/tri-state-maps/tri-state-maps.java: -------------------------------------------------------------------------------- 1 | class Main { 2 | public static void main(String... args) { 3 | Map map = new HashMap<>(); 4 | map.put("foo", 42); 5 | map.put("bar", null); 6 | map.put("baz", 0); 7 | if (map.containsKey("bar")) { 8 | // Throws NullPointerException 9 | int bar = map.get("bar"); 10 | System.out.println("Bar: "+bar); 11 | } else { 12 | System.out.println("Bar not in map"); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /java/type-erased-generics/README.md: -------------------------------------------------------------------------------- 1 | # Type-erased generics 2 | 3 | The Java Virtual Machine does not have generics. The compiler does not monomorphise 4 | them, it fakes them by using `Object` instead of the generic type. 5 | 6 | As a consequence, only one class object represents all concrete instantiations of 7 | the generic class. The value of type parameters isn't known at runtime and 8 | therefore you can't do everything with a type parameter that you can do with 9 | a concrete type. For example, you can't obtain its `Class` object or 10 | create an array of it. 11 | 12 | ## Workarounds 13 | - use `Object[]` and add casts everywhere 14 | - use an `ArrayList`, which does the same internally (but adds a layer of indirection) 15 | - require the caller to repeat themself and take an argument of type `Class` that specifies 16 | the generic parameter (which is known at compile-time but got erased), then use reflection 17 | to create a new array of that class (`java.lang.reflect.Array.newInstance`) -------------------------------------------------------------------------------- /java/type-erased-generics/type-erased-generics.java: -------------------------------------------------------------------------------- 1 | class Foo { 2 | // You can create an array type out of any type 3 | Object[] concrete_array; 4 | // The type can be a type parameter 5 | T[] generic_array; 6 | 7 | Foo() { 8 | // You can also create instances of the array type 9 | concrete_array = new Object[0]; 10 | // …but not always (this fails to compile) 11 | generic_array = new T[0]; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /java/unicode-escapes/README.md: -------------------------------------------------------------------------------- 1 | # Unicode escapes 2 | 3 | Java allows unicode escapes in the form of `\` followed by at least one `u` 4 | followed by four hex digits representing the unicode code point. Notably this 5 | isn't limited to string and character literals. 6 | 7 | In the example, there is no difference between using `Object.equals` and 8 | `String.contentEquals`. Instead, the `\u000A` in the line comment on line 8 9 | gets parsed as a newline, ending the comment, so the following `!` is considered 10 | part of the boolean expression and negates it. Nevertheless, most syntax highlighting 11 | engines are not aware of this and treat it as a comment. 12 | -------------------------------------------------------------------------------- /java/unicode-escapes/unicode-escapes.java: -------------------------------------------------------------------------------- 1 | class Main { 2 | public static void main(String... args) { 3 | var string = 4 | // Java source files are UTF-8, so you can use any unicode directly 5 | // in string literals (though the string is represented in UTF-16)! 6 | "Hä?"; 7 | var match = 8 | // But string literals can also have unicode escapes like \u000A! 9 | string.equals("H\u00E4?"); 10 | 11 | if (match) { 12 | // But something strange is going on... 13 | System.out.println("This never prints!"); 14 | } 15 | 16 | var chars_match = 17 | // Yet if we treat it as a CharSequence... 18 | string.contentEquals("H\u00E4?"); 19 | 20 | if (chars_match) { 21 | System.out.println("This prints!"); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /js/array-sort/README.md: -------------------------------------------------------------------------------- 1 | # Array Sorting in JavaScript using `sort()` method and its surprising behavior 2 | 3 |

The JavaScript arrays can be confusing for developers that come from strongly typed languages like Java or C#. This means that the following is a valid array:

4 | 5 | ```javascript 6 | let arr = [4, 20, "hello", "world", function() {return "Hello World";}, [4,20]] 7 | ``` 8 | 9 |

Here, arr contains numbers, strings, another array and a function. What happens if we call sort on this array? The result of sorting is: 10 | 11 | ```javascript 12 | [20, 4, [4,20], function() {return "Hello World";}, "hello", "world" ] 13 | ``` 14 | 15 |

This might seem randomly sorted, but it isn't. The array sort() method sorts an array in place regardless of data types that reside with in; string array, numbered array, array of objects or any other array are all sorted the same way. Elements are sorted in ascending order, built upon converting elements into strings after which they are sorted according to each character's UTF-16 code value.

16 | 17 | ```javascript 18 | array.sort() 19 | array.sort(compareFn) 20 | ``` 21 | 22 | > **Note**: 23 | > Everything can be converted to a string, so any array can be sorted in JavaScript. 24 | 25 |

So, after learning about the array sort method, what is the result of the following sorting:

26 | 27 | ```javasript 28 | [1,3,2,10].sort() 29 | ``` 30 | 31 |

If your answer was [1, 2, 3, 10] you would've been wrong because the correct answer is [1, 10, 2, 3]. Default behavior of sort() method in this case produces "incorrect" results if you are expecting the array of numerals to be sorted. So, how can you fix this problem? You can fix it by passing the compare function (arrow function in this example) to the sort method:

32 | 33 | ```javascript 34 | [1,3,2,10].sort((a, b) => a - b) 35 | ``` 36 | 37 | ## References 38 | - [JavaScript Array sort](https://www.javascripttutorial.net/javascript-array-sort/) 39 | - [Array.prototype.sort()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) 40 | - [Javascript’s astonishing sort function](https://medium.com/@winwardo/the-principle-of-least-astonishment-and-javascripts-sort-e98a734a30c9) 41 | - [How to sort an array of integers correctly](https://stackoverflow.com/questions/1063007/how-to-sort-an-array-of-integers-correctly) 42 | - [Array.prototype.some ( callbackfn [ , thisArg ] )](https://262.ecma-international.org/13.0/#sec-array.prototype.sort) -------------------------------------------------------------------------------- /js/array-sort/array-sort.js: -------------------------------------------------------------------------------- 1 | console.log([1, 3, 2, 10].sort()) 2 | // Prints [1, 10, 2, 3] due to the default behavior of the sort() method. 3 | // In order to get the sorting of numerical values, you will need to write a compare function. 4 | 5 | console.log([1, 3, 2, 10].sort((a, b) => a - b)) 6 | // Prints [1, 2, 3, 10]. 7 | 8 | console.log([1, 3, 2, 10].sort(function(a, b) {return a - b})) 9 | // Prints [1, 2, 3, 10]. 10 | 11 | console.log([1, 3, 2, 10].sort(function(a, b) {return a > b ? 1 : a < b ? -1 : 0})) 12 | // Prints [1, 2, 3, 10]. 13 | 14 | console.log(Array.from(Int8Array.from([1, 3, 2, 10]).sort())) 15 | // Prints [1, 2, 3, 10]. -------------------------------------------------------------------------------- /js/automatic-semicolons/README.md: -------------------------------------------------------------------------------- 1 | # Automatic Semicolon Insertion in JavaScript 2 | 3 | ## Tested using 4 | 5 | Node.js v19.7.0 6 | 7 | ## Explanation 8 | 9 | [Automatic semicolon insertion](https://en.wikipedia.org/wiki/Lexical_analysis#Semicolon_insertion) is present in a few languages, but JavaScript's implementation has a few special rules. 10 | 11 | One being the expression after one of the keywords `return`, `throw`, or `yield` must be on the same line as the keyword itself. 12 | 13 | Example: 14 | 15 | ```js 16 | function automatic() { 17 | return 18 | "i won't return" 19 | } 20 | 21 | let res = automatic(); 22 | 23 | console.log(res === undefined); // true 24 | ``` 25 | 26 | What happened here? Since the return statement is on a separate line, the JavaScript interpreter added some semicolons for us. This is how the above function gets parsed. 27 | 28 | ```js 29 | function automatic() { 30 | return; 31 | "i won't return"; 32 | } 33 | ``` 34 | 35 | Fixing this is simple enough, just put your return statement on the same line. 36 | 37 | --- 38 | 39 | Another rule is for opening paranthesis, [without a semicolon, the two lines together are treated as a CallExpression.](https://tc39.es/ecma262/#sec-asi-interesting-cases-in-statement-lists) 40 | 41 | This can lead to issues with [IIFEs](https://developer.mozilla.org/en-US/docs/Glossary/IIFE), functions that are run as soon as they are defined. 42 | 43 | Example: 44 | 45 | ```js 46 | // note lack of semicolon after 2 47 | let res = 1 + 2 48 | (function () { 49 | console.log(res); 50 | })(); 51 | ``` 52 | 53 | We get a TypeError trying to run this. Specifically: 54 | 55 | ```sh 56 | (function () { 57 | ^ 58 | 59 | TypeError: 2 is not a function 60 | at Object. (/workspaces/tragic-methods/js/automatic-semicolons/automatic-semicolons.js:14:1) 61 | at Module._compile (node:internal/modules/cjs/loader:1275:14) 62 | at Module._extensions..js (node:internal/modules/cjs/loader:1329:10) 63 | at Module.load (node:internal/modules/cjs/loader:1133:32) 64 | at Module._load (node:internal/modules/cjs/loader:972:12) 65 | at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:83:12) 66 | at node:internal/main/run_main_module:23:47 67 | ``` 68 | 69 | What's happening here? The above statement is getting evaluated as one expression: 70 | 71 | ```js 72 | let res = 1 + 2() { console.log(res) }; 73 | ``` 74 | 75 | Again, the fix is simple: 76 | 77 | ```js 78 | let res = 1 + 2; 79 | ;(function () { 80 | console.log(res); // 3 81 | })(); 82 | ``` 83 | 84 | > **Note** 85 | > These issues are pretty easy to avoid using linting and formatting tools like ESLint to enforce semicolons. 86 | 87 | ## References 88 | 89 | * [MDN - JavaScript Lexical Grammar](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#automatic_semicolon_insertion) 90 | -------------------------------------------------------------------------------- /js/automatic-semicolons/automatic-semicolons.js: -------------------------------------------------------------------------------- 1 | // JS inserts semicolons in situations where statements span multiple lines 2 | 3 | function automatic() { 4 | return 5 | "i won't return" 6 | } 7 | 8 | let res = automatic(); 9 | 10 | console.log(res === undefined); 11 | 12 | // ever wonder why IIFE's sometimes have semicolons placed in front of them? 13 | res = 1 + 2; 14 | ;(function () { 15 | console.log(res); 16 | })(); 17 | -------------------------------------------------------------------------------- /js/comparison-of-three-numbers/README.md: -------------------------------------------------------------------------------- 1 | # Comparison of three numbers 2 | 3 | If you thought you were good at comparing numbers, think again. This quirky Javascript code compares the numbers 1, 2 and 3 with less- and greater than operators. The result however is not the same. 4 | 5 | ## The Example 6 | 7 | ```js 8 | 1 < 2 < 3; // true 9 | 3 > 2 > 1; // false 10 | ``` 11 | 12 | Both statements should be true right? Well, the problem is the first part of the expression, this gets handled first. 13 | 14 | ```js 15 | 1 < 2 < 3; // 1 < 2 -> true 16 | true < 3; // true -> 1 17 | 1 < 3; // true 18 | 19 | 3 > 2 > 1; // 3 > 2 -> true 20 | true > 1; // true -> 1 21 | 1 > 1; // false 22 | ``` 23 | 24 | Should you want to fix the statement you could use the following. 25 | 26 | ```js 27 | 3 > 2 >= 1; // true 28 | ``` 29 | 30 | This has all to do with how relational operators work. Should you want to read more about it, check out the [MDN web docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators). 31 | -------------------------------------------------------------------------------- /js/comparison-of-three-numbers/example.js: -------------------------------------------------------------------------------- 1 | console.log(1 < 2 < 3); // true 2 | console.log(3 > 2 > 1); // false 3 | -------------------------------------------------------------------------------- /js/completion-records/README.md: -------------------------------------------------------------------------------- 1 |

Conflicting Completion Records.

2 | 3 | What does this function return? 4 | 5 | ```js 6 | function foo() { 7 | try { 8 | return true; 9 | } finally { 10 | return false; 11 | } 12 | return null; 13 | } 14 | ``` 15 | 16 | In JavaScript `return` generates a "Completion Record" which specifies an 17 | intention. This record may be overriden. The code snippet above hits the 18 | `return true` and writes a completion record intending to return `true`. 19 | Then the `finally` clause is called, and the `return false` overwrites the 20 | earlier record. The `return null` is never reached. Thus `false` is the 21 | final return value. 22 | 23 | The same rules also apply to `break` and `continue`. Consider the following: 24 | 25 | ```js 26 | function bar() { 27 | for (var i = 0; i < 10; i++) { 28 | try { 29 | return i; 30 | } finally { 31 | continue; 32 | } 33 | } 34 | return null; 35 | } 36 | ``` 37 | 38 | Affects all versions of JavaScript from ECMA-262 1st edition (June 1997) to present. 39 | The same behaviour is present in Java, Python, C, and most other languages. 40 | -------------------------------------------------------------------------------- /js/completion-records/finally.js: -------------------------------------------------------------------------------- 1 | function foo() { 2 | try { 3 | return true; 4 | } finally { 5 | return false; 6 | } 7 | return null; 8 | } 9 | console.log(foo()); // Returns false. 10 | 11 | 12 | function bar() { 13 | for (var i = 0; i < 10; i++) { 14 | try { 15 | return i; 16 | } finally { 17 | continue; 18 | } 19 | } 20 | return null; 21 | } 22 | console.log(bar()); // Returns null. 23 | -------------------------------------------------------------------------------- /js/indexof-undefined/README.md: -------------------------------------------------------------------------------- 1 |

indexOf without arguments.

2 | 3 | String's `indexOf` method returns the position of the argument in the called 4 | string. For example: 5 | 6 | ```js 7 | 'JavaScript'.indexOf('Script'); // Returns 4. 8 | 'JavaScript'.indexOf('Pineapple'); // Returns -1. 9 | ``` 10 | 11 | If the argument isn't a string, it gets coerced to one: 12 | 13 | ```js 14 | 'Copyright 2023'.indexOf(2023); // Returns 10. 15 | ``` 16 | 17 | When no argument is passed, normally things work as one would expect: 18 | 19 | ```js 20 | 'JavaScript'.indexOf(); // Returns -1. 21 | ``` 22 | 23 | However, sometimes indexOf can return unexpected results: 24 | 25 | ```js 26 | 'drundefineder'.indexOf(); // Returns 2. 27 | ``` 28 | 29 | The issue is that JavaScript always coerces the first argument as a string -- even 30 | if the argument does not exist. So in this case the first argument is 31 | `undefined` and thus becomes `'undefined'`. If that sequence of letters exists 32 | in the called string, then it matches. 33 | 34 | Affects all versions of JavaScript from ECMA-262 1st edition (June 1997) to present. 35 | -------------------------------------------------------------------------------- /js/indexof-undefined/indexof.js: -------------------------------------------------------------------------------- 1 | // Returns 2. 2 | console.log('drundefineder'.indexOf()); 3 | -------------------------------------------------------------------------------- /js/indexof-undefined/string-count.js: -------------------------------------------------------------------------------- 1 | const stringLength = (string) => (string + [][0]).indexOf() 2 | 3 | console.log(stringLength('abc')) 4 | 5 | // > 3 6 | -------------------------------------------------------------------------------- /js/infinite-circular-reference/README.md: -------------------------------------------------------------------------------- 1 | # Infinite Circular Reference 2 | 3 | Circular references are not so much unexpected behaviour but can make for some ridiculously looking code. 4 | 5 | --- 6 | 7 | ## The Example 8 | Naming in this example was made intentionally confusing or _funcy_ 9 | 10 | We create a function `func` that logs a parameter `func` 11 | ```js 12 | const func = (func) => console.log(func); 13 | ``` 14 | 15 | Functions in Javascript are also objects which means you can assign properties to them. 16 | We assign the function `func` to the property `func` to make a circular reference. 17 | 18 | ```js 19 | func.func = func; 20 | ``` 21 | 22 | Making it possible to call on itself indefinitely before executing the function. 23 | ```js 24 | func.func.func.func.func/*...*/.func('func') 25 | ``` 26 | -------------------------------------------------------------------------------- /js/infinite-circular-reference/infinite-circular-reference.js: -------------------------------------------------------------------------------- 1 | //Javascript functions are also objects, so it is possible to assign properties to them. 2 | //We assign the function itself to a property with the same name making it circular. 3 | const func = (func) => console.log(func); 4 | func.func = func; 5 | func.func.func.func.func 6 | .func.func.func.func 7 | .func.func.func.func 8 | .func.func.func.func 9 | .func.func.func.func 10 | .func.func.func.func 11 | .func.func.func.func 12 | .func.func.func.func 13 | .func.func.func('func'); 14 | -------------------------------------------------------------------------------- /js/our-nans/README.md: -------------------------------------------------------------------------------- 1 | # Our NaNs 2 | 3 | This is an example of the "quirky" but expected behaviour of the `NaN` (Not a Number) value. 4 | The example uses a wordplay on the word Nan (Grandmother) - just for fun. 5 | 6 | --- 7 | 8 | ## The Example 9 | 10 | The functions are one of many ways to return a `NaN` value in javascript. 11 | `NaN` values are returned by any equation containing an undefined variable. 12 | ```js 13 | const yourNan = () => { 14 | let yourMom, yourMomsMom; 15 | return yourMom + yourMomsMom; 16 | } 17 | ``` 18 | 19 | The function itself returns `NaN`. 20 | The `isNaN` function is provided by javascript to check if the given value is `NaN` as it is not possible to compare `NaN` to another `NaN` value. 21 | 22 | ```js 23 | console.log('Is your nan a nan?', yourNan(), isNaN(yourNan())); 24 | ``` 25 | 26 | As mentioned before comparing `NaN` to another (or the same) `NaN` value is not possible and will always return `false`. 27 | It doesn't matter if a loose (`==`) or strict (`===`) equality operator is used. 28 | ```js 29 | console.log('Are our nans the same nan?', yourNan() === myNan(), yourNan() == myNan()); 30 | const myGrandmother = myNan(); 31 | console.log('wait what?', myGrandmother === myGrandmother); 32 | ``` 33 | The IEEE 754 spec for floating-point numbers describes that `NaN`s are never equal. 34 | -------------------------------------------------------------------------------- /js/our-nans/our-nans.js: -------------------------------------------------------------------------------- 1 | //both functions return NaN as one or both variables in the equation are undefined 2 | const yourNan = () => { 3 | let yourMom, yourMomsMom; 4 | return yourMom + yourMomsMom; 5 | } 6 | 7 | const myNan = () => { 8 | let myMom, myMomsMom; 9 | return myMom + myMomsMom; 10 | } 11 | 12 | //both functions return NaN. isNaN will confirm the NaN value 13 | console.log('Is your nan a nan?', yourNan(), isNaN(yourNan())); // returns Is your nan a nan? NaN true 14 | console.log('Is my nan a nan?', myNan(), isNaN(myNan())); // returns Is my nan a nan? NaN true 15 | 16 | //The IEEE 754 spec for floating-point numbers (which is used by all languages for floating-point) says that NaNs are never equal 17 | console.log('Are our nans the same nan?', yourNan() === myNan(), yourNan() == myNan()); //returns Are our nans the same nan? false false 18 | const myGrandmother = myNan(); 19 | console.log('wait what?', myGrandmother === myGrandmother); //returns wait what? false 20 | -------------------------------------------------------------------------------- /js/proxy-chain/proxy-chain.js: -------------------------------------------------------------------------------- 1 | /** 2 | * We are leveraging proxies to intercept property calls 3 | * and returning the proxy itself so we can keep on chaining infinitely 4 | * 5 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy 6 | */ 7 | const proxy = new Proxy( 8 | {}, 9 | { 10 | get: (_, prop, receiver) => { 11 | // If the property we are trying to access is called YEET, we will log "YEET" to the console 12 | if (prop === 'YEET') { 13 | console.log('YEET'); 14 | } 15 | 16 | // Always return the proxy back 17 | return receiver; 18 | }, 19 | }, 20 | ); 21 | 22 | proxy.you.can.call.literally.anything.but.if.you.call.YEET.it.will.log.YEET.to.the.console; 23 | -------------------------------------------------------------------------------- /js/schrodingers-regex/README.md: -------------------------------------------------------------------------------- 1 |

Schrodinger's regex

2 | 3 | > Tested using NodeJS v18.13.0
But this should function in [all modern javascript engines](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/lastIndex#browser_compatibility). 4 | 5 | Regular expressions in javascript can be funky.
Let's look at an example. 6 | 7 | --- 8 | 9 |

The Example

10 | 11 | You have a regular expression with a global flag. You want to check a string for the word ``Test``. You have a big string that you want to run this regex against. You need to check if a match exists and if it does get all matches for the string and do something with them. You create a new regex object: 12 | 13 | ```js 14 | const regex = new RegExp(/Test/g); 15 | ``` 16 | 17 | Since regexes are expensive operations we first run a ``regex.test()`` against our string to check if any matches even exist. 18 | 19 | ```js 20 | const string = "Testing"; 21 | if (regex.test(string)) { 22 | matches = regex.exec(string); 23 | // Do stuff 24 | } 25 | ``` 26 | 27 | This code is OK. But imagine we needed to run the same regex multiple times on the same string: 28 | 29 | ```js 30 | const string = "Testing"; 31 | if (regex.test(string)) { 32 | matches = regex.exec(string); 33 | // Do stuff once 34 | } 35 | //Do something else 36 | if (regex.test(string)) { 37 | matches = regex.exec(string); 38 | // Do stuff again 39 | } 40 | ``` 41 | 42 | Due to the way that javascript handles regular expressions, this code is flawed. 43 | 44 | --- 45 | 46 |

Stateful RegExp objects and flags.

47 | 48 | You would think that just checking if a regex exists inside our big string using ``regex.test()`` multiple times would return the same value every time, right?
Not necessarily. 49 | 50 | In the above example we use a ``global`` flag in our regex. 51 | 52 | From the *[MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/global)* : 53 | > The ``global`` accessor property indicates whether or not the ``g`` flag is used with the regular expression. The ``g`` flag indicates that the regular expression should be tested against all possible matches in a string. 54 | 55 | When we set the ``global`` (or ``sticky``) flag on a javascript RegExp object, it becomes stateful. 56 | 57 | From the *[MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test)* : 58 | > JavaScript ``RegExp`` objects are **stateful** when they have the ``global`` or ``sticky`` flags set (e.g., ``/foo/g`` or ``/foo/y``). They store a ``lastIndex`` from the previous match. Using this internally, ``test()`` can be used to iterate over multiple matches in a string of text (with capture groups). 59 | 60 | This behaviour does not affect us as long as we don't have a ``global`` or ``sticky`` flag set, or test the same thing twice. This is why the first code snippet is functional, but the second code snippet breaks. Since we have the global flag set, when we run the same code multiple times it will not return the same result since due to the existance of ``lastIndex`` the code will not be checking the regex against the string we think it is checking it against. 61 | 62 | --- 63 | 64 |

How to fix this?

65 | 66 | If you really need a global or sticky flag in your regular expression, and need to use it multiple times, run it once and save the result in a variable, create an entirely new test every time, or some other similar solution. 67 | -------------------------------------------------------------------------------- /js/schrodingers-regex/regex.js: -------------------------------------------------------------------------------- 1 | //Create a regex for the string "Test" with the global flag enabled 2 | const regex = new RegExp(/Test/g); 3 | //Create a "big" string with content "Testing" to test against 4 | const bigString = "Testing"; 5 | 6 | //Run test (Expected result: True; Actual result: True) 7 | console.log(regex.test(bigString)); 8 | //Run test (Expected result: True; Actual result: False) 9 | console.log(regex.test(bigString)); 10 | //Run test (Expected result: True; Actual result: True) 11 | console.log(regex.test(bigString)); 12 | //Run test (Expected result: True; Actual result: False) 13 | console.log(regex.test(bigString)); 14 | // ... 15 | -------------------------------------------------------------------------------- /js/typeof-null/README.md: -------------------------------------------------------------------------------- 1 |

typeof null === "object"

2 | 3 | Devlopers can use JavaScript's `typeof` operator to get the type of something. For example: 4 | 5 | ```js 6 | typeof "hello"; // Evaluates to "string" 7 | 8 | typeof { hello: "world" }; // Evaluates to "object" 9 | ``` 10 | 11 | This is incredibly handy, but using it with `null` can easily introduce bugs. That's because `typeof null` evaluates to `"object"`: 12 | 13 | ```js 14 | typeof null; // "object" 15 | ``` 16 | 17 | If you're reading this and thinking that seems like bizarre, counterintuitive behavior, you're right! 18 | 19 | ## Example 20 | 21 | Let's see an example of how this could be problematic in practice. Imagine a developer wants to write a function that's responsible for returning the value of the `description` property on an object, or `undefined` if the argument provided isn't an object or doesn't have a `description` property. They may attempt to write something like this: 22 | 23 | ```js 24 | function getDescription(value) { 25 | if (typeof value !== "object") { 26 | return undefined; 27 | } 28 | 29 | if (!value.hasOwnProperty("description")) { 30 | return undefined; 31 | } 32 | 33 | return value.description; 34 | } 35 | ``` 36 | 37 | At first glance, this looks like it should work as expected. The `if (typeof value !== "object")` guard clause ensures that the value we're dealing with is an object and `if (!value.hasOwnProperty('description'))` ensures that that object has a `description` poperty. If those checks pass, then the value of `value.description` is returned. What could go wrong? 38 | 39 | Despite the initial appearance of correctness, this function is buggy when called with `null` as an argument. Since `typeof null` evaluates to `"object"`, the first guard clause passes. The code after that continues to execute and the `.hasOwnProperty()` method is called on `null`. `null` of course has no such method, so a `TypeError` is thrown. 40 | 41 | ### Example usage 42 | 43 | ```js 44 | getDescription({ description: "New product" }); 45 | // Returns "New product" 46 | 47 | getDescription("hello"); 48 | // Returns `undefined` 49 | 50 | getDescription(null); 51 | // Uncaught TypeError: Cannot read properties of null (reading 'hasOwnProperty') 52 | ``` 53 | 54 | To fix this particular function, the first check would need to be updated to `typeof value !== "object" && value !== null`, or the developer would need to verify whether the argument provided is a true object by some other means. (Side note: don't forget that in JavaScript, an array is a type of object, too!) 55 | 56 | ## Moral of the Story 57 | 58 | Whenever you're doing type checking, don't assume that something with a type of "object" is in fact an object (as silly as that may sound!). Always account for the possibility of a `null` value and check for that explicitly. 59 | 60 | ## More Info 61 | 62 | The MDN page on `typeof` provides historical information on why `typeof null` evaluates to "object", if you care to dig a bit deeper: 63 | https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof#typeof_null 64 | -------------------------------------------------------------------------------- /js/typeof-null/typeof-null.js: -------------------------------------------------------------------------------- 1 | // Evaluates to "object". 2 | typeof null; 3 | -------------------------------------------------------------------------------- /php/call-function-by-timestamp/README.md: -------------------------------------------------------------------------------- 1 | # Call function by timestamp 2 | 3 | 4 | ## Tested using 5 | PHP 8.0 6 | 7 | ## Explanation 8 | 9 | The __call method will get called whenever a non-existing method is called 10 | on and instance of a class. We will use the name of this non-existing method, 11 | which should be a valid RFC3339 timestamp, and start processing it. 12 | 13 | We leverage the ASCII incrementing functionality and increment a starting string "a" 14 | as many times as there are seconds since epoch until the date calculated from the given timestamp. 15 | 16 | This will result in a string with the name of the original function we are trying to call 17 | and, since we're using PHP, we can just call that string as if it were a function. 18 | 19 | ```php 20 | class TimestampFunctionCaller { 21 | public function __call(string $name, array $arguments): mixed 22 | { 23 | $count = DateTime::createFromFormat(DATE_RFC3339, $name)->getTimestamp(); 24 | 25 | $functionName = 'a'; 26 | 27 | for ($i = 0; $i < $count; $i++) { 28 | $functionName++; // This line will increment the ASCII value this string, so a -> b -> c -> ... 29 | } 30 | 31 | return $functionName(); 32 | } 33 | } 34 | ``` 35 | 36 | Let's say we have a function called `login` we wanted to call this way. 37 | 38 | It takes `5752331` increments to get from string `"a"` to string `"login"`. 39 | Now we use `5752331` as an epoch timestamp and convert it to RFC3339 resulting in `1970-03-08T13:52:11+00:00`; 40 | We can use this final timestamp to call the login function. 41 | 42 | ```php 43 | function login() { 44 | echo 'You successfully hacked the system!' . PHP_EOL; 45 | } 46 | 47 | [new TimestampFunctionCaller, '1970-03-08T13:52:11+00:00'](); 48 | 49 | // > You successfully hacked the system! 50 | ``` 51 | -------------------------------------------------------------------------------- /php/call-function-by-timestamp/TimestampFunctionCaller.php: -------------------------------------------------------------------------------- 1 | $char) { 19 | $count += (array_search($char, $map) + 1) * 26 ** $i; 20 | } 21 | 22 | $date = new DateTime(); 23 | $date->setTimestamp($count); 24 | 25 | return $date->format(DATE_RFC3339); 26 | } 27 | 28 | /** 29 | * The __call method will get called whenever a non-existing method is called 30 | * on and instance of this class. We will use the name of this non-existing method, 31 | * which should be a valid RFC3339 timestamp, and start processing it. 32 | * We leverage the ASCII incrementing functionality and increment a starting string "a" 33 | * as many times as there are seconds since epoch until the date calculated from the given timestamp. 34 | * This will result in a string with the name of the original function we are trying to call 35 | * and, since we're using PHP, we can just call that string as if it were a symbol. 36 | */ 37 | public function __call(string $name, array $arguments): mixed 38 | { 39 | $count = DateTime::createFromFormat(DATE_RFC3339, $name)->getTimestamp(); 40 | 41 | $functionName = 'a'; 42 | 43 | for ($i = 0; $i < $count; $i++) { 44 | $functionName++; 45 | } 46 | 47 | return $functionName(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /php/call-function-by-timestamp/call.php: -------------------------------------------------------------------------------- 1 | login . PHP_EOL; 7 | -------------------------------------------------------------------------------- /php/type-juggling-string-to-number/README.md: -------------------------------------------------------------------------------- 1 | # Type juggling string to number 2 | 3 | ## Tested using 4 | PHP 8.2 5 | 6 | ## Explanation 7 | 8 | Due to the nature of how PHP handles type casting from string to number there are a few strange quirks that arise. 9 | 10 | Consider the following string: 11 | ```php 12 | $casted = (int) "test"; // 0 13 | ``` 14 | 15 | PHP casts this into an integer with the value 0. I think this is weird in my opinion, but at least we know that string to integer casts will be 0 every time ... right? 16 | 17 | Well let's take the following example 18 | ```php 19 | $casted = (int) "123test"; // 123 20 | ``` 21 | 22 | This is what's known as a leading numeric string and when casted to an integer, PHP just drops everything after the number. So in this case `$casted` would be equal to `int(123)`. 23 | 24 | > [PHP docs on what is defined as a numeric string](https://www.php.net/manual/en/language.types.numeric-strings.php) 25 | 26 | This gave me an idea for a rather odd function: 27 | ```php 28 | function startsWithNumber(string $input) { 29 | return (bool) (float) $input; 30 | } 31 | 32 | startsWithNumber('test'); // false 33 | 34 | startsWithNumber('123test'); // true 35 | ``` 36 | 37 | But please don't use this in production because it fails when using it on strings that start with `"0"` due to the fact that boolean casts on `0` will result in a `false` value. 38 | -------------------------------------------------------------------------------- /php/type-juggling-string-to-number/starts-with-number.php: -------------------------------------------------------------------------------- 1 | str: 4 | """Generate a random ID string of specified length""" 5 | print("A new random ID has been requested!") 6 | return ''.join(random.choices('0123456789abcdef', k=length)) 7 | 8 | class Demo: 9 | """A class representing a demo object with an ID""" 10 | def __init__(self,id = generate_id(6)): 11 | """Instantiate the demo object with the ID from the 'id' parameter.""" 12 | self.id = id 13 | 14 | print('Starting script execution!') 15 | 16 | # Create two objects with an empty constructor, 17 | # relying on the constructor's default values to populate the fields 18 | demo_object_a = Demo() 19 | demo_object_b = Demo() 20 | 21 | # Print the IDs of the two demo objects 22 | print(f'Random(?) ID of object a: {demo_object_a.id}') 23 | print(f'Random(?) ID of object b: {demo_object_b.id}') -------------------------------------------------------------------------------- /python/initialize-attributes-from-function/readme.md: -------------------------------------------------------------------------------- 1 | # Initialize attributes from function 2 | Using functions as default parameters for class constructors will cause them to only be executed once during initialization. 3 | 4 | ## Tested using 5 | Version Python 3.10.7 6 | 7 | ## Explanation 8 | Looking at the code in `initialize-attributes-from-function.py` one might expect that the two demo objects would get different random IDs, but they don't. 9 | Instead, the `generate_id` method is only called once during class initialization and the value is then reused for all following calls of the constructor. 10 | No warnings are generated from all of this (at least not using my VSCode setup) and the entire code seems to work perfectly fine until a second object of the affected class is created and the ids are compared. 11 | 12 | ## Workarounds 13 | Python will evaluate the function and assign it as the default value for the function on that evaluation. If you intend that behaviour, then this can be a feature, as many tragic methods may be. But don't fret! There's various work-arounds to this. 14 | 1. Replace the `id` default value with the function name instead of the function call. This way, the function instead of it's evaluation is the default. The function will evaluate on each instatiation, and a different ID will be derived. 15 | ```python 16 | class Demo: 17 | """A class representing a demo object with an ID""" 18 | def __init__(self, id = generate_id): 19 | """Instantiate the demo object with the ID from the 'id' parameter.""" 20 | self.id = id(6) 21 | ``` 22 | 2. Higher order object. Working on the previous idea, we can set up the initializtion method to take any function we would like. 23 | ```python 24 | class Demo: 25 | """A class representing a demo object with an ID""" 26 | def __init__(self, id_func = generate_id, id_args = [6]): 27 | """Instantiate the demo object with the ID from the 'id' parameter.""" 28 | self.id = id_func(*id_args) 29 | ``` 30 | 3. Another idiomatic way of addressing this issue is to use None as a default argument 31 | ```python 32 | class Demo: 33 | """A class representing a demo object with a default value as None""" 34 | def __init__(self, id_func = None): 35 | if not id_func: 36 | id_func = generate_id 37 | self.id = id_func() 38 | ``` 39 | In either of these cases, we can avoid unwanted behaviour by being careful to not set the default of `id` to be whatever a function evaluates on the first instatiation. 40 | -------------------------------------------------------------------------------- /python/mutable-default-args/mutable-default-args.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | def add_two_lists(a: list = [], b: list = []) -> list: 4 | a += b 5 | return a 6 | 7 | def add_two_lists_proper(a: Optional[list] = None, b: Optional[list] = None) -> list: 8 | return (a or []) + (b or []) 9 | 10 | if __name__ == "__main__": 11 | print(add_two_lists()) # [] 12 | print(add_two_lists(b=[1])) # [1] 13 | 14 | # Due to the mutability of default parameters in Python, when this function is executed again 15 | # the value of `a` is now [1], although the intuitive expectation would be that `a` is still [] 16 | a = add_two_lists() 17 | print(a) # [1] 18 | a.append(2) 19 | print(add_two_lists()) # [1, 2] 20 | 21 | print("*" * 80) 22 | 23 | print(add_two_lists_proper()) # [] 24 | print(add_two_lists_proper(b=[1])) # [1] 25 | # By setting the default to None, and defaulting to a newly constructed empty list, this problem is avoided. 26 | a = add_two_lists_proper() 27 | print(a) # [] 28 | a.append(2) 29 | print(add_two_lists_proper()) # [] 30 | -------------------------------------------------------------------------------- /python/mutable-default-args/readme.md: -------------------------------------------------------------------------------- 1 | # Mutable default arguments 2 | Using mutable objects as default arguments in a function will cause their values to change unexpectedly. 3 | 4 | ## Tested using 5 | Version: Python 3.11.2 6 | 7 | ## Explanation 8 | One might expect that default arguments of a Python function are immutable, and that running the function multiple times will always return the same result. 9 | 10 | That is not the case, as the default arguments are mutable. This means that any operations performed on these values will be persist between function calls, even operations performed on the object done outside the function will change their state (As they are returned by reference, not by value). 11 | 12 | See this [stackoverflow post](https://stackoverflow.com/questions/1132941/least-astonishment-and-the-mutable-default-argument) for further details. 13 | -------------------------------------------------------------------------------- /python/shadowing-assignment-to-globals/README.md: -------------------------------------------------------------------------------- 1 | # Shadowing Assignment to Globals 2 | 3 | In Python, global variables are always in scope. They can not 4 | only be read, but also modified. However, if you try to modify them 5 | by assigning directly to the variable, a new variable that shadows 6 | the global variable is created instead (declare the variable as `global` 7 | within the function to prevent this). 8 | -------------------------------------------------------------------------------- /python/shadowing-assignment-to-globals/shadowing-assignment-to-globals.py: -------------------------------------------------------------------------------- 1 | list = [0] 2 | 3 | 4 | def set_to_1(): 5 | list[0] = 1 6 | 7 | 8 | def set_to_2(): 9 | list = [2] 10 | 11 | 12 | # Prints 0 13 | print(list[0]) 14 | 15 | # Prints 1 16 | set_to_1() 17 | print(list[0]) 18 | 19 | # Still prints 1 20 | set_to_2() 21 | print(list[0]) 22 | -------------------------------------------------------------------------------- /ts/reference-covariance/README.md: -------------------------------------------------------------------------------- 1 | # Reference covariance 2 | 3 | A reference of a subtype can be assigned to a reference of its supertype, this seems logical at first glance because every subtype is compatible with its supertype. 4 | 5 | ## Tested using 6 | Version: TypeScript 4.9.5 7 | 8 | ## Explanation 9 | 10 | ```typescript 11 | const circles: Circle[] = []; 12 | const shapes: Shape[] = circles; 13 | ``` 14 | 15 | The only problem with this, is that the array of `Shapes` is a reference to the array of `Circles` and **not** a clone. This means that, if you add an element of a different subtype of `Shape` to the array of `Shapes`, it will also be added to the array of `Circles`. 16 | 17 | ```typescript 18 | function disguiseAsCircle(square: Square): Circle { 19 | const circles: Circle[] = []; 20 | const shapes: Shape[] = circles; 21 | 22 | shapes.push(square); 23 | 24 | return circles[0]; 25 | } 26 | ``` 27 | 28 | Meaning this function will successfully compile and return a `Square` even though we specifically expect a `Circle` as a return type. 29 | 30 | ```typescript 31 | const square: Square = { name: 'square of length 10', length: 10 }; 32 | const squareDisguisedAsCircle = disguiseAsCircle(square); 33 | 34 | console.log(squareDisguisedAsCircle); 35 | 36 | // > { "name": "square of length 10", "length": 10 } 37 | ``` 38 | 39 | This is not restricted to arrays, take for example a generic interface. This would still compile: 40 | 41 | ```typescript 42 | interface Reference { 43 | value?: T; 44 | } 45 | 46 | function disguiseAsCircle(square: Square): Circle|undefined { 47 | const circleRef: Reference = {}; 48 | const shapeRef: Reference = circleRef; // Problematic 49 | 50 | shapeRef.value = square; 51 | 52 | return circleRef.value; 53 | } 54 | ``` 55 | -------------------------------------------------------------------------------- /ts/reference-covariance/example.ts: -------------------------------------------------------------------------------- 1 | interface Shape { 2 | name: string; 3 | } 4 | 5 | interface Square extends Shape { 6 | length: number; 7 | } 8 | 9 | interface Circle extends Shape { 10 | radius: number; 11 | } 12 | 13 | function disguiseAsCircle(square: Square): Circle { 14 | const circles: Circle[] = []; 15 | const shapes: Shape[] = circles; // Problematic 16 | 17 | shapes.push(square); 18 | 19 | return circles[0]; 20 | } 21 | 22 | 23 | const square: Square = { name: 'square of length 10', length: 10 }; 24 | const squareDisguisedAsCircle = disguiseAsCircle(square); 25 | 26 | console.log(squareDisguisedAsCircle); 27 | 28 | // > { "name": "square of length 10", "length": 10 } 29 | --------------------------------------------------------------------------------