| ▲ | rtpg 5 hours ago |
| One thing I think worth considering for systems languages on this point: if you don't want to solve every expressiveness issue downstream of Result/Option/etc from the outset, look at Swift, which has nullable types. MyObject can't be null. MyObject? can be null. Handling nullability as a special thing might help with the billion-dollar mistake without generating pressure to have a fully fleshed out ADT solution and everything downstream of that. To people who would dismiss ADTs as a hard problem in terms of ergonomics: Rust makes it less miserable thanks to things like the question-mark shorthand and a bazillion trait methods. Languages like Haskell solve it with a monads + do syntax + operating overload galore. Languages like Scala _don't_ solve it for Result/Option in any fun way and thus are miserable on this point IMHO |
|
| ▲ | tialaramex 4 hours ago | parent | next [-] |
| I like to think about how many problems a feature solves to judge whether it's "worth it". I believe that the Sum types solve enough different problems that they're worth it, whereas nullability solves only one problem (the C-style or Java-style null object) the Sum types can solve that with Option<T> and also provide error handling with Result<T, Err> and control flow with ControlFlow<Continue, Break> among others so that's already a better deal. Nullability is a good retro-fit, like Java's type erased generics, or the DSL technology to cram a reasonable short-distance network protocol onto the existing copper lines for telephones. But in the same way that you probably wouldn't start with type erased generics, or build a new city with copper telephone cables, nullability isn't worth it for a new language IMO. |
| |
| ▲ | noelwelsh an hour ago | parent [-] | | Erased generics give parametricity, which most PL people think is fairly important. See https://en.wikipedia.org/wiki/Parametricity or https://www.cl.cam.ac.uk/teaching/1617/L28/parametricity.pdf | | |
| ▲ | tialaramex 28 minutes ago | parent [-] | | I mean, yeah, type erasure does give parametricity, but, you can instead design your language so that you monomorphize but insist on parametricity anyway. If you write stable Rust your implementations get monomorphized but you aren't allowed to specialize them - the stable language doesn't provide a way to write two distinct versions of the polymorphic function. And if you only regard parametricity as valuable rather than essential then you can choose to relax that and say OK, you're allowed to specialize but if you do then you're no longer parametric and the resulting lovely consequences go away, leaving it to the programmers to decide whether parametricity is worth it here. | | |
| ▲ | noelwelsh 2 minutes ago | parent [-] | | I don't understand your first paragraph. Monomorphization and parametricity are not in conflict; the compiler has access to information that the language may hide from the programmer. As an existance proof, MLTon monomorphizes arrays while Standard ML is very definitely parametric: http://www.mlton.org/Features I agree that maintaining parametricity or not is a design decision. Recent languages that break it (e.g. Zig) don't seem to understand what they're doing in this regard. At least I've never seen a design justification for this, but I have seen criticism of their approach. Given that type classes and their ilk (implicit parameters; modular implicits) give the benefits of ad-hoc polymorphism while mantaining parametricity, and are well established enough to the point that Java is considering adding them (https://www.youtube.com/watch?v=Gz7Or9C0TpM), I don't see any compelling reason to drop parametricity. |
|
|
|
|
| ▲ | _flux 5 hours ago | parent | prev | next [-] |
| I personally don't enjoy the MyObject? typing, because it leads to edge cases where you'd like to have MyObject??, but it's indistinguishable from MyObject?. E.g. if you have a list finding function that returns X?, then if you give it a list of MyObject?, you don't know if you found a null element or if you found nothing. It's still obviously way better than having all object types include the null value. |
| |
| ▲ | gm678 9 minutes ago | parent | next [-] | | Different language, but I find this Kotlin RFC proposing union types has a nice canonical example (https://youtrack.jetbrains.com/projects/KT/issues/KT-68296/U...) inline fun <T> Sequence<T>.last(predicate: (T) -> Boolean): T {
var last: T? = null
var found = false
for (element in this) {
if (predicate(element)) {
last = element
found = true
}
}
if (!found) throw NoSuchElementException("Sequence contains no element matching the predicate.")
@Suppress("UNCHECKED_CAST")
return last as T
}
A proper option type like Swift's or Rust's cleans up this function nicely. | |
| ▲ | ecedeno 3 hours ago | parent | prev | next [-] | | Your example produces very distinguishable results. e.g. if Array.first finds a nil value it returns Optional<Type?>.some(.none), and if it doesn't find any value it returns Optional<Type?>.none The two are not equal, and only the second one evaluates to true when compared to a naked nil. | | |
| ▲ | _flux an hour ago | parent [-] | | What language is this? I'd expect a language with a ? -type would not use an Optional type at all. In languages such as OCaml, Haskell and Rust this of course works as you say. | | |
| ▲ | _rend an hour ago | parent [-] | | This is Swift, where Type? is syntax sugar for Optional<Type>. Swift's Optional is a standard sum type, with a lot of syntax sugar and compiler niceties to make common cases easier and nicer to work with. | | |
| ▲ | _flux 30 minutes ago | parent [-] | | Right, so it's not like a union type Type | Null. Then naturally it works the same way as in the languages I listed. |
|
|
| |
| ▲ | lock1 3 hours ago | parent | prev | next [-] | | Well, in a language with nullable reference types, you could use something like fn find<T>(self: List<T>) -> (T, bool)
to express what you want.But exactly like Go's error handling via (fake) unnamed tuple, it's very much error-prone (and return value might contain absurd values like `(someInstanceOfT, false)`). So yeah, I also prefer language w/ ADT which solves it via sum-type rather than being stuck with product-type forever. | | |
| ▲ | _flux an hour ago | parent [-] | | How does this work if it is given an empty list as a parameter? I guess if one is always able to construct default values of T then this is not a problem. |
| |
| ▲ | 2 hours ago | parent | prev | next [-] | | [deleted] | |
| ▲ | skydhash 2 hours ago | parent | prev [-] | | I like go’s approach on having default value, which for struct is nil. I don’t think I’ve ever cared between null result and no result, as they’re semantically the same thing (what I’m looking for doesn’t exist) | | |
| ▲ | onionisafruit an hour ago | parent | next [-] | | In Go, the default (zero) value for a struct is an empty struct. | |
| ▲ | Cyph0n an hour ago | parent | prev [-] | | Eh, it’s not uncommon to need this distinction. The Go convention is to return (res *MyStruct, ok bool). An Option type is a cleaner representation. |
|
|
|
| ▲ | noelwelsh 4 hours ago | parent | prev [-] |
| The Scala solution is the same as Haskell. for comprehensions are the same thing as do notation. The future is probably effect systems, so writing direct style code instead of using monads. It's interesting that effect system-ish ideas are in Zig and Odin as well. Odin has "context". There was a blog post saying it's basically for passing around a memory allocator (IIRC), which I think is a failure of imagination. Zig's new IO model is essentially pass around the IO implementation. Both capture some of the core ideas of effect systems, without the type system work that make effect systems extensible and more pleasant to use. |