[GHC] #15928: Reduction stack overflow when using "coerce"

#15928: Reduction stack overflow when using "coerce" -------------------------------------+------------------------------------- Reporter: harendra | Owner: (none) Type: bug | Status: new Priority: normal | Milestone: 8.6.3 Component: Compiler | Version: 8.6.2 Keywords: | Operating System: MacOS X Architecture: | Type of failure: Compile-time Unknown/Multiple | crash or panic Test Case: | Blocked By: Blocking: | Related Tickets: Differential Rev(s): | Wiki Page: -------------------------------------+------------------------------------- Compiling the following snippet results in a "Reduction stack overflow" error message: {{{#!hs {-# Language ScopedTypeVariables #-} {-# Language RankNTypes #-} import Data.Functor.Identity import Data.Coerce newtype Stream m a = Stream { unStream :: forall r. (Stream m a -> m r) -> m r } newtype SerialT m a = SerialT (Stream m a) g :: SerialT Identity a -> Identity Bool g m = undefined idSerial :: SerialT Identity a -> SerialT Identity a idSerial = id f :: SerialT Identity a -> Identity Bool f = g . idSerial . coerce main = undefined }}} The following error message is produced on compiling this with ghc-8.6.2: {{{ xy.hs:26:20: error: • Reduction stack overflow; size = 201 When simplifying the following type: Coercible ((Stream Identity a -> Identity r) -> Identity r) ((Stream Identity a0 -> Identity r) -> Identity r) Use -freduction-depth=0 to disable this check (any upper bound you could choose might fail unpredictably with minor updates to GHC, so disabling the check is recommended if you're sure that type checking should terminate) • In the second argument of ‘(.)’, namely ‘coerce’ In the second argument of ‘(.)’, namely ‘idSerial . coerce’ In the expression: g . idSerial . coerce | 26 | f = g . idSerial . coerce | ^^^^^^ }}} When I use an inline signature like this: {{{#!hs f :: SerialT Identity a -> Identity Bool f = g . (id :: SerialT Identity a -> SerialT Identity a) . coerce main = undefined }}} It again results in the same error: {{{ xy.hs:18:60: error: • Reduction stack overflow; size = 201 When simplifying the following type: Coercible ((Stream Identity a -> Identity r) -> Identity r) ((Stream Identity a0 -> Identity r) -> Identity r) Use -freduction-depth=0 to disable this check (any upper bound you could choose might fail unpredictably with minor updates to GHC, so disabling the check is recommended if you're sure that type checking should terminate) • In the second argument of ‘(.)’, namely ‘coerce’ In the second argument of ‘(.)’, namely ‘(id :: SerialT Identity a -> SerialT Identity a) . coerce’ In the expression: g . (id :: SerialT Identity a -> SerialT Identity a) . coerce | 18 | f = g . (id :: SerialT Identity a -> SerialT Identity a) . coerce | ^^^^^^ }}} Everything works fine is I use an inline signature with a `forall` keyword like this: {{{#!hs f :: forall a. SerialT Identity a -> Identity Bool f = g . (id :: SerialT Identity a -> SerialT Identity a) . coerce }}} I have following questions: 1) Why the first version results in a panic? Is that a bug? 2) The second version might possibly be incorrect code because the types do not unify, but still it should not result in a panic, because of the panic I could not figure out what the problem is. It took a long time to isolate the code and then do some trial and error on it. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/15928 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#15928: Reduction stack overflow when using "coerce" -------------------------------------+------------------------------------- Reporter: harendra | Owner: (none) Type: bug | Status: new Priority: normal | Milestone: 8.6.3 Component: Compiler | Version: 8.6.2 Resolution: | Keywords: Operating System: MacOS X | Architecture: Type of failure: Compile-time | Unknown/Multiple crash or panic | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by simonpj): Actually the error message is quite informative, isn't it? It says that GHC is trying to solve {{{ Coercible ((Stream Identity a -> Identity r) -> Identity r) ((Stream Identity a0 -> Identity r) -> Identity r) }}} Well the `->` and `Identity` parts are easy. That leaves us with proving {{{ Coercible (Stream Identity a) (Stream Identity a0) }}} How can we do that? Aha! Unwrap the newtype (on both sides). That givs us {{{ Coercible (forall r. (Stream Identity a -> Identity r) -> Identity r) (forall r. (Stream Identity a0 -> Identity r) -> Identity r) }}} How can we prove that? Well, we can dive under the `forall` to give {{{ Coercible ((Stream Identity a -> Identity r) -> Identity r) ((Stream Identity a0 -> Identity r) -> Identity r) }}} And now we are back where we began. This known incompleteness is described in Section 5.3.1 of [https://www.microsoft.com/en- us/research/publication/safe-zero-cost-coercions-haskell/ Safe zero-cost coercions in Haskell]. I don't know how to solve it; the reduction-stack bound gives a decent diagnosis. I grant that what seems decent to me may seem very different to a Haskell programmer. But I don't yet know how to do better. Does the explanation help? -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/15928#comment:1 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#15928: Reduction stack overflow when using "coerce" -------------------------------------+------------------------------------- Reporter: harendra | Owner: (none) Type: bug | Status: new Priority: normal | Milestone: 8.6.3 Component: Compiler | Version: 8.6.2 Resolution: | Keywords: Operating System: MacOS X | Architecture: Type of failure: Compile-time | Unknown/Multiple crash or panic | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by harendra): I think GHC needs to move on from "by GHC developers for GHC developers" to "by GHC developers for mortal Haskell programmers". You have a lot more context here than a Haskell programmer at large would have. The error needs to be resolved quickly, in a few seconds or perhaps minutes would be fine too. The error message and the documentation does not serve that purpose, it (Data.Coerce) refers to the paper and the roles wiki page (referring to papers is common theme for GHC and Haskell libraries), the paper is accessible to only a very small population, and we should not expect a large population of programmers to go read the paper and spend hours or even days or weeks trying to understand it and having to learning a lot of other things in the process. For that one error message, which can be explained better in just a few more words, it becomes a big digression. I am sorry about that rant, GHC devs are doing a terrific job, it is a wonderful technology but it has a potential to become much more usable by a lot more ordinary programmers like me if the error messages and the documentation become a little better. Keeping that aside, I still have a few questions about the issue at hand, I suspect there is a bug here: 1) Why a and a0 are treated as different types in the first version of the code? If a ~ a0 then there won't be a loop. In fact, this code is coercing a type into itself, which should be trivial. In the second version of the code where it works, the two types have been explicitly forced to be the same via a type signature. 2) This code works fine if the "Stream" type is defined using "data" instead of a "newtype". It puzzles me, why in that case we are not going into a loop? Why in that case the types a and a0 are the same? About the error message, I think it can be improved. First of all, a regular programmer won't have any idea what a "reduction stack" is, they can just guess, it is possibly a GHC internal thing. The error message is for GHC developers, not for users. The flag "-freduction-depth" is only mentioned in "Undecidable Instances section" of the guide, nowhere else. If the programmer is supposed to know about the reduction stack then it should be documented and the concept should be explained. But I guess we can do better even without doing that. The error message can perhaps be phrased something like this, it might be inaccurate, but just to give an idea: {{{ When trying to determine that the representation of type x is the same as that of type y, we expanded the types n times but still could not determine that they are equal, we can go on but we are limited by the reduction stack size which can be changed by using the -freduction-depth option. In some cases where the types are recursively defined, it is possible that the expansion forms an infinite loop that never terminates. For more details and examples see this manual page. }}} -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/15928#comment:2 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#15928: Reduction stack overflow when using "coerce" -------------------------------------+------------------------------------- Reporter: harendra | Owner: (none) Type: bug | Status: new Priority: normal | Milestone: 8.6.3 Component: Compiler | Version: 8.6.2 Resolution: | Keywords: Operating System: MacOS X | Architecture: Type of failure: Compile-time | Unknown/Multiple crash or panic | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by goldfire): Replying to [comment:2 harendra]:
I think GHC needs to move on from "by GHC developers for GHC developers" to "by GHC developers for mortal Haskell programmers". ...
I agree that we do a poor job of this. I think this needs to be more of a priority for us, which is why I recommended time spent on error messages in the recent survey put out about what to spend 6 months of developer time on. I think the only answer is to have interactive error messages, where an IDE and GHC work together to allow, e.g., a user to right-click on a term in an error message and get a definition, or can right-click on a constraint and get information about how GHC thought that the constraint should be solved. #8809 sets the stage for that future. Short of interactivity, I really don't think we can do better with an error message such as this one. The message says "stack overflow", which a functional programmer can understand as a process than never appears to be ending. It then shows what it's stuck on. Of course, GHC ''could'' show the whole cycle as Simon did above, but then the error message would get very long, which would then lead to complaints about long error messages... which is why I want interactive error messages, so everyone gets the length they want. As for references to papers: better, more accessible documentation would be fantastic. Would you (for any value of "you") care to write some? That, too, is a hard and time-consuming task.
1) Why a and a0 are treated as different types in the first version of
the code? If a ~ a0 then there won't be a loop. In fact, this code is coercing a type into itself, which should be trivial. `a` there is the type variable used in the input, as given in the type signature for `f`. GHC must figure out, though, what the type variable should be in the argument to `idSerial` (which is the same as the type variable in the argument to `g`, as we learn from `idSerial`'s type signature). GHC calls this `a0`. There's no reason for GHC to assume that `a` and `a0` should be the same, so it doesn't... and this is the crux of the problem.
2) This code works fine if the "Stream" type is defined using "data"
instead of a "newtype". Yes. That's because the loop is induced by the way that GHC "unwraps" newtypes in trying to find coercions between types. If you use `data`, this unwrapping doesn't happen.
About the error message, I think it can be improved.
These are great, concrete suggestions. Thank you! I agree with Simon that accepting the original program would be quite hard (and would require fresh computer science research), but rephrasing an error message is very doable. I propose we make this ticket about rewriting this error message, incorporating your suggestions. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/15928#comment:3 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

