Hacker Newsnew | past | comments | ask | show | jobs | submit | nemo1618's commentslogin

Your passwords should never be visible on screen anyway: They go straight from a password manager into a censored input field.

Over the course of ~10 years of writing Go, my ratio of "embedding a struct" to "regretting embedding a struct" is nearly 1:1.

I do not embed structs anymore. It is almost always a mistake. I would confidently place it in the "you should be required to import 'unsafe' to use this feature" bin.


Using struct embedding for pure data to implement discriminated unions is fine, better than MarshalJSON() that is lost on a type definition. Using it to save typing, or going crazy with it (I consider embedding two things going crazy) is bad.

I think that using embedding for discriminating unions if a good idea. It would work, but it does not force the user to do the discrimination. I would say that explicit typecasting at the point of discrimination is safer. Without it, nothing prevents you from using one field from one variant of the union, and another from a different variant.

Introduction of proper discriminated unions would be great.


I'm not sure I understand you, or you understand me. I'm saying this is okay:

  type Order struct {
   Type        OrderType
   CommonAttr1 int
   CommonAttr2 string
  }
  
  type OrderTypeA struct {
   Order
   TypeAAttr1 int
   TypeAAttr2 string
  }
  
  type OrderTypeB struct {
   Order
   TypeBAttr1 int
   TypeBAttr2 string
  }
And yes you should convert to OrderTypeA or OrderTypeB at the first opportunity in domain code, and only convert from them at the latest opportunity.

You seem to be under the impression that I'm advocating for something like

  type OrderUnion struct {
   CommonAttr1 int
   CommonAttr2 string
   TypeAAttrs
   TypeBAttrs
  }
That's what I consider going crazy.

> And yes you should convert to OrderTypeA or OrderTypeB at the first opportunity in domain code,

Go can only downcast through interfaces so there's something missing to your approach to unions, isn't there?


The missing something is manually creating the structs, not casting.

>And yes you should convert to OrderTypeA or OrderTypeB at the first opportunity

How would you convert Order to OrderTypeA? You would need some other source to fill TypeAAttr1 and TypeAAttr2 with.


Needing further external information to "convert" from one struct type to another is fairly common and completely normal. One I happen to have encountered in multiple places over the years is normalizing user names. Sometimes there is no mechanical process to normalize user names, such as simply lowercasing them; you may need access to an LDAP server to get a canonical name/account, or access to information about local email munging rules (like "which character do you use to allow users to specify multiple addresses for themselves, like gmail uses '+'?" - not all systems use +), or you may need DB access to verify the user name exists if you want a value of the given type to represent a user that is not only normalized but guaranteed to exist.

You said struct embedding could be used for discriminated unions, but there's no mechanism to discriminate between union variants here.

Go simply doesn't have discriminated unions, so a number of pattern can all be called "discriminated unions" in Go. I was simply emphasizing that sharing common fields between pure data structs with struct embedding (commonly seen in but not limited to discriminated unions) but now people are weirdly hung up on discriminated unions. I just showed a data model with a discriminator (.Type), there are a number of mechanisms to discriminate depending on your actual needs. You can make the types conform to an interface, pass an interface value around and cast to specific types. You can get a fat row with a bunch of left joins from your database then immediate create type-specific structs and call type-specific methods with them. You can get a discriminated union on the wire, unmarshal the type field first, then choose the type-specific struct to unmarshal to. Etc. These are largely irrelevant in a discussion about type embedding.

> so a number of pattern can all be called "discriminated unions"

Assuming they've got discriminators and some sense of type union, sure.

> I just showed a data model with a discriminator (.Type)

Which won't let you recover the additional fields from a pointer because you can't downcast, so that's insufficient for a union. AFAIK you need to combine this with interfaces, which I already know how to do.

> These are largely irrelevant in a discussion about type embedding.

Don't tell me, you brought it up.


It’s almost like I brought it up in passing because it’s a somewhat relevant concrete use case, rather than brought it up to have people who “already know how to do” to chastise me for not writing a full treatise on the use case.

