Defining a Strict language pragma

Hi all, I would like to experiment with writing some modules (e.g. low-level modules that do a lot of bit twiddling) in a strict subset of Haskell. The idea is to remove boilerplate bangs (!) and instead declare the whole module strict. I believe this would both make code that needs to be strict more declarative (you say what you want once, instead of putting bangs everywhere), less noisy, and more predictable (no need to reason about laziness in places where you know you don't want it). The idea is to introduce a new language pragma {-# LANGUAGE Strict #-} that has the above described effect. The tricky part is to define the semantics of this pragma in terms of Haskell, instead of in terms of Core. While we also need the latter, we cannot describe the feature to users in terms of Core. The hard part is to precisely define the semantics, especially in the presence of separate compilation (i.e. we might import lazy functions). I'd like to get the Haskell communities input on this. Here's a strawman: * Every function application f _|_ = _|_, if f is defined in this module [1]. This also applies to data type constructors (i.e. the code acts if all fields are preceded by a bang). * lets and where clauses act like (strict) case statements. * It's still possible to define strict arguments, using "~". In essence the Haskell lazy-by-default with opt-out via "!" is replaced with strict-by-default with opt-out via "~". Thoughts? Cheers, Johan 1. I could see the very opposite choice be made: function applications to functions defined in other modules are also strict. We should try to think about the implications of either policy.

On Mon, Nov 05, 2012 at 02:52:56PM -0800, Johan Tibell wrote:
Hi all,
I would like to experiment with writing some modules (e.g. low-level modules that do a lot of bit twiddling) in a strict subset of Haskell. The idea is to remove boilerplate bangs (!) and instead declare the whole module strict. I believe this would both make code that needs to be strict more declarative (you say what you want once, instead of putting bangs everywhere), less noisy, and more predictable (no need to reason about laziness in places where you know you don't want it). The idea is to introduce a new language pragma
{-# LANGUAGE Strict #-}
that has the above described effect.
The tricky part is to define the semantics of this pragma in terms of Haskell, instead of in terms of Core. While we also need the latter, we cannot describe the feature to users in terms of Core. The hard part is to precisely define the semantics, especially in the presence of separate compilation (i.e. we might import lazy functions).
I'd like to get the Haskell communities input on this. Here's a strawman:
* Every function application f _|_ = _|_, if f is defined in this module [1]. This also applies to data type constructors (i.e. the code acts if all fields are preceded by a bang).
* lets and where clauses act like (strict) case statements.
* It's still possible to define strict arguments, using "~". In essence the Haskell lazy-by-default with opt-out via "!" is replaced with strict-by-default with opt-out via "~".
Did you mean here "it's still possible to define _lazy_ arguments"? The duality of !/~ makes sense, indeed. I personally have no idea what implications this might have, but I would be very interested to see how existing code (which doesn't require laziness) would behave when run under this new pragma. regards, iustin

On Mon, Nov 5, 2012 at 5:52 PM, Johan Tibell
The tricky part is to define the semantics of this pragma in terms of Haskell, instead of in terms of Core. While we also need the latter, we cannot describe the feature to users in terms of Core. The hard part is to precisely define the semantics, especially in the presence of separate compilation (i.e. we might import lazy functions).
I'd like to get the Haskell communities input on this. Here's a strawman:
* Every function application f _|_ = _|_, if f is defined in this module [1]. This also applies to data type constructors (i.e. the code acts if all fields are preceded by a bang).
* lets and where clauses act like (strict) case statements.
What ordering constraints will exist on let and where clauses? Is the compiler free to re-order them in dependency order? Must they be strictly evaluated in the context in which they occur? Haskell syntax readily lends itself to a style a bit like this: f x y z | p x = ... a ... b | q y = ... a ... c | otherwise = ... d ... where a = ... b = ... c = ... d = ... This tripped us up a lot in pH and Eager Haskell, where we at least wanted to be able to float d inwards and where it was sometimes surprising and costly if we missed the opportunity. But that changes the semantics if d = _|_. It's even worse if d = _|_ exactly when p x || q y. Part of the answer, I'm sure, is "don't do that", but it might mean some code ends up surprisingly less readable than you'd expect. * It's still possible to define strict arguments, using "~". In essence
the Haskell lazy-by-default with opt-out via "!" is replaced with strict-by-default with opt-out via "~".
Thoughts?
I found myself wondering about free variables of lambdas, but realized that would be handled at the point where those variables are bound (the binding will either be strict or lazy). -Jan

What if the strict code were to assume nothing is ever _|_, and result in
"undefined behavior" if it is? Kind of like a NULL pointer in C.
On Tue, Nov 6, 2012 at 8:36 AM, Jan-Willem Maessen
On Mon, Nov 5, 2012 at 5:52 PM, Johan Tibell
wrote: The tricky part is to define the semantics of this pragma in terms of Haskell, instead of in terms of Core. While we also need the latter, we cannot describe the feature to users in terms of Core. The hard part is to precisely define the semantics, especially in the presence of separate compilation (i.e. we might import lazy functions).
I'd like to get the Haskell communities input on this. Here's a strawman:
* Every function application f _|_ = _|_, if f is defined in this module [1]. This also applies to data type constructors (i.e. the code acts if all fields are preceded by a bang).
* lets and where clauses act like (strict) case statements.
What ordering constraints will exist on let and where clauses? Is the compiler free to re-order them in dependency order?
Must they be strictly evaluated in the context in which they occur? Haskell syntax readily lends itself to a style a bit like this:
f x y z | p x = ... a ... b | q y = ... a ... c | otherwise = ... d ... where a = ... b = ... c = ... d = ...
This tripped us up a lot in pH and Eager Haskell, where we at least wanted to be able to float d inwards and where it was sometimes surprising and costly if we missed the opportunity. But that changes the semantics if d = _|_. It's even worse if d = _|_ exactly when p x || q y.
Part of the answer, I'm sure, is "don't do that", but it might mean some code ends up surprisingly less readable than you'd expect.
* It's still possible to define strict arguments, using "~". In essence
the Haskell lazy-by-default with opt-out via "!" is replaced with strict-by-default with opt-out via "~".
Thoughts?
I found myself wondering about free variables of lambdas, but realized that would be handled at the point where those variables are bound (the binding will either be strict or lazy).
-Jan
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

Hi Johan (and haskell-cafe),
I work on the Racket language (but I'm also interested in Haskell so
I've been following this list and I am consistently impressed with
what the Haskell community has produced!). In Racket, we are very
interested in the problem of inter-language operation, like what you
suggested, with the goal of creating a framework that supports
programs where each module potentially uses a different language.
You might be interested in checking out Matthews and Findler's work on
interlanguage interoperability, which addresses the theory of how
languages can interoperate in a safe manner.
On the practical side, we have had success getting Typed Racket and
(untyped) Racket to safely interoperate by using contracts at module
boundaries to specify and enforce the desired program properties. See
Sam Tobin Hochstadt's dissertation for more details.
For lazy-strict language interoperation, I'm not sure if a pragma is
sufficient. Matthias Felleisen and I have looked into this but we've
run into problems since in
a higher-order language, strict and lazy functions can easily flow back
and forth between the two worlds and like you mentioned, it's not
clear how we want each to behave. For example, in a strict module, if
you define a function, you can avoid thunking the arguments when the
function is called, but what happens if you export the function and
use it in a lazy module, which passes in a thunk. Do you have to
preemptively insert bangs in your strict function to handle this? We
have considered something similar to the Typed Racket solution --
adding strictness
patterns at boundaries to describe the interactions -- analogous
to higher-order contracts -- but it is not clear if this is sufficient
to make all flows safe.
On Mon, Nov 5, 2012 at 5:52 PM, Johan Tibell
Hi all,
I would like to experiment with writing some modules (e.g. low-level modules that do a lot of bit twiddling) in a strict subset of Haskell. The idea is to remove boilerplate bangs (!) and instead declare the whole module strict. I believe this would both make code that needs to be strict more declarative (you say what you want once, instead of putting bangs everywhere), less noisy, and more predictable (no need to reason about laziness in places where you know you don't want it). The idea is to introduce a new language pragma
{-# LANGUAGE Strict #-}
that has the above described effect.
The tricky part is to define the semantics of this pragma in terms of Haskell, instead of in terms of Core. While we also need the latter, we cannot describe the feature to users in terms of Core. The hard part is to precisely define the semantics, especially in the presence of separate compilation (i.e. we might import lazy functions).
I'd like to get the Haskell communities input on this. Here's a strawman:
* Every function application f _|_ = _|_, if f is defined in this module [1]. This also applies to data type constructors (i.e. the code acts if all fields are preceded by a bang).
* lets and where clauses act like (strict) case statements.
* It's still possible to define strict arguments, using "~". In essence the Haskell lazy-by-default with opt-out via "!" is replaced with strict-by-default with opt-out via "~".
Thoughts?
Cheers, Johan
1. I could see the very opposite choice be made: function applications to functions defined in other modules are also strict. We should try to think about the implications of either policy.
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
participants (5)
-
Clark Gaebel
-
Iustin Pop
-
Jan-Willem Maessen
-
Johan Tibell
-
Stephen Chang