Would you (for any value of "you") care to write some? Considering the case "you" == "me", in fact my (tiny) personal contribution to GHC started with documentation. Though I would love to do
#15928: Reduction stack overflow when using "coerce" -------------------------------------+------------------------------------- Reporter: harendra | Owner: (none) Type: bug | Status: new Priority: normal | Milestone: 8.6.3 Component: Compiler | Version: 8.6.2 Resolution: | Keywords: Operating System: MacOS X | Architecture: Type of failure: Compile-time | Unknown/Multiple crash or panic | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by harendra): that more unfortunately due to lack of funding I cannot devote time on this.
There's no reason for GHC to assume that a and a0 should be the same, so it doesn't
I am a bit puzzled, initially I thought the same, but then I wondered why GHC treats them the same in the case when `Stream` is not a newtype. So there seems to be a contradiction here, and that is the origin of this issue, because it was working in one case but not in the other, and it was working with explicit type annotation. Let's start with the types `SerialT Identity a` and `SerialT Identity a0`. In the "data Stream" case it would get expanded to: {{{ Stream Identity a Stream Identity a0 }}} Now why does GHC think that these two types are equal if it thinks `a` is not the same as `a0`. I am sure I am missing something, so please help me understand. Maybe after the representations have been simplified GHC is able to unify a with a0. In the problematic case it is never able to simplify them in the first place so the question of unifying never comes. If ultimately `a` can anyway be unified with `a0` then can it unify them before simplifying? In the explicit annotation case perhaps that is what is happening, the programmer has already told GHC that `a` and `a0` are the same, so the simplification starts with that assumption. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/15928#comment:4 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#15928: Reduction stack overflow when using "coerce" -------------------------------------+------------------------------------- Reporter: harendra | Owner: (none) Type: bug | Status: new Priority: normal | Milestone: 8.6.3 Component: Compiler | Version: 8.6.2 Resolution: | Keywords: Operating System: MacOS X | Architecture: Type of failure: Compile-time | Unknown/Multiple crash or panic | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by harendra): I am further puzzled, as we know already, this definition works fine: {{{ data Stream m a = Stream { unStream :: forall r. (Stream m a -> m r) -> m r } }}} And this one results in an error: {{{ data Stream (m :: * -> *) a = Stop | Yield a (Stream m a) }}} {{{ xy.hs:26:20: error: • Couldn't match representation of type ‘a0’ with that of ‘a’ arising from a use of ‘coerce’ ‘a’ is a rigid type variable bound by the type signature for: f :: forall a. SerialT Identity a -> Identity Bool at xy.hs:25:1-40 • In the second argument of ‘(.)’, namely ‘coerce’ In the second argument of ‘(.)’, namely ‘idSerial . coerce’ In the expression: g . idSerial . coerce • Relevant bindings include f :: SerialT Identity a -> Identity Bool (bound at xy.hs:26:1) | 26 | f = g . idSerial . coerce | ^^^^^^ }}} If there is no bug then this needs to be explained in some simple way. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/15928#comment:5 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#15928: Reduction stack overflow when using "coerce" -------------------------------------+------------------------------------- Reporter: harendra | Owner: (none) Type: bug | Status: new Priority: normal | Milestone: 8.6.3 Component: Compiler | Version: 8.6.2 Resolution: | Keywords: Operating System: MacOS X | Architecture: Type of failure: Compile-time | Unknown/Multiple crash or panic | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by goldfire): If we ask GHC for the role signature on `Stream` (as you can with `:info` in GHCi), we learn that the `a` parameter has a phantom role. This means that `Stream Identity a` shares a representation with `Stream Identity a0` no matter what `a` and `a0` are. So GHC does not unify them. Instead, it simply doesn't care what `a0` is. It chooses `Any`. (We can see this with `-ddump-tc -fprint-typechecker-elaboration`). When you change the definition for `Stream`, `a` then gets a representational role, and so GHC is stuck trying to figure out what `a0` is, giving you the error you observe. If you say `type role Stream representational nominal`, then the program is accepted, because now GHC observes that `a0` must certainly be `a`. It does the unification and then succeeds.
If there is no bug then this needs to be explained in some simple way.
I respectfully disagree with this statement. Though simplicity is always a goal, it cannot be achieved in all cases. In the end, the `coerce` feature was added to provide a performance boost when GHC can know that runtime representations of two types are the same. That knowledge -- i.e., knowing when two types are `Coercible` -- is subtle, which is why it took the research community three papers to figure it out. Even now, as you have observed, we have room to improve. (That is, the choice between `data` and `newtype` shouldn't matter here... but it does, because we don't know an algorithm that can work reliably with recursive newtypes.) One might argue that we should have never introduced `coerce` because the theory behind it is too complicated. For better or worse, the Haskell community tends to prefer complicated, powerful solutions over simple, less expressive ones. (Of course, we love simple, powerful ones!) And so we end up where we are today. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/15928#comment:6 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#15928: Reduction stack overflow when using "coerce" -------------------------------------+------------------------------------- Reporter: harendra | Owner: (none) Type: bug | Status: new Priority: normal | Milestone: 8.6.3 Component: Compiler | Version: 8.6.2 Resolution: | Keywords: Operating System: MacOS X | Architecture: Type of failure: Compile-time | Unknown/Multiple crash or panic | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by harendra): Thanks for patiently and promptly explaining the problem. I appreciate that. Phantom role explains the inconsistency and everything falls in place. I did not realize it, to create an absolutely minimal example I removed the use of the parameter and it became phantom, it was not so in the original code. I like the `coerce` feature and I am perfectly ok with the theory as long as the behavior is documented, errors are easy to understand and the documentation is discoverable. So perhaps we can make the error message better, if you think it can be improved. Does it make sense to spit out the role information in the error message? We can also add one or more examples to explain the pitfalls/incompleteness in the user guide. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/15928#comment:7 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#15928: Improve error message: Reduction stack overflow when using "coerce" -------------------------------------+------------------------------------- Reporter: harendra | Owner: (none) Type: bug | Status: new Priority: normal | Milestone: 8.6.3 Component: Compiler | Version: 8.6.2 Resolution: | Keywords: Operating System: MacOS X | Architecture: Type of failure: Compile-time | Unknown/Multiple crash or panic | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Old description:
Compiling the following snippet results in a "Reduction stack overflow" error message:
{{{#!hs {-# Language ScopedTypeVariables #-} {-# Language RankNTypes #-}
import Data.Functor.Identity import Data.Coerce
newtype Stream m a = Stream { unStream :: forall r. (Stream m a -> m r) -> m r }
newtype SerialT m a = SerialT (Stream m a)
g :: SerialT Identity a -> Identity Bool g m = undefined
idSerial :: SerialT Identity a -> SerialT Identity a idSerial = id
f :: SerialT Identity a -> Identity Bool f = g . idSerial . coerce
main = undefined }}}
The following error message is produced on compiling this with ghc-8.6.2:
{{{ xy.hs:26:20: error: • Reduction stack overflow; size = 201 When simplifying the following type: Coercible ((Stream Identity a -> Identity r) -> Identity r) ((Stream Identity a0 -> Identity r) -> Identity r) Use -freduction-depth=0 to disable this check (any upper bound you could choose might fail unpredictably with minor updates to GHC, so disabling the check is recommended if you're sure that type checking should terminate) • In the second argument of ‘(.)’, namely ‘coerce’ In the second argument of ‘(.)’, namely ‘idSerial . coerce’ In the expression: g . idSerial . coerce | 26 | f = g . idSerial . coerce | ^^^^^^ }}}
When I use an inline signature like this:
{{{#!hs f :: SerialT Identity a -> Identity Bool f = g . (id :: SerialT Identity a -> SerialT Identity a) . coerce
main = undefined }}}
It again results in the same error:
{{{ xy.hs:18:60: error: • Reduction stack overflow; size = 201 When simplifying the following type: Coercible ((Stream Identity a -> Identity r) -> Identity r) ((Stream Identity a0 -> Identity r) -> Identity r) Use -freduction-depth=0 to disable this check (any upper bound you could choose might fail unpredictably with minor updates to GHC, so disabling the check is recommended if you're sure that type checking should terminate) • In the second argument of ‘(.)’, namely ‘coerce’ In the second argument of ‘(.)’, namely ‘(id :: SerialT Identity a -> SerialT Identity a) . coerce’ In the expression: g . (id :: SerialT Identity a -> SerialT Identity a) . coerce | 18 | f = g . (id :: SerialT Identity a -> SerialT Identity a) . coerce | ^^^^^^ }}}
Everything works fine is I use an inline signature with a `forall` keyword like this:
{{{#!hs f :: forall a. SerialT Identity a -> Identity Bool f = g . (id :: SerialT Identity a -> SerialT Identity a) . coerce }}}
I have following questions:
1) Why the first version results in a panic? Is that a bug? 2) The second version might possibly be incorrect code because the types do not unify, but still it should not result in a panic, because of the panic I could not figure out what the problem is. It took a long time to isolate the code and then do some trial and error on it.
New description: EDIT: Executive summary: The error messages below are confusing and not perspicuous to users. We should fix. comment:2 has a concrete suggestion to use as a starting point, and comment:7 suggests we print out the role signature of any tycons involved. Compiling the following snippet results in a "Reduction stack overflow" error message: {{{#!hs {-# Language ScopedTypeVariables #-} {-# Language RankNTypes #-} import Data.Functor.Identity import Data.Coerce newtype Stream m a = Stream { unStream :: forall r. (Stream m a -> m r) -> m r } newtype SerialT m a = SerialT (Stream m a) g :: SerialT Identity a -> Identity Bool g m = undefined idSerial :: SerialT Identity a -> SerialT Identity a idSerial = id f :: SerialT Identity a -> Identity Bool f = g . idSerial . coerce main = undefined }}} The following error message is produced on compiling this with ghc-8.6.2: {{{ xy.hs:26:20: error: • Reduction stack overflow; size = 201 When simplifying the following type: Coercible ((Stream Identity a -> Identity r) -> Identity r) ((Stream Identity a0 -> Identity r) -> Identity r) Use -freduction-depth=0 to disable this check (any upper bound you could choose might fail unpredictably with minor updates to GHC, so disabling the check is recommended if you're sure that type checking should terminate) • In the second argument of ‘(.)’, namely ‘coerce’ In the second argument of ‘(.)’, namely ‘idSerial . coerce’ In the expression: g . idSerial . coerce | 26 | f = g . idSerial . coerce | ^^^^^^ }}} When I use an inline signature like this: {{{#!hs f :: SerialT Identity a -> Identity Bool f = g . (id :: SerialT Identity a -> SerialT Identity a) . coerce main = undefined }}} It again results in the same error: {{{ xy.hs:18:60: error: • Reduction stack overflow; size = 201 When simplifying the following type: Coercible ((Stream Identity a -> Identity r) -> Identity r) ((Stream Identity a0 -> Identity r) -> Identity r) Use -freduction-depth=0 to disable this check (any upper bound you could choose might fail unpredictably with minor updates to GHC, so disabling the check is recommended if you're sure that type checking should terminate) • In the second argument of ‘(.)’, namely ‘coerce’ In the second argument of ‘(.)’, namely ‘(id :: SerialT Identity a -> SerialT Identity a) . coerce’ In the expression: g . (id :: SerialT Identity a -> SerialT Identity a) . coerce | 18 | f = g . (id :: SerialT Identity a -> SerialT Identity a) . coerce | ^^^^^^ }}} Everything works fine is I use an inline signature with a `forall` keyword like this: {{{#!hs f :: forall a. SerialT Identity a -> Identity Bool f = g . (id :: SerialT Identity a -> SerialT Identity a) . coerce }}} I have following questions: 1) Why the first version results in a panic? Is that a bug? 2) The second version might possibly be incorrect code because the types do not unify, but still it should not result in a panic, because of the panic I could not figure out what the problem is. It took a long time to isolate the code and then do some trial and error on it. -- Comment (by goldfire): Yes, by all means we can improve that error message, and we should leave this ticket open as an opportunity to do so. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/15928#comment:8 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#15928: Improve error message: Reduction stack overflow when using "coerce" -------------------------------------+------------------------------------- Reporter: harendra | Owner: (none) Type: bug | Status: new Priority: normal | Milestone: 8.6.3 Component: Compiler | Version: 8.6.2 Resolution: | Keywords: ErrorMessages Operating System: MacOS X | Architecture: Type of failure: Compile-time | Unknown/Multiple crash or panic | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Changes (by goldfire): * keywords: => ErrorMessages -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/15928#comment:9 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler
participants (1)
-
GHC