Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
Xarn
Jun 26, 2015
Yeah, but nobody expects them to be smart.

Adbot
ADBOT LOVES YOU

Canine Blues Arooo
Jan 7, 2008

when you think about it...i'm the first girl you ever spent the night with

Grimey Drawer
I haven't touch golang in a long time and even when I did, I barely started to scratch at it, but my take away from it was, 'This language seems to be extremely opinionated about how it wants you to write code', and then realized this came from it's maintainers, who happen to be extremely opinionated about how they want people to write code.

Pass.

nielsm
Jun 1, 2009



It's called being omakase.

Athas
Aug 6, 2007

fuck that joker

Xerophyte posted:

Chalmers University of Technology.

Their Parallel Functional Programming course is lovely. I've given guest lectures there now and then, but I never realised they bribed students with alcohol. That is a bold pedagogical technique.

In our similar course, we award students who get a top grade a coffee mug with our project logo on it. It is my impression that cheap swag actually does improve student motivation.

Soricidus
Oct 21, 2010
freedom-hating statist shill

nielsm posted:

It's called being omakase.

Nah, omakase means you trust the chef to deliver a good meal with minimal specifications. Golang is all about you having to write out every single step by hand because abstraction is scary, it’s literally the opposite

ExcessBLarg!
Sep 1, 2001

GABA ghoul posted:

Now that I think about it, their approach to throwing an exception in case of a missing keys actually makes a lot of sense. For value types anything a GetKey method could return for a missing key could be confused for an actual value, i.e. am I getting back a 0 because the inventory for this productID is 0 or because the productID is not in the hashmap at all?
This is a problem that's faced by every dictionary/map API ever. The options are generally:
  • Return null when a key does not exist, which may create ambiguity with keys explicitly assigned null (if that's actually possible, and if the distinction is relevant).
  • Allow the code to supply a default value to return if the key is not present (usually as an argument) and return that value.
  • Return an optional type, if the language supports optional types and if they were supported at the time the map/dictionary API was drafted.
  • Throw an exception.
Of these, throwing an exception is my personal least favorite. I'm a believer that "exceptions should be used for exceptional situations" and a key-not-present condition is usually a frequent occurrence. In practice though, it depends on how exception handling is implemented in the runtime and if thrown exceptions aren't heavyweight in terms of performance cost, then whatever. Also, some languages like Python utilize exceptions as part of standard control flow (e.g., raising a StopIteration exception is expected as part of the iterator protocol).

NihilCredo
Jun 6, 2011

iram omni possibili modo preme:
plus una illa te diffamabit, quam multæ virtutes commendabunt

Option #5 is to return both a value and a boolean, which is what C# does with TryGet - albeit with the awkward out-parameter syntax from long before it had decent support for actual tuples. It's equivalent to returning an optional type, just uglier.

Plorkyeran
Mar 22, 2007

To Escape The Shackles Of The Old Forums, We Must Reject The Tribal Negativity He Endorsed
Option #6 is the JS and Obj-C choice of having two null values, where one can be stored in the map and one indicates no value present in the map. Of those two I think obj-c's approach is the slightly less bad one: collections can't store null and by convention you use the singleton NSNull object instead, which lets failed map lookups return nil. JS's choice of objects storing null and undefined meaning "key not found" gives a nicer map API, but ends up complicating the type system.

ultrafilter
Aug 23, 2007

It's okay if you have any questions.


Option #7: undefined behavior.

leper khan
Dec 28, 2010
Honest to god thinks Half Life 2 is a bad game. But at least he likes Monster Hunter.

ultrafilter posted:

Option #7: undefined behavior.

"Return value will be indexed from the map. In the event the key is outside the map, the return value will be parsed from the memory immediately following the map"

ExcessBLarg!
Sep 1, 2001

leper khan posted:

"Return value will be indexed from the map. In the event the key is outside the map, the return value will be parsed from the memory immediately following the map"
And my favorite corollary: "key collisions are statistically unlikely".

Hammerite
Mar 9, 2007

And you don't remember what I said here, either, but it was pompous and stupid.
Jade Ear Joe

NihilCredo posted:

Option #5 is to return both a value and a boolean, which is what C# does with TryGet - albeit with the awkward out-parameter syntax from long before it had decent support for actual tuples. It's equivalent to returning an optional type, just uglier.

what's ugly about it? bool TryGetX(..., out X x) is a fine idiom. It lets you do if (TryGetX(...)) and if ( ! TryGetX(...)), which are nice and expressive.

CPColin
Sep 9, 2003

Big ol' smile.
Whatever the syntax, I just don't want to do the key lookup twice

biznatchio
Mar 31, 2001


Buglord

ExcessBLarg! posted:

Of these, throwing an exception is my personal least favorite. I'm a believer that "exceptions should be used for exceptional situations" and a key-not-present condition is usually a frequent occurrence.

The collection itself is not really in the best place to decide whether a key not being present is an 'exceptional situation' or a 'totally expected situation' because collections are very commonly used in both cases. And this isn't a problem with just collections, it's an issue with almost every sort of general library, and so the maxim of "exceptions should be used for exceptional situations" falls apart quickly in practice and as a result it shouldn't be the touchstone you strive to stick to.

Instead, a better approach is that 1) your method names should be verbs or verb phases; and 2) the method should throw an exception if that verb was not successfully performed; and 3) the method should not throw an exception if the verb was successfully performed.

