Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: @expect macro #1594

Closed
wants to merge 1 commit into from
Closed

RFC: @expect macro #1594

wants to merge 1 commit into from

Conversation

toivoh
Copy link
Contributor

@toivoh toivoh commented Nov 22, 2012

I've found the @assert macro very useful for writing assertions in a compact and readable way. The same kind of format would be useful also when the condition to be checked is not really an assertion.

I therefore propose an analogous @expect macro, to e.g. check for fulfillment of preconditions to a function call. If an @expect fails, it is the caller of the function that is to blame,
as opposed to an assertion failure, where there is a bug in the function itself.

Compare e.g. the somewhat readable code

function split_fdef(fdef::Expr)
    @expect (fdef.head == :function) || (fdef.head == :(=))
    @expect length(fdef.args) == 2
    signature, body = fdef.args
    @expect is_expr(signature, :call)
    @expect length(signature.args) >= 1
    (signature, body)
end

to the much bulkier version without @expect:

function split_fdef(fdef::Expr)
    if !(fdef.head == :function || fdef.head == :(=))
        error("need fdef.head == :function or :(=)")
    end
    if !(length(fdef.args) == 2)
        error("length(fdef.args) != 2")
    end
    signature, body = fdef.args
    if !is_expr(signature, :call)
        error("signature.head != :call") 
    end
    if !(length(signature.args) >= 1)
        error("length(signature.args) == 0")
    end
    (signature, body)
end

The latter takes longer time both to write and to read.

Do people think this is a good idea? Thoughts?

@ViralBShah
Copy link
Member

I like it.
+1

@aviks
Copy link
Member

aviks commented Nov 22, 2012

Java, for example, has different artefacts for these use cases, but for a language with macros, there doesn't seem to be much value in separating these two.

The only addition I would want in the @expects use-case is the ability to globally turn off expectation checking (for when performance is at a premium, the code is debugged, and all user input is separately sanitised). Even that seems sensible to implement within @asserts.

@toivoh
Copy link
Contributor Author

toivoh commented Nov 22, 2012

The error message is indeed the only functional difference so far. The reason that I submitted this pull request is because I think that there is an important distinction to be made in intent and interpretation between the two cases.

From the point of view of design by contract,
@expect would be for preconditions, while
@assert would be for postconditions.
For that reason,

  • a valid function should never cause an assertion failure,
    but is expected to produce an @expect or other kind of error on invalid input.
  • a unit test should always fail upon an assertion failure,
    even for a test that is expected to fail.
  • @asserts may be disabled in code that is considered to be stable,
    but @expects should never be disabled in a user-facing function.
  • @expect should be able to provide a meaningful error message to the user,
    while @assert only has to provide a message to its own writer/maintainer.

Perhaps some of these distinctions should be present in the code already.
I've submitted an extended version of this as a new pull request, where I've tried to implement what parts of the above that I could right now. Perhaps that can provide some fuel for the discussion.

@ViralBShah
Copy link
Member

Yes, it is a good idea simply for the purpose that it gives a more appropriate error message.

@timholy
Copy link
Member

timholy commented Nov 23, 2012

I'm not sure the error message is that much clearer, especially to a newbie who is unlikely to be attuned to the subtle differences between expect and assert (it's like a fine point of English grammar more than code structure). I guess I'm not convinced by this one.

@toivoh
Copy link
Contributor Author

toivoh commented Nov 23, 2012

I do think that the user deserves something clearer than assertion failed: x >= 5.
Probably the error message can be worded better than expected (x >= 5) == true. Do you have any suggestions?

I actually have an old (in julia terms :) project call failexpect (I think the most recent version is here if anyone is interested), where you could register an error message builder for a predicate. E.g.

@failexpect is_expr(ex, head::Symbol) = "expected expr(:$head,...), got :$ex" 

would make a failed @expect is_expr(arg, :call) print something like

expected expr(:call,...), got :(x+y)

I've been thinking to dust it off and see if I could make it available.
That's of course orthogonal to the @assert/@expect issue, but I think it shows that error messages written with plain expect can be made more user friendly with a little work.

@toivoh
Copy link
Contributor Author

toivoh commented Nov 23, 2012

It seems there's a bit to discuss here. Perhaps I'll take it up on julia-dev.

@toivoh toivoh closed this Nov 24, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants