32 Reasons WHY TS IS BETTER Than Go
Вставка
- Опубліковано 28 вер 2024
- Recorded live on twitch, GET IN
/ theprimeagen
You Like Melkey? GIVE HIM A FOLLOW:
/ melkey
/ @melkeydev
Reviewed Reddit post: / anyone_who_tried_to_li...
By: simple_exporer1 | / simple_explorer1
MY MAIN YT CHANNEL: Has well edited engineering videos
/ theprimeagen
Discord
/ discord
Have something for me to read or react to?: / theprimeagenreact
Hey I am sponsored by Turso, an edge database. I think they are pretty neet. Give them a try for free and if you want you can get a decent amount off (the free tier is the best (better than planetscale or any other))
turso.tech/dee... - Наука та технологія
As so often is the case... The guy in the article tries to write TS using Go. If you want to write TS then use TS. Go is great but not if you treat it like TS.
No immutable variables tho. Tf is up with that
@@ryangamv8 yeah sometimes it would be nice. Constants can cover some of those cases but since they're compile time constants they're not the same unfortunately. It would be nice if they introduced that eventually.
Or any other language, I’m tired of people trying to write Java, C++, JS and other stuff in Go. You look at the code, you see the mess, you think “this looks like this person is trying to write in X language”, and than you go check their background and as it turns out, in their previously job they were writing in X language, you can see it right through it.
@@gilbertovampre9494how Go style different from x language?
@@cranberry888 like, when I see "IRepository", "IClient", etc. or getters and setter for everything and everywhere, named "GetThis", "GetThat", they all came from some OOP language like C# or Java. When I see too much reflection or Generics, they probably came from JS, and so on. I might be wrong, I'm just stating about my own experience, and to this day it was never wrong. It's just minor things that give up their background.
As a Rust programmer, the one thing I dont like in Go's error handling is that you can still technically access the value even if there's an error. You get both the error and the value at the same time returned. In Rust you're guaranteed to only get one or the other thanks to ADTs.
It has been repeated 1000 times. Yet as a go programmer, it doesn't bother me, or cause any bugs. I had to get used to it, sure. But after that it's just smooth sailing.
@@krux02 The issue is that it CAN cause bugs. Not to mention, not every function follows the rule of "if it returns an error, the value should be regarded as invalid". And it's not always clear without reading through all of a function's documentation (io.Reader is a perfect example of this). I've been programming primarily in Go for the past 7 years, and can say with confidence that Rust's way is just plain better.
"as a ___" why do people feel the need to state they are writing as something or other? Is it just readit brain?
@@johnyewtube2286it helps the reader gain perspective about where someone might be coming from.
@@johnyewtube2286because it informs you of why they're saying the thing they're saying?
You might have had a point if you saw "as a hair dresser, I think roundabouts are better than lights"
25. A slice is a portion of an array (which may be the whole array if you do x := [5]int{0, 1, 2, 3, 4}; s := x[:]). They work on top of the array you slice. Mutating a slice element (s[0] = 5), will mutate its backing array (x[0] is also 5 now), and those changes will be reflected on any slice from that same array, if you need a copy, you have to use the builtin copy functiom or make a copy manually (for loop and copy to new slice with a different backing array, for example).
Slices are passed as copy, but it doesn't mean you will get a full copy of its elements, you just get a copy of the slice data structure, which is a struct { length, capacity int, data uintptr }, but data is still a pointer, so if you do s[0] = 0 inside a function, it will mutate its backing array. If you pass s to a function, and do s = s[1:], s is now [1 2 3 4] in the function scope, but it will still be [0 1 2 3 4] outside the function.
I actually find it quite easy to understand, and I am pretty stupid, its like any user defined data structure would behave when it is used as argument in Go. There are some other rules like appending to a slice with not enough capacity, but once you know the rules about slices, it is easy and makes sense.
P.S. having unmutable data structures in Go is not possible, you can make copies, but mutating it is not a compile error.
It took me a very long time to understand slice and yet I don't think I have fully captured it. Currently I see it as a view of the underlying array, when we cut a slice, we just create a new view of the same array, so manipulate one of the views will affect all the other views. It's so silly that Go designed it this way, make something very simple so hard to understand.
@@a-yon_n I think sometimes we overcomplicate some concepts, in simple words, arrays are static arrays and slices are dynamic arrays.
You have to be careful when appending to a slice within a function, if you want to observe that change in the caller function. If the capacity is exceeded, the slice inside the function will start pointing to a new array which is a copy of the old array but with double capacity. The original array remains unchanged and at the same old address, at which your outside slice points to. Thus you won't see the change.
In that case pass the pointer-to-slice, instead of the slice. It points to the address of the slice header, not of the array, and so any change within a function works on the slice residing in the caller.
Also consider this: you pass the instantiated but empty slice 'a' to a function which should populate it, and suppose your function makes slice 'b' with some elements, or calls another function whose return value is equivalent to slice b. And you just assign: a = b or a = g(...) (function g returns a slice).
Now, slice 'a' inside the function (and, of course, inside the function that slice can have a different name i.e. the name of the parameter, but we tend to use the same names for arguments and parameters) - points to the underlying array of b, but the slice a in the caller still points to the same empty array as before.
In that case, also, pass the pointer-to-slice to the function parameter and assign like this: *a = b, since by 'a' now you call a pointer variable, so you deference it to get to the slice header in the caller. And now slice in the caller points to the underlying array of b. You didn't have to copy each element in a loop. Also use *a = append(*a, c...) when appending inside the function, slice 'c' to slice a in the caller and you are not absolutely sure that you won't exceed capacity at runtime.
I think I just like Go now. The whole try-catch blocks in TS is a nightmare, and I'm fairly sure it nudges you towards just "YOLO: Hope it doesn't fail".
I love getting a new perspective on things, although I don't think you are always right about everything. I feel like it takes great courage to stand in front of this many people and state you opinion and I admire you for it. - No matter if it is factual or aligns with my views. Thank you. Keep it up :)
Just a quick PSA, accessing JS object fields by variable is a security vulnerability if that field can be defined by user input.
by the same token, indexing a list with a variable is also a vulnerability, isn't it?
@@aimanbasem if there is no bounds checking, which i believe there is in JS. But in C there is no bounds checking so if an array can be indexed arbitrarily (or even just access OOB), there are ways for a user to take control of the program and start trying to execute OS commands with whatever access the program was given. This is also why you should assign specific users/roles to processes and never run them as root unless absolutely needed
Functional overloading exists in alot of langs, and its quite useful. Im pretty sure c++, java, a good handful of functional langs, etc
Its good if you have some optimisation that uses a lookup table or premade collection that needs to be searched, if its created before, it can be passed in, or the function does it itself
The only thing function overloading is good at is making the code more confusing, change my mind!
@@tokiomutex4148 with proper naming, I'd disagree. The method "String.toString()" in C# has many overloaded methods that still does exactly what the function means, the algorithm just is different depending on what data type is converted into a string.
Building multiple constructors is also great, but you could argue we could simply create an empty constructor first then chain set properties to accomplish the same goal (such as " New Object().property1 = value
.property2 = otherValue
.property3 = value3; )
I find that function overloading only makes sense in OOP though. In Go, it would feel weird, kind of out of place
@@tokiomutex4148and they cause name mangling...
Function overloading can be good for optional arguments or handling of same functionality for different types
@@edoga-hf1dp Until someone modifies the code and a different definition of your function ends up called, good luck debugging it!
function overloading is a good alternative to OOP and object.verb(subject) notation. overloaded function call is essentially pattern matching on a tuple of types. so it's a good option to have
For number 7 and your example, i would use json instead of map. In number 14, we could utilize struct and pointer to construct a optional value parameter or u can say dto
for point 7, I think it's better in GoLang, as they can pass variables that don't exist in your struct.
What I do in that scenario is create a function, loop through the filters and have a swith case statement that handles the supported properties, and a default. It's a bit more work, but it ensures the integrity of the system is not compromised.
@ThePrimeTimeagen, your point is very valid! If function signature in ts accepts a union type of arrays, it will be a runtime error in js!
So, before pushing something into an string[] array, you should check for argument type to be a string with typeguard: ```ts if (typeof arg !== 'string') return; ```
#26 - slices are NOT messy if you first read the language specifications. A slice holds three numbers: memory address, len and cap. The first one is a pointer to an allocated array. That address is the VALUE of the slice. In assignments to other slices/new slices, as any other value, it is copied, thus the memory address of the underlying array is copied. Therefore, all slices derived from one slice point to the same array UNTIL YOU CHANGE their value.
Example. a := []int{1,2,3}. There is an array holding 1,2 3 and some more space where you can append new elements. Now, b:= a. b points to the same array. c:= b[1:] . c points to the same array but starts at index 1 of b, so it represents 2, 3 elements. e :=c[1:]. e represents element 3, because it starts from index 1 of c. You can't retrieve the leading elements, i.e 1, 2 (as you can with tailing elements), but you still have slice a if you need them.
But let's have d:= []int{1,3,5.7}. This points to another array. Now lets assign: c = d[:len(d)-1]. Since value of d (memory address of the second array) is assigned to the value of c, c NOW DOESN'T POINT TO THE FIRST ARRAY but points to the second array and represents numbers1,3,5 , but last element is sliced away by slice operation. You can retrieve it: c= c[: len[d)].
Just as with x:=3 , x changes its value when you assign x=7. A slice behaves the same way, only its value is some memory address.
Now, his objection was (probably) this: When you pass a slice to a function as value (it is still reference to an array, and thus light-weight), and use the builtin append function to append the receiver var inside the function, if the slice capacity is exceeded, Go runtime will copy the ARRAY to a new location but with a double size (new cap = 2*(old cap + num of appended elements).Of course, the value of slice inside the function changes: it now points to this new array. The slice in the caller still points to the old array and thus that array is not appended beyond its capacity.
In order to obtain the wanted result in this case, don't pass a slice to the function, pass a pointer to slice. The receiving parameter points to the slice header on the stack of the caller function, and whatever you do with it affects that slice header, and that one, as any slice, is always pointing to its underlying array on the heap, no matter if that one was copied to another place in memory (at least until you change the value of the slice).
OR, return the slice from the function, and you don't have to use pointer to it.
But if your function parameter ap received &a (a being some slice from the caller), and then the function did this: *ap = c (c being some other slice pointing to a different array), then you changed the value of a from within the function, and now a points to that different array. This is perfect for populating a slice from some function that spits out the slice, all at once, and you don't have to copy element by element, UNLESS you want, for some reason, to have exactly the old array repopulated and you care about the address. In that case you wouldn't use *ap = c, but builtin copy function: copy(*ap, c). The copy function copies all elements to the underlying array of a, and the value of a (the address of that array) is not changed. In fact, in this case, you can pass a (not pointer to it) and do: copy(a, c) in the function, and you don't have to return a.
I will say Omit type is nice for DTOs. Like I can say this DTO is the whole object except the DB generated ID, and now any time I modify the object my DTO mirrors it.
Oh, _that's_ what that is? Just a slice of a struct? Why isn't that a thing?
@@mage3690 the only thing that is a little frustrating is the type hint will always just show Omit instead of the whole object, but it really is a really convenient feature otherwise. And it makes refactoring things easier. When you update a class, you don't have to update all the partial DTOs and you don't even have to modify much code unless the code specifically access a field that was changed.
Why not use struct embedding?
17:50 - I don't think that's really TS unions fault but more fault on the part of how TS treats mutations. I think TS type system is very "math-y" with how sets relate to each other and that's the artifact of that - Array is indeed a subset of Array, there's nothing inherently wrong with TS "thinking" that.
@llIlllIIlIIlllIlllIl fair point, yeah. Cause then the operations available on set members also are a part of the set. So a mutable Array set also includes an operation .push(string). And then you have that .push(string) is not a subset of .push(string|number) but vice versa so neither Array set is a subset of each other.
Typia allows for runtime validaton of TS objects using TS types/interfaces. Write an interface. Validate some incoming json against it. Beautiful.
All the other runtime validation libraries have you operate kind of backwards to that.
Your Zod competitor already exists, it's called Typia. It does types => validation, types => json schema, types => protobuff schemas.
io-ts existed before both and did the same.
To me, a telling sign that someone is inexperienced in programming (or that they're using the wrong tool for the job) is that they complain about error handling being cumbersome. It tells me that they haven't been bit in the ass enough times to realize that handling errors is orders of magnitude less painful than dealing with run time bugs and crashes. Go's approach to errors might not be elegant, but try/catch is 100 times worse, because it gives programmers a responsibility they're usually are too lazy to handle.
I'd rather the program fail than clobber itself or anything else
It turns out that it's easy to mistakingly ignore or overwrite errors in golang. I've worked on large golang code bases, and I've seen this issue many times. It's a mediocre and badly designed language at the end of the day.
I still feel like errors should be sum types, like in Rust with Result or Either with Haskell.
That being said, I'm 100% on having error handling as values and that the compiler forces you to know that something can error.
Why not use Checked Exceptions like in Java. If they made it so that you had to either eat the exception or declare that your function also throws it, then the compiler can always inform you if you missed handling any error.
@@refusalspam Yep, many languages have this. Rust, for example.
Errors by values is definitively the best. Rust does it better leveraging sum types to force engineers to handle the error to unwrap the value. I think what is also missing in Go is a sort of propagating the error up the callstack feature, like Rust's question mark operator. That allows the example of one mechanism to catch all and handle the same way.
Also, yeah dead locking yourself could be argued as being a skill issue, but so could every C++ footgun and we still dunk on C++ for it... as an industry we need to push for languages with fewer footguns.
Valid points!
If not every, an overwhelming majority(like, 90% or more) of language disadvantages could be compensated with skill... so technically they are all skill issues.
@14:00 An uncaught exception is an abort()/assert(). It's pretty trivial to do that around the code if that is in fact the behavior you want.
what you really want is to explicitly handle all errors that are the result of a bad input, and a 4xx in all such cases. a try/catch or a panic/recover are only to handle something that got missed, that is presumed to be a coding bug as 5xx. the try/catch or panic/recover is a fault barrier. each thread should have a fault barrier at the top if it doesn't continue with a new session; or in the top-most loop as the fault barrier. fault barriers log things that got missed. reaching the fault barrier IS a bug. you can't always recover for real though. "out of filehandles", and your process can't safely proceed with anything.
the main thing that IS wrong with Go error handling: returning 'error' isn't specific enough. You should return the most specific type you can that has the error interface. ie: ex FileNotFound ... ex.FileName. If you are in the middle of doing a task, then that task's error should get returned. If it was caused by an error inside of that task, then that error should be used to construct that task's error. That way, you get context about what you were DOING when it errored out. It is pretty important that the task know what HTTP error code to suggest. You should return 4XX errors on bad input, and 5XX on errors deemed to be bugs to be fixed if your own code. This is why it's a bug to hit the top-most fault-barriers. It means that you missed something that was bad input, or something that could have gone wrong internally. You need to update your code so that all errors can tell you definitively whether fault lies outside the process, or inside the process.
there is definitely overloading in JS/TS. this helps with variations of the function
25:30 * is a dangerously leaky abstraction. Option and ? are less leaky.
Constructor overloading != function overloading.
And function overloading can be generics via syntax sugar.
In go, you can recover from panics and return a 500, or treat it basically like try/catch. It's kinda wild but it's useful to use at the root of your request handler in a http server for instance.
I really like when you take a really small snippet to explain your point. Would love to see more.
I've had to write some TypeScript this week despite being someone who usually only does Go.
TypeScript was pleasant BUT WTF is the difference between cjs, node, esm2014,esm2020 and on and on. The tooling also sucks. `tsc` for some reason doesn't handle imports correctly and I had to use esbuild to bundle stuff then use tsc just for the d.ts type declarations.
I could probably spend a month learning how to use it "properly" but why must it be so painful?
I tried bun. It was great except for the fact that the cryptography implementation is incomplete and thus I can't use `bun build`. It's meant to be a simple npm library. It shouldn't take this much effort.
true. the ecosystem sucks. you can give Deno a shot, my favorite so far.
Welcome to the Javascript ecosystem.
The bun developers are VERY helpful. They solved my issues with their crypto implementation in an hour
Almost everything has to be convoluted in javascript. Almost everything.
It’s not much effort just ppl trying to use stuff without learning anything about it first. Oh JavaScript have versions oh noo so hard 😮😂
People taking about elixir are actually dead on, kinda insane how much it taught me about alot of these areas
Guy must've gotten oat milk instead of soy.
13:00 that's not really true... you can have similar construct as try catch also in go :
func HandlePanic() {
r := recover()
if r != nil {
fmt.Println("RECOVER", r)
}
}
func divide(divisor int, divider int) {
defer HandlePanic()
if divisor < divider {
panic("start is greater than end")
} else {
fmt.Println(divisor / divider)
}
}
Point 9 is invalid since json you can make a new decoder that doesn’t allow unknown fields. Therefor catching your typo.
In practice, with golangci-lint (a huge collection of validation), I feel like Go's 'security' is pretty good, a weakness that people make comparing to Rust. I have a setup using Task (makefile in Go) that runs the whole suite of checks and output good code before commit. I feel the people often compare languages based on the 'out of factory, default' DX that they provide, which I think it's valid but also not practical, when serious project are a work of engineering system with carefully setup pipelines, no one just use the default language toolings itself in real project.
Errors are values but not the desired value so this sounds like a use case for Either. If you use Either and pattern match on the result you will always handle the error assuming unhandled cases are treated as errors.
Ok I have tried and tried to figure out what the actual problem is with the CrappyUnion complaint around 17 minutes in. Can someone please enlighten me? IMO it does precisely what I would expect. The parameter of the function takes in an array of type CrappyUnion which means the array can be a mix of strings and numbers or all strings or all numbers. You are guaranteeing nothing other than the fact the array has to be made of items that are either a string or a number.
So when you then pass in “a” to the function, it is an array of all strings…. that satisfies the requirement for the parameter type of CrappyUnion[]… so what is the problem?
Can it be a little weird working with an array like that? Yes I agree, but that is more of an implementation/design issue than a TS Type system issue. That example also isn’t really practical IMO having a parameter with an array type of string or number but then all you do is push a number to it. In that simple scenario the issue is the parameter should just be number[] given that is all the function needs. And yes if you added another push of a string in the function you could say now it needs the union type of string | number for the array but I would argue that is just poor design then. First off to me this scenario would be a bit of a code smell as a mutability issue passing that string array in like that to a void function without a return. I think a more typical realistic pattern would be setting a = to return of the function which at that point then it would indeed give you a type error because it would know “a” is a string[] but the return of the function would be a (string | number)[] … OR if just using the specific simple example and it returned the array after just pushing a number, it would return number[] which would obviously also cause an error during compile / transpile.
TLDR: to me that is a bad implementation example along with the fact mutating an array with a void function is an anti pattern in my opinion 🤷🏻♂️
This is kind of like the parseInt() example later in the video where yes you can create some weird scenarios to prove a point but it shouldn’t be used as a qualifier for whether a language feature is good or not when in reality it is more of a quark or idiosyncrasy than a bug or bad feature.
But I would love it if someone enlightened me if I am just wrong or missing something?! 🤷🏻♂️
Typed this original comment on my phone yesterday which is why no real code examples and potentially was a bit confusing, but ultimately here is my point... this is the way I would write this code in the first place (assuming it was a legit use case / example) given the function will be mutating the "a" array. You will then get a TS Type error:
```ts
type CrappyUnion = string | number;
let a: string[] = [];
function addToCrappyUnion(b: CrappyUnion[]) {
b.push(123);
return b;
}
a = addToCrappyUnion(a);
// TS Error: Type 'CrappyUnion[]' is not assignable to type 'string[]'
```
OP: "Why can't you be like typescript?"
Go: "I'm sorry, dad"
#5 - "x implements Method1 and Method2" sounds like the repetition of obvious to me. I guess some confusion might arise when assigning a var to an interface (or putting it as an argument to a function whose parameter is an interface, which is the same as assignment). But then, the compiler will tell you if you made a mistake and why.
In the following example it is easy to see that y implements only addOne() method and thus implements both myInt and myFloat types, because they both have that method. On the other hand, x must implement two methods (more restrictive), so it only implements myInt, which is the one that has both of these methods.
Thus, if I try to assign: x = &f, I'll get the following clear response from the compiler: "cannot use &f (value of type *myFloat) as incdec value in assignment: *myFloat does not implement incdec (missing method subtractOne)", although I would phrase it: "myFloat is not implemented by incdec".
BTW, why would you have to go over declarations if you work in IDE? Just type . or . and you'll get the list of methods.
package main
import "fmt"
type (
incdec interface {
addOne()
subtractOne()
}
inc interface {
addOne()
}
)
type (
myInt int
myFloat float32
)
func (n *myInt) addOne() { *n++ }
func (n *myFloat) addOne() { *n++ }
func (n *myInt) subtractOne() { *n-- }
func main() {
var (
n myInt
f myFloat = 1.2
x incdec
y inc
)
x = &n
x.addOne()
a := n
x = &a
x.subtractOne()
y = &f
y.addOne()
y = &n
y.addOne()
fmt.Println(a, n, f)
}
Output:
0 2 2.2
In the next example, there are slices types: Ints, Floats and Strings. All three have method App attached to them, so they all are implemented by AnyAppender interface (only App method). Only Ints and Floats also have method Inc, and so the interface NumAppender (App and Inc methods) implements those two types but not Strings. See go.dev/play/p/J9FEKP6NvJF
JSON thingy hint: use yaml string tags `yaml:",inline"` and you never miss a object key :'D and you can use yaml tags for json xD (I kinda find this silly xD but works)
All we need is seperate AOT to machine code compiled engine for TS. Someone please invent this shit!
Very much on the go team. Just wanted to point out that function overloading is absolutely a thing in TypeScript and I'm fairly certain it actually came from JavaScript. Been writing odin recently, and their approach to overloading has been super convenient and I kind of wish go had it
Overloading is completely unnecessary. Just create two different functions.
#define true false 😂
This was basically programming slander in all directions for like an hour and I love it
Agree, there are pros and cons for both languages. You can do functional programming in TS and vice versa in GO.
Go is bad *BUT* receiver methods are great because (1) avoid need for `this` and (2) methods don't need to be bound to receiver, they are pre-bound
yes
You guys completely missed the point about difference in exceptions in Go and TypeScript.
Errors in Go do not stop program execution, which is completely crazy. That is THE difference.
In any normal/sane language errors stop the execution. In TypeScript, as an example of such language, you put try/catch when you have "expected exception" and want to continue execution, which is fairly rare case (>5%?). While in Go, in order to stop execution, which is 95+% situations, you have to handle it manually.
And people who are writing try/catch blocks all over the place... they are doing it wrong way.
Simply disagree
Errors are values
@@ThePrimeTimeagen And when your function returns int 0, because of an error, you code should simply continue running and threat your 0 as result from calculation?
@@nenadvicentic Errors are not some random thing, they have causes which you, as the code author, must be able to anticipate and handle in advance. Errors in calculation are not acceptable. You'll discover bugs by testing, bugs are not errors. Aborting is not graceful. Warning the client side and leaving them options to change parameters or exit, is.
@@Nenad_bZmaj Half of your reply is stating exactly what I am saying and other half is contradicting it.
Errors can be expected (once you can anticipate) and unexpected (e.g. random hardware or network failure). Do you think your code should "continue on unexpected error" by default? I think it should throw by default. And BUBBLE. And if you want to provide user with options after error, you do try/catch on top, user interaction level. Not on every intermediate level, just to re-throw it up. That is anti-pattern and that is what Primeagen constantly throws in as an argument against try/catch.
Regarding calculation, parts of data you calculate on, or pre-calculated partial, come from different data sources or a different server. What if IO fails while reading the subset of the data? You catch that with unit-tests? You "continue by default" defaulting, let's say `int` to default 0? This is an argument to say that in 90%+ of use-cases we want code/runtime to throw on unexpected error, not to continue. Therefore, language/runtime defaults should cover 90% use-case, not 10% use-case.
@@nenadvicentic To be honest, I have zero experience in any language other than Go, but I very much like the way I can handle errors in Go, because I feel I am in full control and there is no magic or black box that I have to rely upon.
In your example, one should always anticipate reading failure. Each os or io function returns errors as values and they should be called that way. Your function that calls reading a file should not abort the whole application, but back propagate the error to the caller. It is up to the overall design at which level the caller decides what to do with that error, say, request data to be sent again before proceeding with calculations.
You know what data structure you expect and the nature of the data, so you can also check the data integrity (in case reading was successful) and your function that does this check should return error as value.
But even in the case you mentioned, unsuccessful read will not generate zeros in place of missing data - there will be no bytes at that place (you'll also get the returned number of bytes read). So, if you decide to not handle that error, in the next step, when accessing that missing spot, you'll get panic anyway, so the program does abort. I just don't like that, so I'd handle panic as well at some higher caller level to be able to finish gracefully.
To be more precise: you have a slice of int32 and it holds N elements, so you expect to read 4*N bytes. But the read was unsuccessful and only 12 bytes were read. What you were saying is that only first 3 ints were populated but the rest remain zeros and the program continues. But that is not true. You have to have a function that converts bytes to int32 and that function ranges through the slice of int32. When it comes to index 3 it wants to access the byte slice at offset: 3*4 , i.e. the 13th byte. But there is no 13th byte in that slice, so program panics with "out of range" if you chose to ignore the reading error.
A good use case for [8] that was explained to me was say, creating a new entry in something.
For example a user obj/struct that has fields for name, email address, phone number and a unique ID. When adding a new user, you might not have an ID yet, that could be automatically assigned from the DB, so your create user gets filled out as a (not the right syntax, but whatever).
So this way when the User type gets more fields added to it in the future, your adder still needs to be modified to support that because it's linked through.
Whereas having two versions, effectively UserWithID and UserWithoutID means there's no linking and you have to remember to add/remove fields in both whenever it changes.
I guess the alternative is having inheritance maybe? But fuck that.
Yes, this is what I'm missing as a Rust developer. I really enjoyed composing the types in Typescript even though it was quite hard at times. But in Rust I need a variation of similar types (think of builder pattern for example) and it's a hassle!
In Go, you can embed struct as a struct field:
type(
UserData struct {
ID string
somethinElse int
}
User struct {
name string
email string
phone string
otherData UserData
}
)
//In func :
var (
usrDatas []UserData
users []User
)
//For index i:
user, usrData := new(User), new(UserData)
user.name =
usrData.ID =
user.otherData = usrData
users = append(users, *user)
usrDatas = append(usrDatas, *usrData)
//
//updating:
users[i].otherData = usrDatas[i]
You access users[i]'s ID by: users[i].otherData.ID
36:38 isn't that a polymorphism?
Hey! I'll have you know I like living under my stupid rock, thank you very much
0:01 @ThePrimeTimeagen, About that stupid rock, there's no "under" or "over" in space.
1 reason why a balance article is better than any article entitled "N reasons why X is better than Y".
The TS guy has not been thru hell -- he thought Satan is throwing him a party there.
The only inheritance you may desire is that which from your great grandpa: The clock. But if you love your great grandma, you would rather borrow his books in your composition :D
hearing how go handles errors and i realize thats exactly what i use rust for i just use match to then set a variable to a err json output XD
What watch is Melkey wearing?
With reference to point 8 can't u just make a var struct (temporary struct) that contains the filtered fields and paase that around
What's not to like about Go? The colon-equals? Fair enough, I don't like that either. That's a whole extra two keypresses every time I set a variable.
No ternary operators is a _good_ thing. If you're stacking ifs that deep, just use an if statement to set a variable that you can use as an rvalue in the next if. My first officially taught programming language in tech school was ladder logic, a dataflow language. I was always trying to stuff more logic into every box (or rung, because ladder logic is primarily written in rungs). I'd ask my prof how to stuff more logic onto a rung, he'd come over, stare at my colossal mess with an expression of horror and confusion, and tell me "if it's that complicated, set a variable." Naturally I didn't want to, stuffing a procedural mindset into a dataflow language is an exercise in futility, but it taught me to seriously think about why I needed that much logic, and to make my code more readable. If there's one language that will make the most trivial code unreadable if you're trying to do it wrong, it's ladder logic.
Nah, you should research the difference between expressions and statements and try to better understand how expression oriented programming is better.
@@justsomerandomguyman just because you want to use functional programming for concurrency doesn't mean you can't use imperative for creating memory. Matter of fact, I think programming in general would go so much better if everyone just did that: imperatively create all the memory you need, then functionally edit that memory as inputs come in. Let each tool do the thing it's good at without being dogmatic.
Also, the idea that somehow an extra colon in my variable initialization makes a language better is just goofy to me. I guarantee you the compiler could trivially know where that colon should go even if I never specified it. Unless you're shadowing a variable, there's no point. And if you are, well, IMHO that's bad practice anyways: the language should just assume scopes inherit everything or nothing from the scope above them and go with that. In this one area, be more like PHP. Assume nothing, at least for function scopes, and force programmers to declare side effects.
After watching this video I'm convinced that Prime is the reincarnation of the original Menace.
In Go, the equivalent of “implements XYZ” is “var _ XYZ = (*MyStruct)(nil)”. A bit clunky, but does just about the same thing as an “implements”.
Interfaces are intrinsic. You're not supposed to write implements in the code, Go is supposed to find that out itself.
The pattern I described can be found in Go’s FAQ. Sure, it’s not necessary to have such a statement, but it can’t be that unidiomatic if it’s in the official documentation. I find that it can be useful in certain scenarios, providing a helpful guard rail when I refactor interfaces.
11:29 did he just V> instead of >>? what a war crime, inefficient, and blazingly slow keybinding
make a video sir about how to handle errors
isnt your zod alternative just ajv?
17:33 The problem is that typescript by default does not distribute the union members for array declarations. So `CrappyUnion[]` turns into `(string | number)[]` and *not* `string[] | number[]`. You could use a custom type to create a better array type though:
type BetterArray = T extends T ? T[] : T[];
type CrappyUnion = string | number;
function addToCrappyUnion(arr: BetterArray) {
arr.push(69); // Compilation error
}
This is usually not what you mean though - you'd expect an array of number | undefined to be (number | undefined)[], rather than number[] | undefined[].
The issue is deeper than this, and comes from TS having to drop guarantees to wrangle JS into a usable type system (trying to stay typesafe while dealing with shared references, mutability, function parameter invariance + array covariance, etc)
7, 8, 9, 14 and 15 is basically only 1 Point and YES, it sucks in Go to parse JSON. If the field can be string or number, you are already screwed in Go. Now you have to check the error and if the error is for this attribute which u defined default as number, but it's content was suddenly "50%" which is a string, you can only set a new struct into this place which holds a number or a string for this value.. so it's significantly easier, to just leave it as Interface and validate it on using it, which is also clearly the more dirty way to handle it.
But I mean, what's the point now? "The JavaScript Object Notation works best in JavaScript" ... good combined, Watson
damm this is class of titan teach... , union type hit TS a lot
I get annoyed by try/catch error handling because in a lot of cases there's not enough bread crumbs to figure out where the exception is thrown... unless you print out a stack trace.... but a stack trace isn't ideal for all types of errors. I'm guilty of writing error handlers that check for return codes even in languages where exception handling is more of a standard. I like Go's choice here
Typescript is good for small projects. But after working on a larger scale project. HELLLL NAAHHH, never again...
i challange you with try/catch/finally decorator ;)
C# outclasses both of the discussed options
Ok let's just agree that he never touched go beyond "go playground" and move on cause in our line of work you have to produce Examples and evidences that convince me that Typescript is better than GO...sadly the more i read his points the more he shows his skill issues ...don't get me wrong Go has problems but it's 1 million times better handled than typescript or js ever could
In your experience, which language has the best typing?
We are not finished comparing JS to TS yet and the guy wants to compare TS to Go ! Go is the best, simply !
to be fair to javascript, parseInt() isn't trying to do the same thing as parseFloat(), and there are clear rules for how parseInt() is going to work out a mess (which it should try to do instead of failing since it's on client side)
Union types are the main thing I wish Go had.
He definitely knows everything
How do you ship a TypeScript binary?
I don't know who other guy is but he didn't have his own judgements. Every time Prime said something, his brain turn off and start agreed everything. Lack of confidence. LOL
Half of these "reasons" are somebody complaining that a strongly static typed compiled language does not let you do "dynamic" things. Well, duh.
Go is one of the most imperative languages of them all. What the hell this person is talking about when he uses the term "functional language"? He obviously is a beginner not only in Go but in programming in general too.
what about t3 channel?
Never really liked try-catch / traditional exception handling since I first learned about it in college. Errors as values ftw.
Go's implementation of errors-as-values is poor compared to Options/Maybes and Results/Eithers from other languages.
Comparing crap like ts which was created with only one purpose to add types to js vs a modern language which was specifically designed for network stuff and perfectly fits for writing backend servers, infrastructure support etc etc. k8s is written in golang, docker is written in go and does it’s job perfectly fine. Top engineers in Google and other companies chosen Go because they know how powerful it is, and then this dude… 😂
Unlike Go, TS authors had no luxury to start from scratch and improve everything in JS. Golang is objectively a shitty language - considering the amount of freedom and budget its creators had (and screwed: generics, error handling, sum types, etc).
I don't find "xyz is written in Go and works just fine" to be a compelling argument when compared to TypeScript. TypeScript has been used to make many things too!
I googled rule 34 when Primeagen said it (1:35)... thinking that Primeagen was referring to the cellular automaton rule 30... big rip there... (nsfw btw)
@ThePrimeTimeagen i kinda love ho many people are using go as compare to their favorite language and want to show off how theirs is "better". It kinda seems like they are all scared of go and thatfor need to proof that their favorite language is still worth using ;D
Lol num 8. You've never needed to pull a class model from DB and then remove 6 fields from the 20 columns legacy model 🫠 `interface presentedModel extends Omit
I've written a lot of code in both. I like the simplicity of Go, for sure. Fewer ways to shoot yourself in the foot...kind of. There are just fewer choices to make in Go, which can be a good thing.
TypeScript is more like Scala. It can support about any paradigm you'd like to use. There are 10 ways of doing anything. You can easily over engineer a problem by adding layers of generic inheritance or something like that. Then, there's the layers of configuration. Not all TypeScript is the same. Max strict TypeScript, in practice, ends up looking much different than minimally strict TypeScript.
The point about no union/sum types is really important. Not everyone uses these features, but once you start using patterns like tagged unions, everything starts to look like a tagged union problem. The simplest example is an Option type, which you can't make yourself in Go today.
Option types are possible in Go, using Generics. They're just not as nice to work with, compared to a language like Rust which has very powerful pattern matching. Go Generics, at least in their current state, are very basic/underpowered. You could argue that that's perfect for Go, but I'd have to disagree. If you're going to add a feature like Generics, you can't half-ass it.
type Option[T any] struct {
Some T
Valid bool
}
The difference is that Scala is half decent.
@@Treslahey thats a creative way to implement an optional type. but having it as a struct vs a enum just feels wrong like u mentioned. since an optional can only have 2 states, it makes more sense to abstract it as an enum, which u cant do in go unfortunately since enums in go are just untyped uints.
@@anon-fz2bo I wholeheartedly agree. Go's lack of a truly generic enum type is one of its sore spots.
The whole thing about optional type is you staying inside the Maybe/Option monad and delegating the error handling to the monadic structure. No need for a enum or some shit like that, thats just a neat sprinkle on top of your chocolate milk, but the chocolate milk itself doesnt need it to be delicious.
The main problem of this article is that the author just wants to write Typescript code using Go. This is the stupid idea that doesn't work with any programming language.
You just need to learn how to write idiomatic Go and most of those "problems" won't even appear in your project.
Doesnt it give you inspiration. Anyone (with the right connections) can become a journalist these days
makes sense
for real, the nil pointer arguement is dumb, u just have to remember to check if its nil. which if u write c/c++ you should already be comfortable with.
@@anon-fz2bo I think he meant that in Go interface that contains nil is not nil. For example:
var i *int // initializes to nil
var something interface{} = i
fmt.Println(something == nil) // prints false
That's why it is very bad to write your own error type that implements error interface. Because it won't be nil when you return it
I've worked with so many of these people when a company I used to work at adopted go. Not only were people not writing idiomatic go, they couldn't even be consistent about what language they _were_ writing via go - java, C++, python, and others. It was a nightmare for the few of us who actually had learned go properly and weren't just angry at the language over it not being a different language.
Go's error handling is amazing because it keeps it as part of the control flow. Exceptions are the bane of new and "mature" codebases alike, especially within a 20 mile radius of any junior engineer.
i hate exception handling with try catch
Go error handling is better than ts for sure, but most of the time i just wish they do something like rust that wrap error in Result enum, or at least have some syntactic sugar like “?” operator in, again, rust
@@ThePrimeTimeagen thoughts on elixir/erlang error handling?
@@ThePrimeTimeagen except in zig
The biggest gap in Go error handling imo is the lack of a “must use” annotation like Rust has. If a function returns only an error, it can be easy to accidentally forget to check for error.
I'm wondering how Melkey could have a Go course on Frontend Masters when he doesn't know how slices work, it's basic stuff
Watched a few of Melkey’s videos recently, seemed to have a fairly reasonable opinion on most things… where is he on the Theo-Prime scale?
An absolute beauty. Best of both worlds
He seems like a junior in comparison. I don't know about his qualification but that's the impression I get.
Can you elaborate on the Theo-Prime scale?
Theo gg is a startup hacker developer productivity focused get stuff out the door
Prime is a high scale optimal code, ergonomics freak, elite coder
Actaully a good comparison
arrays are values, slices are pointers to an array. append returns a copy of a pointer to the same array; BUT if underlying array grows because of append, it will create a new underlying array and return new pointer to new array.
Holy shit, 1 hour. Lets go
yeah let's go. what your name?
Come on… try/catch is the worst, but golang error handling is still not “beautiful”. Please correct me if I’m wrong, but if you ignored the error on line 49 (12:24), go doesn’t catch that without a linter, right? And about readability, there are five statements there totaling 18 lines, that kind of error handling completely throws code overview out the window. In a world where we have algebraic data types such as Result and Maybe/Option, the solution in golang is at best acceptable, but absolutely not beautiful…
Like everything in go, it's not beautiful, it's not the most readable, but it's robust and functional.
I much prefer the style of rust over go and I'm kinda undecided whether I like Java's or go's error handling better (at least, both are explicit).
But I think we can all agree, that error handling in JS/TS is a pain in the ass.
In go, while you can ignore an error, you know that you are doing it (because you need to use a placeholder for the error result explicitly)
Golang's value errors would be nicer or they instead were some result return type with monadic operations on it
Agree but you get some interesting freedom with what it is right now 🤔
@@Entropy67you don't have to use the monadic things everywhere
I don't want to resort to whataboutism but C has the same thing, if I recall correctly, and yet no one bats an eye
I think you have to differentiate to errors in your code to errors from user errors, like a fetch failing. If my code doesn't work, I want it to throw. If a fetch returns a 400 because the user forgot to input their age, I want Go style errors.
Exactly that, that's why I love Rusts error handling.
As someone who actually has a couple of years experience with Go, there is NO WAY that this blogger does. They're not even aware of a lot of the basic functionality and idioms. Yikes that they felt confident enough to write an article and share it online 😂
TS is just JS, just fancier.
The real question, what is the best type system? TS, Go, Rust, Haskell, Kotlin, ...?
we have to write 2x more code to make typescript type safe
This whole article is basically saying "I don't know how to program if I don't have the full set of TS features."
This whole article is basically saying "I like dynamically typed languages"
Not just TS, but the JS frameworks and sundry other packages he must use. Apples != Oranges
For me, less language features means when I have to read your shitty code (don't be mad, all code is shitty) I don't have to spend as long figuring out what the fuck you were doing.
Bro the CaPiTaLIZAtion thing is great. I hate having to write public on every single var to to export it
Actually in go this is how you say you implement an interface:
var _ Interface = (*MyStruct)(nil)
and then it's checked at compile time
I only like Try Catch Finally how it was inplemented in Java. And I hate java! Yet that was a thing they did right. The otherthing java did right is to put a monitor in every object, for easy threadsafety.
If you gonna use ParseInt and Number as example of bad behavior of JS you might as well scream that you don't read documentation instead.
It's on the same level as people that complain that JS follows floating point standards thinking it's odd behavior on JS side. There are a lot of issues with JS ,like Prime showed with Json parse, but saying that the code doesn't follow your intuition is not one of them.
32 best reasons to develop depression
About your "zod competitor" idea. Instead of doing a build step which will codegen validators, you can use the "new Function()" pattern to generate code in runtime.
It’s the same approach that fastify uses to generate fast json serializers / deserializers given json schema definitions.
This idea was already implemented. Look up "Typia"