This approach handles a method named "Get" on a collection. Did you get the requested value? No? Throw an exception. And the C# approach of having a non-throwing "TryGet" alternative. Did you try to get the requested value? Yes? No exception thrown.

If you stick to this approach, it's very clear when a method should or shouldn't throw an exception without any ambiguity; and nobody has to make any judgement calls on what an 'exceptional situation' is. And reading code becomes a lot more straightforward because you see a set of actions and unless an exception bopped you out of normal flow control, you know all of those actions definitively will happen.

biznatchio fucked around with this message at 22:44 on Dec 11, 2023

NihilCredo
Jun 6, 2011

iram omni possibili modo preme:
plus una illa te diffamabit, quam multæ virtutes commendabunt

Hammerite posted:

what's ugly about it? bool TryGetX(..., out X x) is a fine idiom. It lets you do if (TryGetX(...)) and if ( ! TryGetX(...)), which are nice and expressive.

Out parameters are a leaked implementation detail of how returning multiple values on the stack works in IL. In 99.9% of cases there's no reason to care about the difference between a return value and an out parameter, they should look the same in high-level code.

They're less ugly now that you don't need to declare them in advance, but with modern c# syntax you could have the even more expressive form:

code:

someDict.TryGet(key) switch
{
    (true, value) => doWhatever(value),
    (false, _) => fail()
};

