Open QuinnWilton opened 3 years ago
It seem like the guard be used when writing a function in Erlang, importing it into Gleam as a public function, and then calling it from Erlang. Why would one not call the original Erlang version from Erlang in this case? If called from Gleam the guards are made redundant by the type system, and if the type annotations were incorrect then the guards would also be incorrect.
If called from Gleam the guards are made redundant by the type system, and if the type annotations were incorrect then the guards would also be incorrect.
I realize now that adding guards to the arguments doesn't make any sense, because Gleam is already typechecking those. It could still be valuable to add guards for the return value though, to detect cases where the return value of the function is either incorrectly or insufficiently annotated.
I'm not sure I'm fully understanding. Could you describe a situation in which this would catch a problem? Thank you
On Sat, 12 Sep 2020, 00:44 Quinn Wilton, notifications@github.com wrote:
If called from Gleam the guards are made redundant by the type system, and if the type annotations were incorrect then the guards would also be incorrect.
I realize now though that adding guards to the arguments doesn't make any sense, because Gleam is already typechecking those. It could still be valuable to add guards for the return result though, to detect cases where the return value of the function is either incorrectly or insufficiently annotated.
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/gleam-lang/gleam/issues/789#issuecomment-691356237, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABOZVBUNOEYRDWD72G7Q5PTSFKY5NANCNFSM4RIPT3EA .
Sure!
Let's say we're writing an external for maps:find/2
. We might do this:
pub external fn find(k, Map(k, v)) -> tuple(_, v) = "maps" "find"
This annotation is incomplete, however, and if the key can't be found in the map, it returns :error
. Without any sort of guards on the return value, this :error
may end up flowing through the program and lead to a runtime error much later on in the program than where the external was first called.
With the guard, however, the error happens immediately, and it becomes easier to debug that you just have an incomplete annotation, and that you need to handle that error case when reaching into the map.
This is an interesting idea, and I like the goal. I'm not sure how useful it would in reality. For example.
How do you type the return value of maps.find? If I want to play it safe I would use dynamic as the return type and write my own function to pull it apart e.g.
case dynamic.element(ret, 0) {
Ok(tag) if tag == ok_atom -> Ok(v)
_ -> Error(Nil)
}
But if I was to set the return type as dynamic on the external function declaration, then there is no useful guard that can be added.
Another thing is that we inline external functions often, so there's nowhere to add these guards. We could stop inclining them to implement this if we think it's useful, but it does go against the general Gleam pattern of trusting the types and having as little runtime work as possible.
We could have guards for List, Int, Float, String, BitString, and tuple, I think. Maybe single variant records if we're clever.
Those are great points from both of you. Maybe it's an idea that isn't quite as useful as I thought it would be.
I think there may be something here but I'm not sure what yet. Lets move this to the suggestions tracker.
Given the following external:
I think we could generate a function that asserts that the input is a list:
With a bit more work, we could probably also assert that the function returns a list:
I think this would go a long way toward offering safer interop between Gleam and Erlang, by immediately crashing when a runtime type error occurs at the boundary between the systems.
At the very least, this is something I've been doing manually, in the opposite direction, when calling Gleam from Elixir.