proj-oot-old-150618-ootErrorNotes1

"Error messages from Complex types

In Yesod we have some complex types in a few key places. And we are fine with having complex types internally. However, exposing them in an interface is a very delicate matter- users have to be able to understand error messages. Yesod has abandoned certain uses of polymorphism due to this issue. And we would still like to make error messages better.

Reading error messages is an interface through which programmers learn Haskell. Is there a way to make error messages better? The most interesting approach I can think of is to somehow let the library writer specify certain error messages. "


ArgumentCheckingException?:

--

http://docs.python.org/2/library/exceptions.html

--

interesting Scala idiom:

scala Options (like Haskell maybes) are implicitly coerced to sequences with zero elements (for None) or sequences with one element (for Some), allowing you to use flatMap (which is like concat(map())), to take a sequence, map over it producing a sequence of Options, and return only a sequence of the valid elements, e.g.

scala> val l = List(1,2,3,4,5) scala> def f(x: Int) = if (v > 2) Some(v) else None

scala> l.map(x => f(x)) res66: List[Option[Int]] = List(None, None, Some(3), Some(4), Some(5))

scala> l.flatMap(x => f(x)) res67: List[Int] = List(3, 4, 5)

-- http://www.brunton-spall.co.uk/post/2011/12/02/map-map-and-flatmap-in-scala/

--

http://250bpm.com/blog:4

 Why should I have written ZeroMQ in C, not C++ (part I)

--

http://www.drdobbs.com/cpp/generic-change-the-way-you-write-excepti/184403758

--

"

If that wasn’t bad enough, not all Java exceptions are checked, subclasses of java.Error and java.RuntimeException? are unchecked. You don’t need to declare them, just throw them. This probably started out as a good idea, null references and array subscript errors are now simple to implement in the runtime, but at the same time because every exception Java extends java.Exception any piece of code can catch it, even if it makes little sense to do so, leading to patterns like

catch (e Exception) { ignore }

"

--

" Might I even suggest going one step further and stealing the fail/succeed/cut mechanism from Icon. Go with goal-direction would be a thing of genuine beauty. "

--

http://lambda-the-ultimate.org/node/3896

--

my take on golang's exceptions: i agree with Tom Lord, the current way that Go does things is just exceptions under another name, except that 'defer' is more syntactically flexible than a 'catch' block, and except that the defer/panic/recover mechanism is slightly more confusing, esp. the way that all 'defers' are like 'finally' so you have to check 'recover' within your defer to see if you are panicing, and also because you cannot match the exceptions against a pattern in the 'defer' declaration like you can with a 'catch' block

i guess one good thing about them is that panic/recover is not a syntactic control structure.

since pattern-matching guards will be an integral part of oot, mb we can make a 'catch' without core language support

--

Dylan's exception system looks pretty good:

--

if exceptions are (mis?)-used to report expected errors, esp. errors which the caller might want to accumulate, and if scope-based resource release is in play, then the exception will release the resource even though we might have wanted to continue (alternately, the scope-based resource relase cannot be used, which is a pain):

http://stackoverflow.com/a/1744176/171761

--

" In fairness to Go, exception handling is much trickier when you live in a world of lightweight co-routines. But again, Erlang seems to generalize things in a plausible way: Declare links between co-operating processes, and propagate failures across those links until somebody wants to catch them and recover. "

" Also Haskell seems to discourage exceptions. They are possible to raise everywhere and catchable in the IO Monad --- but they do not play along nicely. It's often better to wrap up your error handling in a Maybe or Either or Error Monad. (In level of increasing sophistication.) "

---

i like the notation in the guy with the atom avatar's proposal from https://groups.google.com/forum/#!topic/golang-nuts/SskXyc9D5Sk

that is, for every Maybe type T, #T is the Just type. So #(Maybe Int) = Int

and you'd also want to add the reverse operator @V, that could take a plain type and create a Maybe: @Int = Maybe Int.

of course you don't really need @ as Maybe Int is almost as ez to write.

--

hmm.. golang has a strong convention that panic never crossed module boundaries, e.g. if you are using someone else's API you don't have to worry about panics.. could enforce that by the compiler to say that only checked exceptions may cross module boundaries.. don't make it part of the type so that you don't have the versioning problem of checked exceptions.. but wait if an underlying library throws an exception then you have to change your declaration, right? maybe you can just specify a superclass or set of superclass that covers all exceptions that you might throw, e.g. throws Exception, and that is good enough...

but i like exceptions..

--

http://www.randomhacks.net/articles/2007/03/10/haskell-8-ways-to-report-errors/ http://blog.ezyang.com/2011/08/8-ways-to-report-errors-in-haskell-revisited/ http://book.realworldhaskell.org/read/error-handling.html

---

this presentation http://www.slideshare.net/olvemaudal/deep-c (and comments) show several places where C and C++ use a different design criterion than Python's "refuse the temptation to guess". E.g.:

printf( a, b); }

also, if the standard says that source code must end with a newline, that's a bit silly imo

also, the presentation says "C has very few sequence points.This helps to maximize optimization opportunitiesfor the compiler." -- this is a tradeoff of optimization vs. principal of least surprise.

also, if you have a variable defined outside any function, it is statically allocated whether or not it has the 'static' keyword. but now the 'static' keyword is used as an access modifier to define visibility to other compilation units in the linker! 'static' means 'local to this compilation unit, not visible in this linker', and the default is 'visible to the linker, can be accessed from other compilation units'! this is confusing!

---

as far as 'refuse the temptation to guess' goes, otoh it was mighty frustrating when my logging or debugging code crashed my Python program just becuse the user had passed in Unicode but the debugging code just used "print x" (instead of "print repr(x)" which would have worked) in the logging or error-handling code, and since i hadn't set the (charset on stdout? i dont remember what i hadn't set) Python crashed when a non-ASCII character was printed.

two notes there: * 'refuse the temptation to guess' when you can catch an error at COMPILE TIME, not necessarily runtime * having str(x) be the implicit conversion, and having str(x) be unsafe while repr(x) is safe, is a bad idea. In fact, i might say that implicit type conversions should not be able to fail at runtime * we might want 'refuse the temptation to guess' be a compiler flag/section flag which is turned off in debug sections

---

we want ppl to be able to try to access a memory location and to get a maybe return type that is Nothing if accessing it would be a segfault, and a Just if the access works (or we could use exceptions, which also works for writes). on x86 platform i dont think this is direcly possible, i think the only way to figure out if something would segfault is to segfault, and then it messes up that thread permanently ( http://stackoverflow.com/questions/1955347/is-there-a-point-to-trapping-segfault ) but i think you can spawn another thread and do the memory access in that thread.

anyhow, spawning this other thread is the right thing to do but it's high-overhead. so we'd like to allow the programmer to tell us not to do it. this should be in the form of a promise that the result of accessing the memory is always a Just and never a Nothing.

so we'd need cheap syntactic sugar for promising that. Also, the routine doing the read needs to be able to ask if the result it is giving is promised to be a Just.

--

mb the doctrine should be: use Just/Nothing for pure things (like reads, computations), and use exceptions for failed mutations only.

--

need to be able to subclass Nothing to specify fine-grained error conditions in a way that allows old/ignorant code to still treat it as a normal Just/Nothing.

--

" The second observation is something I had not previously considered. In C there is no exception handling. If, as in the case of extsmail, one wants to be robust against errors, one has to handle all possible error paths oneself. This is extremely painful in one way - a huge proportion (I would guess at least 40%) of extsmail is dedicated to detecting and recovering from errors - although made easier by the fact that UNIX functions always carefully detail how and when they will fail. In other words, when one calls a function like stat in C, the documentation lists all the failure conditions; the user can then easily choose which errors conditions he wishes his program to recover from, and which are fatal to further execution (in extsmail, out of memory errors are about the only fatal errors). This is a huge difference in mind-set from exception based languages, where the typical philosophy is to write code as normal, only rarely inserting try ... catch blocks to recover from specific errors (which are only sporadically documented). Java, with its checked exceptions, takes a different approach telling the user "you must try and catch these specific exceptions when you call this function".

What I realised is that neither exception-based approach is appropriate when one wishes to make software as robust as possible. What one needs is to know exactly which errors / exceptions a function can return / raise, and then deal with each on a case-by-case basis. While it is possible that modern IDEs could (indeed, they may well do, for all I know) automatically show you some of the exceptions that a given function can raise, this can only go so far. Theoretically speaking, sub-classing and polymorphism in OO languages means that pre-compiled libraries can not be sure what exceptions a given function call may raise (since subclasses may overload functions, which can then raise different exceptions). From a practical point of view, I suspect that many functions would claim to raise so many different exceptions that the user would be overwhelmed: in contrast, the UNIX functions are very aware that they need to minimise the amount of errors that they return to the user, either by recovering from internal failure, or by grouping errors. I further suspect that many libraries that rely on exception handling would need to be substantially rewritten to reduce the number of exceptions they raise to a reasonable number. Furthermore, it is the caller of a function who needs to determine which errors are minor and can be recovered from, and which cause more fundamental problems, possibly resulting in the program exiting; checked exceptions, by forcing the caller to deal with certain exceptions, miss the point here. "

---

doug: > consider a coalescing operator rather than > unwrapping. first ?? 0 = first if first is not null, otherwise 0.

yeah i want an operator like that. i have a reminder written somewhere to put one in, i'll write a second reminder so i don't forget.

---

[Python-ideas] Please reconsider the Boolean evaluation of midnight Shai Berger shai at platonix.com Wed Mar 5 11:16:12 CET 2014

    Previous message: [Python-ideas] Python Numbers as Human Concept Decimal System
    Next message: [Python-ideas] Please reconsider the Boolean evaluation of midnight
    Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]

Hi all,

This is my first post here, following a recommendation from Alexander Belopolsky to use this list, to try to convince the Python developers to reopen a ticket. I am a long-time Python user, and a Django committer.

http://bugs.python.org/issue13936 is a complaint about the fact that midnight -- datetime.time(0,0,0) -- is evaluated as False in Boolean contexts. It was closed as invalid, under the claim that """It is odd, but really no odder than "zero values" of other types evaluating to false in Boolean contexts""".

I would like to ask for this to be reconsidered; since the ticket was closed, two main arguments were given for this:

1) The practical argument (paraphrasing Danilo Bergen and Andreas Pelme): The current semantics is surprising and useless; users do not expect valid times to be falsey, and do not normally want code to have special behavior on midnight. Users who ask for Boolean evaluation of a variable that's supposed to hold a time value, usually write "if var" as shorthand for "if var is not None".

2) The principled argument (which I think is at the root of the practical argument); quoting myself from the ticket: """Midnight is not a "zero value", it is just a value. It does not have any special qualities analogous to those of 0, "", or the empty set. ... Midnight evaluting to false makes as much sense as date(1,1,1) -- the minimal valid date value -- evaluating to false""".