which is about as good as it gets short of sum types (and I bet those will arrive in c# 18 or so).

QuarkJets
Sep 8, 2008

ExcessBLarg! posted:

This is a problem that's faced by every dictionary/map API ever. The options are generally:
  • Return null when a key does not exist, which may create ambiguity with keys explicitly assigned null (if that's actually possible, and if the distinction is relevant).
  • Allow the code to supply a default value to return if the key is not present (usually as an argument) and return that value.
  • Return an optional type, if the language supports optional types and if they were supported at the time the map/dictionary API was drafted.
  • Throw an exception.
Of these, throwing an exception is my personal least favorite. I'm a believer that "exceptions should be used for exceptional situations" and a key-not-present condition is usually a frequent occurrence. In practice though, it depends on how exception handling is implemented in the runtime and if thrown exceptions aren't heavyweight in terms of performance cost, then whatever. Also, some languages like Python utilize exceptions as part of standard control flow (e.g., raising a StopIteration exception is expected as part of the iterator protocol).

I like the Python implementation of having 2 different ways to fetch something from a dict: one that raises an exception if the key isn't present, and one that returns a default value. They're both good approaches, sometimes I want the first one, sometimes I want the second.

Hammerite
Mar 9, 2007

And you don't remember what I said here, either, but it was pompous and stupid.
Jade Ear Joe

NihilCredo posted:

Out parameters are a leaked implementation detail of how returning multiple values on the stack works in IL. In 99.9% of cases there's no reason to care about the difference between a return value and an out parameter, they should look the same in high-level code.

The first sentence here is irrelevant and the second sentence is wrong. The most commonplace use of out parameters is as part of a pattern where the return value indicates the status of the requested operation and the out parameter(s) provide detail. These are quite different things, and it's perfectly fine for them to look different.

quote:

They're less ugly now that you don't need to declare them in advance, but with modern c# syntax you could have the even more expressive form:

code:
someDict.TryGet(key) switch
{
    (true, value) => doWhatever(value),
    (false, _) => fail()
};
which is about as good as it gets short of sum types (and I bet those will arrive in c# 18 or so).

I find that code you exhibited far more ugly than if (someDict.TryGetValue(...)), and it's un-idiomatic too, given that TryGetValue() and its cousins have existed in the language for far longer than switch-expressions. The "if" formulation is more flexible (what if there's no action to be taken in one branch? what if I want to execute several statements in one or both cases?)

You don't seriously use switch expressions to handle a dichotomy when if ... else ... is to hand, do you? I don't want to read code written like that.

How would you choose to re-implement the following thoroughly idiomatic method while avoiding TryGetValue(TKey, out TValue)? You may imagine that your tuple-overload exists on Dictionary<TKey, TValue> if you want.

code:
void EnsureListExistsAndAddElement<TKey, TValue>(Dictionary<TKey, List<TValue>> d, TKey key, TValue value)
{
    if ( ! d.TryGetValue(key, out var L))
    {
        L = new List<TValue>();
        d.Add(key, L);
    }
    L.Add(value);
}

SirViver
Oct 22, 2008

Seriously? You think this is more expressive? As in, more clearly communicates what is happening? :crossarms:

Personally I'd fail such "clever" code in a review. Seriously, take a step back and think about which code example would be more likely understood by someone who doesn't know anything about the language. Hint: one basically just requires understanding English.

What I'd advocate for instead is a TValue GetOrDefault(TKey key, TValue defaultValue = default) method to be added to the framework for cases where you don't care whether an entry exists, but at this point it's easier to just do that yourself via extension methods.

Xarn
Jun 26, 2015
Y'all are spelling "return optional<T>" really weirdly.

CPColin
Sep 9, 2003

Big ol' smile.

Hammerite posted:

How would you choose to re-implement the following thoroughly idiomatic method while avoiding TryGetValue(TKey, out TValue)? You may imagine that your tuple-overload exists on Dictionary<TKey, TValue> if you want.

code:
void EnsureListExistsAndAddElement<TKey, TValue>(Dictionary<TKey, List<TValue>> d, TKey key, TValue value)
{
    if ( ! d.TryGetValue(key, out var L))
    {
        L = new List<TValue>();
        d.Add(key, L);
    }
    L.Add(value);
}

Kotlin would do this as d.getOrPut(key, mutableListOf()).add(value), which is nice.

canis minor
May 4, 2011

Plorkyeran posted:

Option #6 is the JS ... choice of having two null values, where one can be stored in the map and one indicates no value present in the map.

Can you please explain what you mean here?

I can do `var foo = {undefined: undefined, null: null}` (or `var map = new Map(); map.set(null, null); map.set(undefined, undefined)`). `Object(foo).keys()` / `Object(foo).values()` (or `map.keys()` / `map.values()`) will return `[undefined, null]`. What I mean is: I can see no difference between trying to access a key on a map that has undefined as a value or where such key doesn't exist - both will return undefined.

OddObserver
Apr 3, 2009

Xarn posted:

Y'all are spelling "return optional<T>" really weirdly.

Yup. Which, notably, doesn't have a T floating around unless one actually is relevant.

Volmarias
Dec 31, 2002

EMAIL... THE INTERNET... SEARCH ENGINES...

canis minor posted:

I can do `var foo = {undefined: undefined, null: null}` (or `var map = new Map(); map.set(null, null); map.set(undefined, undefined)`). `Object(foo).keys()` / `Object(foo).values()` (or `map.keys()` / `map.values()`) will return `[undefined, null]`.

Third base!

Athas
Aug 6, 2007

fuck that joker

SirViver posted:

Seriously, take a step back and think about which code example would be more likely understood by someone who doesn't know anything about the language. Hint: one basically just requires understanding English.

This is an exciting line of thinking! Why not take it further? Why should we assume that the future reader of the code will know what a computer is, what the problem domain is, or even what they are supposed to be doing?

SirViver
Oct 22, 2008
You say that as if it weren't already the case :eng99:

Athas
Aug 6, 2007

fuck that joker
My style guide is based on the assumed context of my code being used in a stock photo, that stock photo being used in a printed newspaper, that newspaper being used for wrapping by a fishmonger, and the buyer of a fresh salmon idly glancing at the code while riding the bus home.

Dijkstracula
Mar 18, 2003

You can't spell 'vector field' without me, Professor!

starting to get a sense of why the "well-intentioned python programmer who picked up Rust last week" demographic considers Option to be magical

Hammerite
Mar 9, 2007

And you don't remember what I said here, either, but it was pompous and stupid.
Jade Ear Joe

CPColin posted:

Kotlin would do this as d.getOrPut(key, mutableListOf()).add(value), which is nice.

It's nice that it's a one-liner and it would be nice if the C# standard library had something like Python's DefaultDict. But isn't that always constructing a new list even when none is required? Or is Kotlin cleverer than that? (or maybe there's some singleton-empty-list plus copy-on-write cleverness or something, I don't know Kotlin or Java at all)

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
I don't think that code compiles, the correct code is d.getOrPut(key, {mutableListOf()}).add(value).

Which doesn't construct a new list each time, but might create a new short-lived lambda (which is lazily called if the default value is needed).

leper khan
Dec 28, 2010
Honest to god thinks Half Life 2 is a bad game. But at least he likes Monster Hunter.

Dijkstracula posted:

starting to get a sense of why the "well-intentioned python programmer who picked up Rust last week" demographic considers Option to be magical

Horror from the thread and all, but I never understood the appeal of option types. Doesn't give any more information than a pointer.

You don't solve the problem of having null data. And you potentially confer that problem on to _value_ types, which implies you don't understand how data enters your system.

Oh you have to check HasValue everywhere instead of != NULL. Wow great good job.

Phobeste
Apr 9, 2006

never, like, count out Touchdown Tom, man
The thing with using pointers is that a) they are reference types and therefore have to refer to something and therefore you have to allocate and free it which is annoying and b) they have more than just null as invalid because of use-after-free at least. Option types narrow that to valid or invalid only and don’t require allocation

CPColin
Sep 9, 2003

Big ol' smile.

Jabor posted:

I don't think that code compiles, the correct code is d.getOrPut(key, {mutableListOf()}).add(value).

Which doesn't construct a new list each time, but might create a new short-lived lambda (which is lazily called if the default value is needed).

Yeah, you're right, I missed that it's a lambda

ultrafilter
Aug 23, 2007

It's okay if you have any questions.


leper khan posted:

Horror from the thread and all, but I never understood the appeal of option types. Doesn't give any more information than a pointer.

You don't solve the problem of having null data. And you potentially confer that problem on to _value_ types, which implies you don't understand how data enters your system.

Oh you have to check HasValue everywhere instead of != NULL. Wow great good job.

See here for a pretty good explanation.

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
It solves the problem by clearly marking where it's possible to have None values, and crucially, makes it impossible to return them or pass them in to places that don't accept an Option<>.

They often also come with lots of conveniences for common tasks like "if the result is present then transform it in this way, otherwise just leave it as None". Some languages try to do a similar thing for null by having some special syntax (e.g. the ?. and ?? operators), but it's pretty ad-hoc and limited compared to the more rigorous treatment a proper option type has.

If you find-replace to wrap all your types in Option<> like a complete idiot and now you have potentially-None values everywhere then sure you're gonna find them sucky, but that's entirely a you problem.

leper khan
Dec 28, 2010
Honest to god thinks Half Life 2 is a bad game. But at least he likes Monster Hunter.

Phobeste posted:

The thing with using pointers is that a) they are reference types and therefore have to refer to something and therefore you have to allocate and free it which is annoying and b) they have more than just null as invalid because of use-after-free at least. Option types narrow that to valid or invalid only and don’t require allocation

You shouldn't have a need for a null value type IMO. Though sure that's a real use case of something they do that's different.