I think there are a handful of cases where it is a nice-to-have and would be sad if it was removed in a hypothetical Go 2. Making a utility wrapper struct that overrides a method or adds new helper methods while keeping the rest of the interface is the most common example, though there are also some JSON spec type examples which are a little more esoteric. However, you need to be mentally prepared to switch to the boilerplate version as soon as things start getting hairy.

But yes, for anything more complicated I have generally regretted trying to embed structs. I think requiring "unsafe" is a bit too strong, but I think the syntax should've been uglier / more in-your-face to discourage its use.

(Fellow 10+ years Go user.)


yup, less than 24 hours after writing that comment, I found myself embedding a struct so that I could override one method in a test, haha

I think embedding structs would be way more useful if a) there would be properties on interfaces and b) there would be generic methods available.

As long as these two aren't there, embedding structs is literally identical to dispatching methods, and can't be used for anything else due to lack of state management through it. You have to manage the states externally anyways from a memory ownership perspective.


I'm very interested in this too. We're beginning to see models that can deeply understand a single image, or an audio recording of human speech, but I haven't seen any models that can deeply understand music. I would love to see an AI system that can iteratively explore musical ideas the same way Claude Code can iterate on code.


Time for Hungarian notation to make a comeback? I've always felt it was unfairly maligned. It would probably give LLMs a decent boost to see the type "directly" rather than needing to look up the type via search or tool call.


It was and still is

https://www.joelonsoftware.com/2005/05/11/making-wrong-code-...

Types help but they don't help "at a glance". In editors that have type info you have to hover over variables or look elsewhere in the code (even if it's up several lines) to figure out what you're actually looking at. In "app" hungarian this problem goes away.


I remember thinking this post was outdated when I first read it.

"Safe strings and unsafe strings have the same type - string - so we need to give them different naming conventions." I thought "Surely the solution is to give them different types instead. We have a tool to solve this, the type system."

"Operator overloading is bad because you need to read the entire code to find the declaration of the variable and the definition of the operator." I thought "No, just hit F12 to jump to definition. (Also, doesn't this apply to methods as well, not just operators?) We have a tool to solve this, the IDE."

If it really does turn out that the article's way is making a comeback 20 years later... How depressing would that be? All those advances in compilers and language design and editors thrown out, because LLMs can't use them?


I wonder if LLMs grok multiple dispatch


I'm dipping my toes into decompilation -- specifically, decompiling Super Smash Bros. Melee. Once you get into the groove, it's kinda addicting! Now that I've done a few functions by hand, I'm wondering how to best leverage AI to speed up the process.

Come decompile with us! https://github.com/doldecomp/melee


Thanks for your work! Always impressive to see the technical side of the SSBM community.


There are two ways to look at this.

First is that, if the parsing library for your codec includes a compiler, VM, and PGO, your codec must be extremely cursed and you should take a step back and think about your life.

Second is that, if the parsing library for your codec includes a compiler, VM, and PGO, your codec must be wildly popular and adds enormous value.


If you want to do something several hundreds of billions of times per day, you probably want to do it very efficiently.


Or, you know, several hundreds of billions of times per second...


I'm not sure which part you're objecting to.

If it's compilation at run-time, then I agree: it should be done at build time. But in this case it's really not a big deal.

If you're objecting to needing a compiler, then... you're not even wrong.


You can also prase Protobuf on the very low end, if you don't need super high throughput

https://jpa.kapsi.fi/nanopb/


I think this adds more confusion than it removes.

A list is not a monad. A list is a data structure; a monad is more like a "trait" or "interface." So you can define a List type that "implements" the monad interface, but this is not an inherent property of lists themselves. That's the sense in which a list "is a" monad: the OOP sense.

Haskell's List monad provides a model for nondeterminism. But that certainly isn't the only way List could satisfy the monad interface! It was a deliberate choice -- a good choice, possibly the best choice, but a choice nonetheless.


> A list is not a monad. A list is a data structure; a monad is more like a "trait" or "interface."

You operate with things that are bound to PL notions of specific languages. Instead, consider that list isn't a data structure, it's an abstraction that defines a multitude of position-ordered values. The multitude of position-ordered values called "list" is a presented entity of "monad", because it can be used as a valid input for a monadic computation defined consistently (in terms of the monad laws).