Thanks, Shai.

---

clarkevans 2 days ago

link

INADA Naoki's argument [1] is succinct and insightful.

  I feel zero value of non abelian group should not mean
  False in bool context.
  () + () == ()
  "" + "" == ""
  0 + 0 == 0
  timedelta() + timedelta() == timedelta()
  time() + time() => TypeError

[1] https://mail.python.org/pipermail/python-ideas/2014-March/02...

reply

StefanKarpinski? 2 days ago

link

This is a nice clean criterion, but it's still pretty unclear to me why the zero element of a monoid should be considered falsey at all.

reply

chrismonsanto 2 days ago

link

Because when writing a recursive function, the zero element is generally the base case.

    if x:
        <destructure x, recurse on subparts>
    else:
        return <something>

This applies to (natural) numbers as well--'destructuring' usually means decrementing the number.

reply

dalke 2 days ago

link

If that were true, it would mean that dict should not support bool:

    >>> {} + {}
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: unsupported operand type(s) for +: 'dict' and 'dict'
    >>> bool({})
    False

Since I find bool(a_dict) to be very useful, and the lack of a_dict+b_dict to be reasonable (precisely because it's non-abelian!), I conclude that logic is cute, but not that relevant.

Those who think it this argument is pursuasive, should bool({}) raise an exception? Or should {1:"A"}+{1:"B"} not raise an exception and instead return ... what?

Or do I misunderstand something?

reply

baddox 1 day ago

link

Honestly, I would be fine with nothing supporting bool, and requiring `if` conditions to be bools. Arrays and dictionaries could have methods like `.empty?` that return a bool. Strings could have `.blank?`. Obviously, I'm stealing the methods from Ruby, although Ruby also has its own notions of truthiness of non-booleans.

reply

dalke 1 day ago

link

There are many languages which do something like that. Python is not one of them. Personally, I find it useful to write things like "if x:" and not worry about if x is None or x is s list of characters or x is a string of characters.

reply

--

what is so annoying about writing latex documents? it's that you shouldn't have the possibility of a compilation error when you are just trying to write a document. so this is perhaps one case in which you'd prefer the compiler to recover upon an error instead of crashing upon an error. you'd want to latex document to insert a 'i dont understand this construct' and to still show the rest. but how to do that?

perhaps erlang can give us some clues, since they seem to have a system where processes can crash but this is isolated from the rest of the system, which keeps on ticking.

--

on Java's new Optional:

rat87 1 day ago

link

...

There's no equivalent of scala's orElse(returning a second optional if the first one is null) and since default methods while useful don't allow adding things to types you don't own you can't use an method like notation for the best you can get is static importing and wrapping a function around it). ofNullable(creating an empty Optional from a null instead of throwing) isn't the default(of which throws is). Also Optional isn't an Iterator which is disappointing since using it with a for loop can be useful if you want to avoid nested closures.

meddlepal 2 days ago

link

My problem with Optional<T> is that it basically makes overloaded Java methods impossible because of erasure. I'm worried that it is going to start getting overused; the JDK developers really only intended for its use in the Streams API.

reply

tel 2 days ago

link

Could you explain that a little bit more? I'm using to Maybe from Haskell and am curious what might be missing from Java's Optional.

reply

matwood 2 days ago

link

Basically, because of type erasure you cannot have overloaded methods using different Optional types. So test(Optional<A> a); is the same method as test(Optional b);

I think you can define the types to get around it, but that is messy and a PITA. For example, class OptionalA? extends Optional<A>.

reply

tel 2 days ago

link

That doesn't seem so bad to my eye, though. In Haskell terms it just means that any overloaded operators on Optional have to respect parametricity. If you need something more specifically typed then you can just use a new name.

Is there a particular use case?

reply

tomp 2 days ago

link

Btw, type erasure is only a problem for Java because it supports runtime type information (which is incomplete, because of type erasure), and overloading (which I have no idea why the compiler can't handle). For languages like ML and Haskell, type erasure is no problem, because you can't access the types at runtime anyways.

reply

dignati 2 days ago

link

Wow that's a real pain point. Why would they even do that, is there a use case?

reply

im3w1l 2 days ago

link

In my opinion, no it really isn't. You can just create a method with a different name or a static constructing method if you need to overload constructors.

The major pain point with generics is that they don't work with primitives.

reply

im3w1l 2 days ago

link

Because of type erasure, all Optional<T> are the same type at runtime, so Optional<Integer> can't be overloaded with Optional<Boolean>, because at runtime it will be impossible to tell which method should be caled.

reply

matwood 2 days ago

link

For those still on pre Java 8, Google's Guava also adds Optional and other useful things like immutable collections.

reply

maaaats 2 days ago

link

And now frameworks can finally supply Optionals, since it before only lead to multiple competing implementations.

reply

mike_hearn 2 days ago

link

But is it really better than @Nullable?

reply

jburwell 2 days ago

link

Completely different concepts. @Nullable expresses an precondition indicating whether or not something can be null. One example of its use is by static analyzers such as Findbugs to find misuse to APIs. In contrast, Optional encapsulates the behavior when something is not present (or null) -- very similar to the Null Object pattern [1]. Used together, you can build an API that permits encapsulates the present/not present behavior and ensure that it is not passed a null reference to the Optional instance.

[1]: http://en.wikipedia.org/wiki/Null_Object_pattern

reply

vog 2 days ago

link

It is a pity that Qt is not mentioned in that article. AFAIK, It is the only widely-used C++ library that implements null objects consequently. Ever wondered why the Qt framework doesn't use any exceptions or C-style error codes? It is solely because of proper use of Null objects.

reply

hutteman 2 days ago

link

I would say in most cases: no.

Due to backwards compatibility concerns, no existing Java APIs that may currently return null can ever be changed to return Optionals instead, so nulls need to be dealt with regardless. Also, there's technically nothing stopping an Optional value from actually being null itself, or from developers calling Optional.get() without checking isPresent(), just like they might currently use a reference without checking for null.

I personally really wish jsr305 had been adopted in Java8, and their existing APIs retrofitted with these annotations.

http://www.oracle.com/technetwork/articles/java/java8-option... describes Optional, and compares it to how in Groovy, the safe navigation and elvis operators allow you to write code like this:

   String version = computer?.getSoundcard()?.getUSB()?.getVersion() ?: "UNKNOWN";

With Java 8 Optionals, this instead becomes:

   String version = computer.flatMap(Computer::getSoundcard)
                            .flatMap(Soundcard::getUSB)
                            .map(USB::getVersion)
                            .orElse("UNKNOWN");

which is not only much longer and arguably uglier, but also forces developers to suddenly have to worry about applying lambdas using flatmap vs map, where conceptually you're really just dealing with method calls.

New languages like Kotlin solve this much better in my opinion by introducing nullable vs nonnullable types. While Java can unfortunately never adopt this due to backwards compatibility issues, @Nullable and @Nonnull will do most of the time, and hopefully we'll see operators like ?. and ?: in future versions of Java.

reply

hibikir 2 days ago

link

While I've enjoyed the Elvis operator in Groovy, the point of optional is to make null go away altogether. In Groovy, you are never guaranteed anything isn't null, so the end result is that you will Elvis everything. While this is easier than the good old if statements, you are still paying for the penalty of a billion ifs in bytecode.

With option, you clearly declare what is safe, and what isn't, and the compiler won't let you screw up. You can decide which layer of your code handles the empty case, and lots of unnecessary ifs go away from the bytecode itself, so the app will run faster, and you know when you have to even consider the null case.

Now, doing that in Groovy is rather silly, because your language is dynamic and your types are optional, so all of this compile time safety would not provide any value anyway. Option is just the way you'd solve the problem in the strongly typed way. It's how you handle it in Scala, for instance, and how Haskell would deal with it.

As far as using flatmaps and maps to work with optionals, yes, it's a chore. I'd argue it's borderline abuse of the stream operators, as treating Option as a collection is ugly. That said, putting yourself in a situation where you chain 3 optional getters is also object design from hell, so one should avoid putting themselves in that position altogether.

In Scala, instead of flatmapping single optionals, we often getOrElse(), or use pattern matching as an extractor. Now that's a feature I would like to see Java 'steal' from the Scalas and Erlangs of the world, but I do not see that happening.

reply

vorg 1 day ago

link

> in Groovy, the safe navigation and elvis operators

The elvis op is called the null coalescing op in other languages. [1] Groovy's promoter chose the elvis name to fit in with marketing the Groovy and G-String names.

PHP also uses the ?: symbol but other languages use different ones, e.g. C# uses ?? and Perl uses

[1] http://en.wikipedia.org/wiki/Null_coalescing_operator

reply


if an exception handler wants to 'resume', then the error condition which caused the exception should be rechecked. But then we also need an infinite-loop detection that does not recheck it more than once (unless this loop detection mechanism is explicitly overridden).

---

---

" Rust let i = box 1234i;

C++ int *i = new int;

Rust infers the correct type, allocates the correct amount of memory and sets it to the value you asked for. This means that it's impossible to allocate uninitialized memory: Rust does not have the concept of null. " -- http://doc.rust-lang.org/master/intro.html

from my plBook:

"What value should a new variable or region of memory have just after it is allocated?

Some choices:

rust appears to have the 4th solution. I like that, let's do that.

---

" The problem is that the null reference has been overloaded to mean at least seven different things:

    a semantic non-value, such as a property that is applicable to some objects but not others;
    a missing value, such as the result of looking up a key that is not present in a key-value store, or the input used to request deletion of a key;
    a sentinel representing the end of a sequence of values;
    a placeholder for an object that has yet to be initialized or has already been destroyed, and therefore cannot be used at this time;
    a shortcut for requesting some default or previous value;
    an invalid result indicating some exceptional or error condition;
    or, most commonly, just a special case to be tested for and rejected, or to be ignored and passed on where it will cause unpredictable problems somewhere else.

Since the null reference is a legitimate value of every object type as far as the language is concerned, the compiler has no way to distinguish between these different uses. The programmer is left with the responsibility to keep them straight, and it’s all too easy to occasionally slip up or forget. " -- Anders Kaseorg, http://qr.ae/CS2A6

---

each function should return, one way or another, one or more results (return arguments), and also a success/failure/error-message value. Instead of a std return argument, like in Golang, we could use the exception paradigm.

However, ultimately we need to be able to switch between these, e.g. to easily add wrappers that turn a function using the exception paradigm into one returning a success code+possible error message, and vice versa.

---

the impurity in oot debug mode functions doesn't affect oot's implicit memoization of lazy functions, or oot's trellis-like spreadsheet-like recomputation of dependent values, or cause transaction rollbacks

and errors in debug mode don't cause a crash

in fact, i think these are attributes that we want in error handling in general. So, error handlers are run in debug mode.

This distinguishes errors from exceptions: exceptions are a special case of events, such that the call chain implies (at least part of) the handler tree for exceptions. Errors are a special case of exceptions, for which any handlers are implicitly run in debug mode.

So, for functions in debug mode, or error handlers, Oot doesn't have to (a) call a function with a debug-mode side-effect each time its called, if it can just memoize the result of the function after calling it once, (b) crash when there's an error in debug-mode, (c) rollback when there's an error or conflict in debug-mode, (d) call a computed attribute with a debug-mode portion even when stuff it depends upon has not changed.

---

to deal with distributed systems:

maybe have two basic error conditions, for three result conditions in total:

(1) success (2) clean failure (3) success or failure unknown; also, partial success possible

so, if a side-effectful RPC was sent but there was a network failure that prevents us from knowing if the RPC request was received or not, (3) would be returned.

---

---

ok, this is crazy:

http://rachelbythebay.com/w/2014/08/19/fork/

similar:

cperciva 3 hours ago

link

This reminds me of one of the most epic bugs I've ever run into:

    mkdir("/foo", 0700);
    chdir("/foo");
    recursively_delete_everything_in_current_directory();

Running as root, this usually worked fine: It would create a directory, move into it, and clean out any garbage left behind by a previous run before doing anything new.

Running as non-root, the mkdir failed, the chdir failed, and it started eating my home directory.

reply

swah 3 hours ago

link

In those times I wish I could use the emacs lisp way:

    (let (dir "/foo")
      (create-directory dir)
      (with-current-directory dir
        (delete-all-files-recursively)))

Factor recognized the value of dynamically scoped variables: http://concatenative.org/wiki/view/Factor/FAQ/What's%20Facto...

A lot of code became much simpler because of that decision.

reply

staunch 2 hours ago

link
  $ mkdir /tmp/foo && cd /tmp/foo && touch bar.txt

reply

---

jwise0 3 hours ago

link

In a similar family, note also that setuid() can fail! If you try to setuid() to a user that has has reached their ulimit for number of processes, then setuid() will fail, just like fork() would for that user.

This is a classic way to get your application exploited. Google did it (at least) twice in Android: once in ADB [1], and once in Zygote [2]. Both resulted in escalation.

Check your return values! All of them!

[1] http://thesnkchrmr.wordpress.com/2011/03/24/rageagainsttheca... [2] https://github.com/unrevoked/zysploit

reply

agwa 3 hours ago

link

Thankfully, setuid() no longer fails on Linux because of RLIMIT_NPROC, as of 2011[1].

Still, I agree with you 100%: check your syscall return values, especially security-critical syscalls like setuid!

[1] http://lwn.net/Articles/451985/ and http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.g...

reply

spudlyo 3 hours ago

link

If a function be advertised to return an error code in the event of difficulties, thou shalt check for that code, yea, even though the checks triple the size of thy code and produce aches in thy typing fingers, for if thou thinkest "it cannot happen to me", the gods shall surely punish thee for thy arrogance. [0]

[0]: http://www.lysator.liu.se/c/ten-commandments.html

reply

---

http://www.lysator.liu.se/c/ten-commandments.html

---

mutation 3 hours ago

link

Just noticed that in Perl the behavior is slightly different: http://perldoc.perl.org/functions/fork.html unsuccessful fork() returns undef, effectively stopping you from kill-ing what you don't want to kill.

reply

takeda 2 hours ago

link

Similarly python throws an exception, and I bet other languages have their own behaviors, but in case of C this is the only way (or at least it is the only non complicated way to do it).

When I read this article I thought it was preaching to a choir. I'm actually quite surprised people programming C don't check for errors. That's the only way the functions can provide a feedback.

reply

--

perhaps we should have a sentinel for 'this information is stored externally, we can get it but only via a query'?

--

https://news.ycombinator.com/item?id=8486340 says powershell has "exceptions and Write-Error", plus different streams for warnings, debug output, and verbose output. just to give another idea for how many loglevels we need

--

loglevels from atr (? or did i make these up here?):

logging: ALL, VERBOSEDEBUG, DEBUG, DETAILEDINFO, INFO, REPORT, WARNING, ERROR, CRITICAL suggested usages:

  ALL: reports all calls to log
  DEBUG: useful almost exclusively when debugging
   DEBUG1, DEBUG2, DEBUG3
   VERBOSEDEBUG, NORMALDEBUG, SUMMARYDEBUG
  INFO: info reported to the user during the normal operation of the program, in ascending order of importance:
   INFO1, INFO2, INFO3
   VERBOSEINFO, NORMALINFO, SUMMARYINFO
  MINOR: stuff that is abnormal but not too important:
   MINOR1, MINOR2, MINOR3
   WARNING: not an error, but a warning
   TRIVIALERROR: technically an error, but it should usually be disregarded
   MINORERROR: an error that is not very important
  MAJOR: important errors:
   MAJOR1, MAJOR2, MAJOR3
   ERROR: an ordinary error
   MAJORERROR: a big error, the sort which an autonomous system would probably email someone about
   CRITICAL: a super important error, the sort which should result in paging someone

or could just use:

logging.DEBUG = 10 logging.DEBUG2 = 15 logging.DEBUG3 = 17

logging.INFO = 10 logging.INFO2 = 15 logging.INFO3 = 17

logging.WARN = 10 logging.WARN2 = 15 logging.WARN3 = 17

logging.ERROR = 10 logging.ERROR2 = 15 logging.ERROR3 = 17

logging.CRITICAL = 10 logging.CRITICAL2 = 15 logging.CRITICAL3 = 17

---

here's a Rust example from https://medium.com/@adamhjk/rust-and-go-e18d511fbd95 which shows the same option type boilerplate as you find in Haskell:

let have_dot_git = have_dot_git(cwd.clone());

let dot_git_dir: &Path = match have_dot_git.as_ref() { Some(path) => path, None => { panic!("{} does not appear to have a controlling .git directory; you are not in a git repository!", cwd.display()) }, };

this is what we want to get rid of with our ' syntax. e.g something like

let dot_git_dir: &Path = have_dot_git.as_ref()'#1

...

  1. 1 "{} does not appear to have a controlling .git directory; you are not in a git repository!", cwd.display()

where the a'b is syntactic sugar for

match a { Some(c) => c, None => { panic(#1) } };

and #1 is a footnote

(note: remember though they we want to handle stuff like Haskell's Either, where the None has more information attached, too)

in other words, a'b converts an Option a into its Some value, unless it's None, in which case an Exception built from b is thrown

the dual of this is 'a, which evaluates expression a and returns Some(a), unless the evaluation threw an exception, in which case it returns None (except not actually only None, but rather some exceptiony thing that is like None but captures the data in the Exception too; maybe just an Exception)

---

note: compare our ' syntax to Swift's option syntax:

http://www.drewag.me/posts/what-is-an-optional-in-swift

their a! is kind of like our a'

although in type construction we'll probably just use ', e.g. String', instead of how Swift uses '?'

---

are these a good idea?

http://www.drewag.me/posts/uses-for-implicitly-unwrapped-optionals-in-swift

http://stackoverflow.com/questions/24006975/why-create-implicitly-unwrapped-optionals

---

some common log levels:

https://www.google.com/search?client=ubuntu&channel=fs&q=info+debug+warning&ie=utf-8&oe=utf-8

---

both 'maybe'/'option' types and nullable types have the idea that the value of the variable can be some sort of 'null', or it can be an ordinary value; so how do they differ?

(1) with nullable types, by default if you are passed in a 'null', you get a 'null' out and you move on; with maybe/option types, you can explicitly choose to do it this way (eg in Haskell, within a Maybe monad, this is what happens), but the default is that the typechecker forces you to branch on null or not null and decide what to do if there is a null coming in

(2) you can have a maybe/option 'tower'; eg a Maybe wrapping another Maybe. If something goes wrong, the resulting value will tell you at which level of the tower (that is, at which point in the computation) the null came from; whereas with nullable types, it's like this tower is 'flattened'; a null anywhere in the tower means that the flattened value is null, otherwise the flattened value is the same as the value at the bottom of the maybe/option tower

so, for the purposes of (2), a nullable type can be thought of as a flattened maybe

---

oot needs a generic error to say 'you attempted to compute a value which is a member of the idealized data type you have, however limitations in the undeerlying representation mean we can't store it". eg if you try to store or compute a value greater than MAXINT into an int; if you try to create an array whose length is greater than the max value of the length header; etc. can avoid by using bignums and fixed point/rationals. (true transcendentals not represnetable in oot! except by symbolic math libs, i guess). also some types/situations silently round, like float.

---

funny Apple C compiler errors: http://www.ralentz.com/old/mac/humor/mpw-c-errors.html

---

maybe 'timeout' try/catch; more generally, a way to make custom try/catchs like that

---

copied from [1]:

defer is somewhat similar to 'finally' in languages with try/catch/finally, but one important difference is that the scope of 'defer' is a function, whereas the scope of a 'finally' is a try/catch block, and in those languages there can be many try/catch blocks in one function; so Golang is trying to force you to write small functions that only do one thing [2]. Otoh, the 'finally' in try/catch/finally must be in the same lexical scope as the 'try', whereas the 'defer' can appear in any lexical scope, but its body will not be run until the end of the current function scope. This means that multiple 'defer's may be encountered before any of their bodies is run. In this case, the bodies of the defers are placed onto a LIFO stack. An example, from [3]: this function prints "3210":

func b() {
    for i := 0; i < 4; i++ {
        defer fmt.Print(i)
    }

This enables many instances of a resource to be acquired within an inner scope, and then not disposed of until the end of the function. This is unlike try/catch/finally or Python's 'with', in which the disposal must be done in the same scope in which the resource was acquired.

--

the previous suggests that 'defer' is a more flexible way of disposing of resources than either try/catch/finally, or Python's 'with' (which can be seen as sugar for try/finally). So maybe we should have something like 'defer' instead of those others. But we don't really want to privilege function scope so much. So how about either (a) we let 'defer' take a label as an argument, or (b) the time when we execute the deferred bodies is explict.

we can sort of combine those by putting the label near the end of the scope instead of the beginning. We also want some syntax to 'quote' the label for passing into a function, so that we can have eg an 'open file' function with a defer inside of it, eg (where 'l .+ a' means 'append a at the end of list l'):

fh = [] for i 1..5 { fhs .+ openFile("f%s.txt" % i, 'LABELA) } doStuffToFiles(fhs) LABELA

the 'LABELA would be an optional argument to openFile; if none is passed in, the file closes at the end of the current scope, like Python 'with'

--

what i find interesting in the following quote is the distinction between handling an exception and unwinding the stack, along with (my additional though) that 'defer' and 'finally' quite properly is triggered by stack unwinding, not just by exceptions:

" Condition systems

FWIW, my favorite solutions to error handling are the condition systems of Common Lisp and Dylan.

They separate the notions of (a) finding a handler and letting it handle an exception from (b) unwinding the stack.

I find any error handling system that doesn't do that tedious at best, and flawed at worst. By Manuel J. Simoni at Tue, 2013-05-07 08:56

Dylan
reply

I actually look into Dylan on error handling, its "block()...end" was partially the reason I added "framed block" in my language, which effectively allows to define a handler (a deferred block in a framed block) anywhere in the codes. By Limin Fu at Tue, 2013-05-07 10:15

block and handler
reply

In Dylan, there are two ways to define handlers, either with block .. exception - or with let handler (DRM: Signalers, Conditions, and Handlers).

For me it sounds like these are the two different approaches mentioned here (try-catch vs defer-recover) - or am I mistaken?

In my own experience, I find the try-catch (or block-exception) mechanism much more readable. By hannes at Sat, 2013-05-11 00:17

reply

"

--

[4] : dylan has a pattern matching, guarded handler chain, like oot (well, we have a tree not just a chain); they add 2 things;

--

cumbersome? or good?:

def _defaultKeyValueHistoryErrorHandler(ticker): (exc_type, exc_value, exc_traceback) = sys.exc_info() if isinstance(exc_value, KeyboardInterrupt?): raise else: ticker.log.warn('Exception when appending to CassandraTimeSeries?', exc_info=True)

shouldn't something like the following just be the default when you 'raise'?

import logging logging.error('Exception when appending to CassandraTimeSeries?', exc_info=True)

        except:
            logging.error('add_edge_of_ishs_to_expr_pipeline2: failed to process %s' % filepath1)

--

some error handling strategies:

golang presents another alternative:

do we want to consider using this as another option for our ' syntax? i doubt it; if you have an error code as a return value, that doesn't make your program structure much different from nullable types; in each case, you have to have an if/then after the potentially-error-producing call, in one case checking the "x is not None", in the other checking that the error code is 0. And nullable types are more powerful in the sense that you can just leave the nil in the variable for later.

--

ilitirit 8 hours ago

I like Rust, and I'm probably missing something, but do I feel that some things should be easier. At times the language (or standard library, I suppose) feels a bit unwieldy. For example, trying to coerce a string from certain types:

    let path = Path::new(&app_path)
    println!("file name: {}", path.file_name().unwrap().to_str().unwrap());

Hopefully this is just a case of unfamiliarity, but surely something like should be easier?

reply

Jweb_Guru 7 hours ago

If you were willing to allow for potential escape characters and acknowledge (to the user) the fact that the path might not have a file name, you could write:

    println!("file name: {:?}", path.file_name());

or just for escape characters, but only if the file name existed:

    if let Some(name) = path.file_name() {
        println!("file name: {:?}", name);
    }

Note that the unwrap() isn't boilerplate, in either case: if you are using `unwrap()` you are implicitly saying that you know for a fact that the file name is present (the first time) and a valid UTF-8 string (the second time), which isn't guaranteed in the general case on all platforms. If you wanted to write this more robustly, and these weren't guaranteed, you would need to explicitly take care of those cases.

I/O has lots of tricky error cases, and Rust usually requires you to acknowledge that. You can avoid dealing with those cases with unwrap(), or you can explicitly handle them. Personally, I much prefer this to being surprised only when the weird error case unexpectedly turns up because I never considered the situation in question, and that's generally how Rust handles things, but it depends on the task. It may make Rust less appropriate for quick scripting tasks, for example, where the domain is well-defined and the script is not likely to be reused (but I find that those scripts have an annoying way of getting reused anyway).

reply

drchickensalad 5 hours ago

However, rust would be relatively well-suited for a "dirty quick scripting task". You could make a more traditional wrapper for the operations you like to use that assumes these rare edge cases don't happen, and panic otherwise.

This is sort of rust's philosophy; being explicit. Rust has no problem with failing, it just has a problem with failing without you telling it to. If you write a wrapper that implies you are willing to fail on these cases, then you're being explicit by choosing to use these functions instead :)

reply

masklinn 6 hours ago

> surely something like should be easier?

That would be nice, but as others hinted that wouldn't actually work:

The Rust philosophy is generally to expose these issues upfront and force the developer to consider them (and either handle or explicitly ignore them). It is indubitably less convenient than e.g. Python 3's `PurePath?(app_path).name` returning an str, but you'll have had to decide what happens in case of failure.

Paths are really a good example of something which looks simple, should be simple, and is actually anything but simple for historical and technical reasons, especially in a cross-platform context.

[0]

reply

WallWextra? 5 hours ago

Also, OSX applies unicode normalization to the filenames, which can cause some interoperability problems with unix, e.g. you try to copy files forth and back between the systems and end up with two copies of a file, with identical-looking filenames that differ in their unicode normal form.

reply

Manishearth 7 hours ago

To add to the other comments, Rust prefers to make error cases explicit. We have memory safety. You can opt out of it, but you have to opt out explicitly. We have exhaustive match. You can use a wildcard, but you need to explicitly do so. We have nullables and Results. You need to explicitly acknowledge the error/null case, or use `unwrap()` to say that you don't mind a panic at this point.

This leads to verbosity at times, but usually its a good verbosity.

reply

mrec 3 hours ago

I'm sure I'm not the first (or the hundredth) to say this, but `unwrap` has always struck me as a horribly innocuous spelling of "yes, feel free to crash here".

I presume there is (or could be) a standard lint to prevent this getting into production code, but it still feels kind of icky.

reply

steveklabnik 2 hours ago

We almost changed it to `assert` but that made some people _really_ upset.

reply

steveklabnik 7 hours ago

Yes, it's true. Code like this is going to be more verbose. Often, it's due to Rust being a systems language and exposing low-level details to you, combined with the type system ensuring safety. In this instance, `Path` is actually fairly complex. From the RFC: https://github.com/rust-lang/rfcs/blob/master/text/0474-path...

        > The design of a path abstraction is surprisingly hard. Paths work
        > radically differently on different platforms, so providing a
        > cross-platform abstraction is challenging. On some platforms, paths
        > are not required to be in Unicode, posing ergonomic and semantic
        > difficulties for a Rust API. These difficulties are compounded if one
        > also tries to provide efficient path manipulation that does not, for
        > example, require extraneous copying. And, of course, the API should
        > be easy and pleasant to use.

So, in this case, you have to call `file_name` to get the filename, but that returns an `Option`, since it might not be a file, it could be a directory. The `unwrap` says "crash if it's not a filename." You then have an `OsStr?`, which is a string type that handles the kinds of cross-platform issues alluded to in the above paragraph. `to_str` will then attempt to turn it into a `&str`, but because `&str` must be unicode, if the conversion fails, it _also_ returns an Option, which is the last unwrap.

In systems programming, details matter. As such, our stdlib APIs try to expose those details. I expect some people will write higher-level abstractions that hide all this if you don't care as much.

Tradeoffs tradeoffs tradeoffs...

reply

jerf 2 hours ago

"Often, it's due to Rust being a systems language and exposing low-level details to you, combined with the type system ensuring safety."

I'm not convinced it's being a "systems language" (in the sense I think you mean). The last three languages I've learned to fluency have all, in various ways, been very concerned about errors: Erlang, Haskell, and Go. Yes, they have widely varying opinions about what to do with them, but they all agree that you have to be fairly constantly worried about them. You can't sweep them under the rug; you need a strategy, and you need to deal with the resulting issues head on.

Despite their differences, what I feel like I've learned from them is that historically speaking, the vast majority of popular languages and their communities have been downright cavalier about errors. It's often in all the little details rather than a consistent, stated design goal, but the presence of various nils and nulls, APIs that will happily return empty strings for paths instead of errors, and all kinds of APIs and libraries that seem to be subconsciously written with the belief that returning an error is a failure on the part of the library and will go to what now seem to me to be absurd lengths to somehow keep klunking along in the presence of already-manifested failure. Even for languages that think they're being really good with exceptions, I find that when I go back to them, they end up using the exception support to "permit" themselves to be sloppy and hope the exceptions will pick up the pieces... and they often don't. Not because of some theoretical weakness, but because allowing developers to think they don't have to think about the error cases means precisely that they don't!

People are used to errors not poking them in the face until deploy time. It seems to me there's a strong, broad motion towards developing languages that will in one way or another force you to deal with them at compile time. It's a culture shock for people, but, I'll tell you this, get really good at Rust or any of these other languages and go back to any of those other languages, and you will find your code changes in those other languages to suddenly look a lot more like your Rust does anyhow, which means this putative disadvantage of Rust evaporates anyhow. Once you see how sloppy you were being, it's hard to go back in good conscience.

reply

RickHull? 7 minutes ago

Great comment. I have run into this problem exactly in inherited codebases at my last couple of jobs:

> libraries that seem to be subconsciously written with the belief that returning an error is a failure on the part of the library and will go to what now seem to me to be absurd lengths to somehow keep klunking along in the presence of already-manifested failure.

It's a big problem with hairy repercussions if this idea is allowed to take root.

reply

steveklabnik 2 hours ago

> The last three languages I've learned to fluency have all, in various ways, been very concerned about errors

Yeah, maybe I should revise my statement a bit: systems languages, overall, tend to expose more details. I meant this in a broader context than errors. As a simple example, manual memory management exposes more details than GC. There are many languages that care dearly about errors, as you've rightfully pointed out.

I broadly agree with the rest of your post. Error handling is hard and tedious, and programmers don't like doing it. I myself had an embarrassing bug in `rustbook` where I did `let _ =`. I only intended to start off that way! Programming is hard. But using Haskell, Rust, and other languages like this have certainly changed my code in other languages.

reply

phaylon 7 hours ago

I would say it is familiarity, but in another sense. You can use something like

    path.file_name().and_then(|f| f.to_str()).unwrap()

but that really doesn't buy you much.

Where familiarity comes in I believe is in what that line communicates to me: There might not be a file name (like having '/' as path), and that getting a string slice out of it might fail (the kind of string the platform uses for paths might not be UTF-8 compatible).

Plus, the unwrap()s tell me that whoever wrote the code considered it an assertion failure if one of those is happening.

I read a lot more Rust than I write, and it really helps that the code communicates a lot. And with knowing about OsString?/OsStr? beforehand, I only had to lookup when file_name() returns None, the rest is rather implicit by convention, or familiarity.

reply

tikue_ 2 hours ago

I wish `and_then` were named `flat_map` so badly.

reply

doomrobo 7 hours ago

That would be:

    let path = Path::new(&app_path);
    println!("file name: {}", path.display());

reply

ilitirit 7 hours ago

No, I just want the file name.

reply

---