How is use after no longer has value different than use after free?

leper khan
Dec 28, 2010
Honest to god thinks Half Life 2 is a bad game. But at least he likes Monster Hunter.

Jabor posted:

It solves the problem by clearly marking where it's possible to have None values, and crucially, makes it impossible to return them or pass them in to places that don't accept an Option<>.

They often also come with lots of conveniences for common tasks like "if the result is present then transform it in this way, otherwise just leave it as None". Some languages try to do a similar thing for null by having some special syntax (e.g. the ?. and ?? operators), but it's pretty ad-hoc and limited compared to the more rigorous treatment a proper option type has.

If you find-replace to wrap all your types in Option<> like a complete idiot and now you have potentially-None values everywhere then sure you're gonna find them sucky, but that's entirely a you problem.

It's not a me problem, it's an everywhere I've ever worked problem. Don't blame me for all the poo poo I have to deal with. :smith:

ExcessBLarg!
Sep 1, 2001

leper khan posted:

Horror from the thread and all, but I never understood the appeal of option types. Doesn't give any more information than a pointer.
Null references subvert the type system, while option types enforce it.

leper khan posted:

Oh you have to check HasValue everywhere instead of != NULL. Wow great good job.
Usually those checks happen sooner though, close to where an unexpectedly-empty value enters the system, which makes them easier to debug.

leper khan
Dec 28, 2010
Honest to god thinks Half Life 2 is a bad game. But at least he likes Monster Hunter.

ExcessBLarg! posted:

Null references subvert the type system, while option types enforce it.

Usually those checks happen sooner though, close to where an unexpectedly-empty value enters the system, which makes them easier to debug.

If they happen sooner, you're converting from a nullable or reference type to a non-nullable value-type. Checking for a value has similar semantics and failing to do that in both cases throws an exception.
:shrug:

Dijkstracula
Mar 18, 2003

You can't spell 'vector field' without me, Professor!

leper khan posted:

Horror from the thread and all, but I never understood the appeal of option types. Doesn't give any more information than a pointer.
As people were saying earlier w/r/t a dictionary datatype where you can insert NULL as a valid value for a key, a nullable pointer suffers from the semipredicate problem, whereas Option types fundamentally don't - in that dictionary example, with some key type K and some value type V, a get(some_key:K) call returns an Option<V>, so even if the V type admits NULL in its type, you can distinguish between an absent value (None) and a present but nulled value (Some(nullptr)).

leper khan posted:

Oh you have to check HasValue everywhere instead of != NULL. Wow great good job.
this isn't true at all: with combinators like map() and flat_map() you thread the computation through a value-holding Option, with well-defined behaviour for a None Option. Have a function that takes a T and returns a U, but you have an Option<T>? No problem, call theOption.map(that_fn) and now you have an Option<U>. Truly need that U out of the option? Cool, pattern match on the option and extract it out if the option is a Some value, and now you can't forget to give a default value of type U for when the Option held no value in the first place. (some interfaces give a get_or_default() function for this, especially if you're writing in a jlang where pattern matching isn't a language feature.)

If you're using Options in the if o.HasValue() { var t = o.get(); var u = that_fn(t); ... } style then yes it's no better, but that's not how they're really intended to be used.

But, even without programming in that style, the entire point of an Option<T> is to enforce statically that you know you have a T value to operate on, either via combinators like the above, or pattern matching on both None or Some(t:T), or get_or_else(your_own_default_value) or whatever. With a T* you have to remember to do the null check yourself and the language is not there to help you if you get it wrong.

Dijkstracula fucked around with this message at 17:02 on Dec 12, 2023

Adbot
ADBOT LOVES YOU

Volte
Oct 4, 2004

woosh woosh

leper khan posted:

You shouldn't have a need for a null value type IMO. Though sure that's a real use case of something they do that's different.

How is use after no longer has value different than use after free?
Forget the word "null", it's distorting your viewpoint. Option is effectively a list that can have either zero or one element in it, and as a result you can basically use all the nice patterns that you can use with lists (and a few others that come from the zero-or-one property too). Null specifically means an invalid pointer, and it is sometimes (mis)used to represent the "empty list" case, but None and null aren't the same thing.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply