Edited to add:
Note that this can be the case in Haskell when you're relying on exceptions for error handling. That's the opposite approach from option types, though.
So, depending on just how "lazy" the program truly is, sure you passed it something that unwraps a Maybe, but only once you actually go to use it. That make sense?
Consider this fun bit of nonsense scala
def foo(x:=>String): Unit = {
println("Hello")
println(x)
}
If I call this as "foo(None.get)", then it will print "Hello" before barfing. In fact, if I didn't evaluate x, it wouldn't even barf. I had thought the laziness of Haskel was that ++. (Meaning I thought if I stored x off to another also lazy reference, it wouldn't barf right away.)To make that a little more clear of what I meant, some even more amusing scala.
class Foo(x: => String) {
def happy = println("Doing good.")
def sad = println(x)
}
If I create something with "new Foo(None.get)", I can call happy on this object all day long. If I ever call sad, I get an exception.Apologies for the ninja edit above, but this scala seems to show it isn't just IO that is at danger here.
class Foo(x : =>String) {
def happy = println("I'm good.")
lazy val ack = x
def sad = println(ack)
}
Again, if I do "new Foo(None.get)", I can call happy as much as I want, but "sad" will blow up.Now, I do not and could not claim that this sort of nonsense would have proliferated with the same abandon as null. I am claiming that this is not that unlike a null pointer exception, though.
case maybe_string of
Just string -> newFoo string
Nothing -> defaultFoo
Or to use one of the relevant higher-order functions: fmap newFoo maybe_string :: Maybe Foo
maybe defaultFoo newFoo maybe_string :: Foohttp://research.microsoft.com/en-us/um/people/simonpj/papers...
This is also why the Control.Exception module exposes `evaluate`, which embeds a value in an IO computation and thus resolves this problem... If you remember to use it.
http://hackage.haskell.org/package/base-4.7.0.0/docs/Control...
Fortunately, in idiomatic Haskell exceptions are considered to be an expert feature (basically I see them in concurrent IO or resource management code only) and partial functions—usually introduced by incomplete pattern matching like `None.get` does—are veboten.
It's not perfect, but -Wall will help with that.
My point here was more that just because you have a file handle doesn't mean you have a valid place to write data. Making it optional would possibly prevent some bugs, true. However, it doesn't really help as soon as you have a filehandle to a full filesystem, for example. (Or, well, any other problem that usually happens to cause grief with the filesystems. You got a file, but by the time you went to use it the system was full and you couldn't, etc.)
Regardless, I should have been clearer on many points (and I fully accept I was flat out wrong on at least a few :) ). I do think null pointers are bad. I also know with proper static analysis tools, you don't need a whole new type system to make things better. Unless you are considering tools such as Coverity and friends some form of type system.
I would, for the record.
There was a thread some days ago (https://news.ycombinator.com/item?id=7916554) where I described some hackary I used to get particular guarantees out of C's type system. My response in https://news.ycombinator.com/item?id=7918423 seems tangentially relevant here. Type systems aren't (only/necessarily) features of a language.
I think the only other differences stem ultimately from standardization. Tooling could exploit any type system a program is known to follow. Likewise, compiler optimizations, &c, &c.
Well, I realize that is false. But the hoops all of the examples I have seen that types have to jump through to accomplish that aren't exactly pleasant.
So, maybe my preference is just that tooling can give you many of the benefits of more advanced type systems, without having to have all of the boilerplate of it everywhere in the code.
At any rate, I should say thank you very much for keeping this dialog going. Been a blast!
By "tooling" there, I wasn't referring to static analysis. I meant things like type-directed name resolution, type-directed completion, Agda's "holes", and so on. Strictly, these do not rely on the type system being "a part of" the language, but they do rely on some degree of standardization on a particular type system for the tool to work (or work well).
"Interleaved calls to different types, for example, can be found through tooling."
I actually don't understand exactly what you mean here. You might be surprised what even pretty rudimentary type systems can find if you leverage 'em right, though - as I mentioned in that other thread, I track which threads access what resources, and which functions live on which threads, with C's type system unadorned by additional checks (I do use additional static analysis, it just is entirely unnecessary to catch this one).
"Well, I realize that is false. But the hoops all of the examples I have seen that types have to jump through to accomplish that aren't exactly pleasant.
"So, maybe my preference is just that tooling can give you many of the benefits of more advanced type systems, without having to have all of the boilerplate of it everywhere in the code."
Ironically, I think my C code has more annotations for my static analysis tool-of-choice than the C types. Certainly there are a lot of poor type systems, and a lot of languages that expose particular type systems poorly. Boilerplate type annotations are mostly unnecessary in languages with type inference.
As I've said elsewhere, though, your tooling may be "more advanced type systems". Splint, for instance, can enforce linear typing - something that is not a part of the type system laid out in the C standard (or that enforced by typical C compilers, even with warnings).
"At any rate, I should say thank you very much for keeping this dialog going. Been a blast!"
Yes, it's turned out great :)