Hi, I completely agree. "A" list isn't inherently a monad, and that is where my metaphor starts to fall apart a bit (my post title furthers this issue.)

I can clarify this earlier in part 1 or 2 instead of in to-be-written part 3.


Its a harmful metaphor and clickbait title.


This seems harsh to the point of being untrue...


Isn’t it the case that for a given functor (on Set) there can only be at most one Monad structure?


Nope. It's that there's only one lawful Functor instance. But Applicatives and Monads can be multiple - lists are the classic example (zip vs cross-product)


Ah right. Didn’t remember it correctly.


The cross-product is not to be confused with the Cartesian product, which is related to the (in this case unfortunately named) "cross join" in SQL. Cross products operate in ℝ³, while Cartesian products are just defined over sets. The standard List monad instance uses the latter.


ah yes my bad! good callout


Can you explain the nondeterminism part of your comment more?


When you bind with (the Haskell definition for) the List monad - `someList >>= \someElement -> ...` it's like you're saying "this is a forking point - run the rest of the computation for each of the possible values of someElement as taken from someList". And because Haskell is lazy, it's (pardon the informality here) not necessarily just going to pick the first option and then glom onto it if it, say, were to cause an infinite loop if the computation were eagerly evaluated; it'll give you all the possibilities, and as long as you're careful not to force ones that shouldn't be forced, you won't run into any problems. Nondeterminism!

A nice demonstration of this is writing a very simple regex matcher with the List monad. A naive implementation in Haskell with the List monad Just Works, because it's effectively a direct translation of Nondeterministic Finite Automata into code.


From automata theory, you might know that nondeterministic automata are represented by a set of states. Deterministic automata are always in a specific state, while nondeterministic ones are in multiple at once. Lists are used for non-determinism in Haskell the same way as a set, mainly because they are easier to implement. But the total order that a list induces over a set is not that relevant.


That's right, and you see this directly reflected in the "types" of the transition functions for DFAs and NFAs, respectively [0] [1]:

    δ : Q × E ⟶ Q
    δ : Q × E ⟶ P(Q)
... where Q denotes the set of the automaton's states, E its alphabet of input symbols, and P the power set operation. Deterministic automata arrive at a definite, single state drawn from Q, while non-deterministic automata may arrive at a set (~list) of possible states, when given a current state from Q and next input symbol from E.

[0] https://en.wikipedia.org/wiki/Deterministic_finite_automaton

[1] https://en.wikipedia.org/wiki/Nondeterministic_finite_automa...


Determinism, in that given some set of inputs you only ever receive one output.

Non-determinism, in that given some set of inputs it's possible to receive a collection (a list) of possible outputs.

With lists you can express things like all possible pairings of all possible outcomes, or the Cartesian product:

    ghci> liftM2 (,) ['a', 'b', 'c'] [1,2,3]
    [('a',1),('a',2),('a',3),('b',1),('b',2),('b',3),('c',1),('c',2),('c',3)]
... or in more explicit monadic do-notation:

    ghci> :{
    ghci| do
    ghci|   x <- ['a', 'b', 'c']
    ghci|   y <- [1,2,3]
    ghci|   return (x, y)
    ghci| :}
    [('a',1),('a',2),('a',3),('b',1),('b',2),('b',3),('c',1),('c',2),('c',3)]
and so on.


> Bill, who lives near Aylesbury, said the reason for the request last Wednesday would be revealed in 23 years.

...well?


I wonder, what's the "weirdest" expression in Go? Here's one:

   type Foo struct{}
   func (Foo) Bar() { println("weird...") }
   func main() {
    ([...]func(){^^len(`
   
   
   `): (&Foo{}).Bar})[cap(append([]any(nil),1,2,3))]()
   }


Makes sense to me, [...]func() is an array of functions, and [...]T{index: value} is uncommon but still perfectly comprehensible


Many people aren't aware that you can use key: val declarations in arrays


That's why I like Rust /s


I have a personal fondness for silly variations of

  type __ *[]*__


this the worst? not too bad. fairly comprehensible.


Knitting is definitely the O.G. fidget toy for neurodiverse women


Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: