u/SuperGrade
SuperGrade
SuperGrade
Oh yeah. Especially with AI now though, I'd insist anyone new do "drop a blob of code into chat 10x a day, at least", combined with using AI responses to tailor answers-with-deep-explanation for situations.
As for "hands-on", I agree, one way or another, everyone will be authoring code.
SuperGrade
I'll emphasize an angle on F# (that others have mentioned) - not for language quality, nor directly for the .Net libraries themselves (which are both good of course). . . . you pick any FP language it's a hard sell and 'going out on a limb'. The equivalence of F# project files to C# project files within a large .Net project gives you the ability to gradually inject it to great effect and low risk. Especially now with AI, strategically porting bits (I tend to almost always go C#->F#) becomes even faster.
You probably have this with Java/Scala as well; but I don't know them. I'm speaking to massive F#/C# combined being viable, from experience.
SuperGrade
For immutable key-value stores, there are Trie-based (hash trees), or things like F#'s Map and Set (which you can call into from C#) which are also immutable; but based on key comparison/ordering and trees.
A key with immutable dictionaries is adding an element gives you a new dictionary; but that structurally shares most of the prior. Key is this gives you what you get with things like atomit ints - where adding still leaves a variable referencing the old one with a full dictionary with the old contents.
(.Add and .Remove are immutable operations in the containers described)
SuperGrade
Yet they are there and they seem to work - a clear distinction from 'never'.
SuperGrade
Unless I've changed there's a cringeworthy distinction I'm aware of for really putting languages on JVM - .Net has tailcall, JVM doesn't. Statically analyzing local recursion is not a substitute (indepth functional code can tend to recurse via function references, i.e. non-statically-knowable elements).
SuperGrade
I'll reemphasize with my own anecdotal. Working in an area where, at the end of the day, request-response; plus carefully load-saving from NOSQL, and doing a trip to an SQL server.
The trips to the data sources are everything that the program pivots around. Any BS ('let's do it wrong because then it'll be OO/encapsulated, etc.") that makes us go to one of them twice, or goes to them them out of order for some reason, or doesn't unroll a certain way from failure in one, is a potential security issue or DEAD WRONG.
^ They ^ are the domain's performance issue. Everything else serves them.
If you were coding in a domain where the optimized mutable dictionaries are the final ultimate portion of the problem whose performance can't be ignored, they become the criticial point. Functional programming can help with the isolation of these and the preventing bugs in the operating order of the code that orbits them.
SuperGrade
I'll make some points.
- Yes, he trolled heavily - and probably misrepresented (the language he was trying to pull people from)
- You should usually listen to him anyway. He's really smart, and while he may over-advocate for the F#/ML family, in doing so he gives the reader information from a source who knows more about them than almost anyone.
I learned a couple things from JH.
SuperGrade
Technically this gets harder than it looks - a lot of the correctness checks, to the degree you can do them, in larger programs, involves passing around references, and newing up replacement things from old things. There isn't a 1:1 mapping from this to some code that may happen to have side effects (and use mutation, placement and overwrite assumptions, and be much faster).
WRT going for speed, even accepting garbage collection is more than not having to remember to delete things; but gives you ability for some factored piece of code to work in, what in optimized/non-GC form, would be 2 different performance contexts.
SuperGrade
By "It" I mean the code for the algorithm for the container. A C# container's algorithm can be expressed once in a DLL and presented without the code. The (template-based) container has its code recreated for each type (at least, the portions that are generic on the type param).
SuperGrade
Well, it doesn't have generics either - but it has the ability for the programmer, with his templates, to determine the storage characteristics of generic containers (i.e. not put on the heap) which is the speed boost Harrop meant.
Really, it's probably best to leave C++ out of these discussions. The distinctions he's making are between 'single reference type' (by necessity, GCed) languages.
(Edit: Hmm - Technically, if you mean a container being able to be put into a static library that factors the contairer's code, you can't do this with templated containes - in this regard, maybe we could say C++ doesn't have reified generics, nor can you technically do the equivalent of what you can factor into .NET container libraries).
SuperGrade
Probably a side note, I'll ruin the surprise but he's talking about .Net platform features more than language (albeit, that any language will dovetail into). It's a combination that makes things like System.Dictionary faster than many counterparts in other GCed languages.
SuperGrade
May depend on the domain. "Mutable then freeze" could be "Build up what you need, then as your final step build the immutable construct". Although, if it were truly frozen (didn't need, say, immutable dictionary.Add) you'd leave the data in the mutable data constructs; but only present an immutable facade to them (e.g. instead of exposing C#'s System.Dictionary, expose only Func<Key, Value> and Func<IEnumerable<Tuple<Key, Value>>>>
SuperGrade
"Easier to reason about" has an objective slant to it. Non-programming grandma can't reason about either of them. And yes, it's illegible gibberish to consensus imperative programmer. The advantage mentioned, unless I'm mistaken, is the advantage to reasoning with functional programming in the toolbox - that a programmer who learns the tools can better reason about his FP solution than his imperative.
Note - this is 'reason about' not read. Anecdotally, in any given imperative program, this comes from the incidence of a programmer being able to look at it in a broken form and a correct form and see the same program from an 'I can read it and see what it's doing' standpoint.
Also consider that most (FP) advocates would have come from some sort of imperative background.
SuperGrade
I think it comes down to the FP program being faster than the program that may evolve without it. Also, a lot of problems just map into it anyway, request-response based servers.
SuperGrade
Persistent mutating containers etc. in your design could become the thing that the design pivots around (through isolation, or monads - like with the IO).
Pure functional sections just strongly delineate and highlight these from the code that revolves around providing the changes to them.
With the humble fast mutable dictionaries (or group of them - a state?) - or IO to an external database - code could describe controlled contained changes to these, while still maintaining a relationship between itself, the IO, and other pieces of pure code. Timing and order (of access to the IO or mutable data structures) would still be explicitly described at some, probably single, point in code.
SuperGrade
Yeah but in the US you're expected to work 80 hours per week,
??
you don't get public healthcare
Bit of info - if he's talking salary, he's got private healthcare, and when people talk salary in the US whatever that costs is not added to the $ amount mentioned. We could bicker that this is bad for the other 3 people he's not supporting; but it's not a negative deal for his own UK vs US prospects. To explain this, whether he makes that bucket of cash in the US vs 1/3 in the UK, those 3 other people still won't have govt healthcare.
you have MUCH higher taxes in America
Than where. . . not than the rest of the anglosphere. Probably higher taxes than HK, Singapore, Cayman Islands. . . . ? Also +/- 10% depending on state; but still lower than the aforementioned anglosphere.
SuperGrade
IMHO also, when people are weighing the pros and cons, think of sum types (F# discriminated unions, etc.) where you can describe an exhaustive set of exclusive values with exhaustiveness support on extraction. In static-checked-language-land, there's a big difference between languages that have sum-type support vs not - you jump through a lot of hoops to deal with this type of data in a very clumsy manner in curly-brace languages.
SuperGrade
In facing complexity I lean toward the statically checked and provable, and even in statically typed languages your choices can make the final result more or less dynamic or statically checked. (and others will remember the true quote behind this); but the provable is a subset of that which is possible - sometimes even when looking for static checks you end up writing a thing in some more formal manner. I think static checking (via dynamic typing) is trying to fill the gap giving you the ability to formally check things that even in dynamic typing at ML level you're still doing in a dynamic sense.
SuperGrade
Knowing (from a group of readonly fields) that the class instance is always holding and conveying the same reference - that is itself information. You could make some derivative information that aligns with the information in the record, and proceed with the assumption that the values in the record, should you read them again, stay in alignment. A getter does not communicate this same information, only that it will provide some value at the point in time when you call it; but nothing constraining it to give back the same value.
SuperGrade
Interesting seeing those perspectives in the stackexchange. The final/readonly version has some positive value in that the recipient of it is guaranteed it won't represent a changing value.
In OO codebases, rules are made for mutating/encapsulated/inherited classes based on that role. Rules/guidelines should treat (those, give them a name) as distinct from "records", which provide their value from slinging around provably (or provably as possible, or by convention) immutable tail-shareable data. The "records" have a role closer to that of ints, albeit that they can safely exist in as a part of a greater graph of other immutable things that parts of an algorithm can share safely.
"But with defining the (record) that way you can't inherit and create MutableRecord". That is technically a positive for the record role, recipients of the (original) record get that as part of the guarantee of what they are receiving.
SuperGrade
You could almost plot out a multi-dimensional curve of each along various axes - at some point either will win/lose. The reference version has an n-times overhead in the tight loop case; but a more contained degenerative case. This makes it start to do better in composition where GC also helps - composing up libraries that may work on generic parameters able to be written less concerned with certain performance cases, and the performance becomes based on the sum total of how they're combined.
On the Gen0 survivor case, understand the use case here though - majority of these things will tend to drift to Gen2, or die in Gen0 - since we're talking about lots of tiny ephemeral allocations too, the Gen1 survivor will statistically tend to be the minority.
They did take the safer course making Tuple a heap allocation. For loops where it counts (especially where you want Tuple<...>) yeah use a struct. I actually wish they'd provide a special Struct-tuple that couldn't default-construct.
I support wanting a standard struct-based Tuple either way, and for F# to get a good way to coexist with it as it does with the existing tuple.
SuperGrade
Cost structure of struct vs heap-allocated - not so simple. In an inner loop for some graphical thing where you're creating/using, structs/stack allocation should win. In code based on prebuilding big graphs of things that share references (that are then used heavily afterwards), quite the opposite.
Also, understand these tiny short term allocations - the ones you don't keep forever- (in .Net with generational/copying GC, not the old mono in unity) have a small allocation cost. Gen0 allocation moves a pointer up in a thread-local pool, and when exhausted you then pay for what you keep, not so much for what you allocated. For a short term allocation you pay a small multiple of what putting that data on the stack would have cost (it's like moving a stack pointer); but it's not proportional to the size of your long-term data (like what a collection costs on a non-generational collector).
SuperGrade
It's not so simple - but IMHO they made the better/safer call (for everyone) putting tuples on the heap.
Toy examples we often write tend to involve local creation of things within the example. But imagine a codebase where there are tuples that end up on the heap (earlier in program startup) that are merely re-referenced heavily or destructured after the fact - just saying, I work in a lot of code like this, where the performance advantage tilts more toward the heap allocated items. Also, consider generic code of a Tuple<T, ...> - where T could be a struct (or struct-tuple were they structs) the copying case could grow invisibly.
SuperGrade
Not sure I would call it the best functional language though.
FWIW I read that as including the qualifier (best) "that noone has ever tried"
I'd refine that - turning *the right* C# devs into F# devs. I think we had a lot of luck; but some shuffling in the org and then the marketplace lost some good ones. But yes, we have never had anyone come into the company with F# experience.