New type of ($) operator in GHC 8.0 is problematic

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

New type of ($) operator in GHC 8.0 is problematic

Theodore Lief Gannon
(Sorry you got this twice, Kyle!)

I agree wholeheartedly with this:

> Its extremely important to not lose touch with the people that make the community; the newcomers. Sacrificing the 99% of beginner and intermediate haskellers for the 1%, I believe is a step in the wrong direction.

But also with this:

> There's a real drawback to flags like -fdefault-levity: they hide things from unsuspecting users. We already get a steady trickle of bug reports stemming from confusion around hidden kinds. Users diligently try to make a minimal test case and then someone has to point out that the user is wrong. It's a waste of time and, I'm sure, is frustrating for users. I'm worried about this problem getting worse.

I've been learning Haskell for about a year and a half, and using it in production for roughly a third of that. More than once I've run into a language construct which struck me as odd, asked about it, and was told it was that way for pedagogical reasons. I consider this a poor state of affairs on all sides, including pedagogy! I had a correct intuition that something was amiss, but here's the language itself sewing unjustified doubt about my understanding. It was discouraging.

Lowering the garden wall is good, but not at the expense of hiding a hedge maze just inside the gate. The advantage of an alternate prelude is that the hedge maze is no longer hidden, and maps are handed out on entry; the disadvantage is that from the outside it just looks like a second, higher wall.

This is a very hard problem and I have no idea how to solve it in the long term. For now though, why not cause -fdefault-levity to produce prominent warnings that it's simplifying types? Possibly even annotate simplified output, e.g.:

    ($) ::# (a -> b) -> a -> b


On Fri, Feb 5, 2016 at 11:46 AM, Kyle Hanson <[hidden email]> wrote:
Richard,

I appreciate your response and have some genuine questions about how you see the Language growing in the future. As much as I am a principled developer in terms of adhering closely to the truth as possible, I also view code as a product that needs to "customers" to be successful. In order for that to happen, it needs to easily accessible and easy to understand.

I learned Haskell almost entirely by looking at existing projects and exploring the very awesome Hackage documentation. What would be the hackage definition for ($)? Would it be `($) :: forall (r :: RuntimeRep) (a :: *) (b :: TYPE r). (a -> b) -> a -> b` with an asterisk  that says "*For beginners: ($) :: (a -> b) -> a -> b"

Would there be a "Simple Hackage"?

It would be interesting for me to see how the skill levels of Haskell are distributed. In most languages it would look like a pyramid with a small group advanced developers on top and a mountain of people underneath. Haskell seems to be pushing towards the inverse, in which to code and understand standard, non beginners mode haskell you have to be "advanced". The barrier to entry looks to be increasing.

I agree with Christopher Allen and also do not agree with your assessment and comparison to the unnecessary syntax in Java. You can explain that program using simple english. That is why it was used for so many years as an introductory language.
How do you explain `forall (r :: RuntimeRep) (a :: *) (b :: TYPE r).` using simple english?

I think its important to identify who you want your "customers" to be. If you only want the most advanced type theorists to use the language, that is perfectly fine, but what you lose are thousands of developers that can benefit the Haskell community without having to know advanced Typing. 

Needing a "Beginners" mode in a language is *not* a feature, its a fundamental design flaw. It shows that the language was not sufficiently thought out and designed for everyone.

Its extremely important to not lose touch with the people that make the community; the newcomers. Sacrificing the 99% of beginner and intermediate haskellers for the 1%, I believe is a step in the wrong direction.

--
Kyle

On Fri, Feb 5, 2016 at 10:13 AM, Richard Eisenberg <[hidden email]> wrote:
Perhaps it will aid the discussion to see that the type of ($) will, for better or worse, be changing again before 8.0.

The problem is described in GHC ticket #11471. The details of "why" aren't all that important for this discussion, but the resolution might be. The new (hopefully final!) type of ($) will be:

> ($) :: forall (r :: RuntimeRep) (a :: *) (b :: TYPE r). (a -> b) -> a -> b

Once again, it's easy enough to tweak the pretty-printer to hide the complexity. But perhaps it's not necessary. The difference as far as this conversation is concerned is that Levity has been renamed to RuntimeRep. I think this is an improvement, because now it's not terribly hard to explain:

---
1. Types of kind * have values represented by pointers. This is the vast majority of data in Haskell, because almost everything in Haskell is boxed.
2. But sometimes, we don't care how a value is represented. In this case, we can be polymorphic in the choice of representation, just like `length` is polymorphic in the choice of list element type.
3. ($) works with functions whose result can have any representation, as succinctly stated in the type. Note that the argument to the function must be boxed, however, because the implementation of ($) must store and pass the argument. It doesn't care at all about the result, though, allowing for representation-polymorphism.

In aid of this explanation, we can relate this all to Java. The reference types in Java (e.g., Object, int[], Boolean) are all like types of kind *. The primitive types in Java (int, boolean, char) do not have kind *. Java allows type abstraction (that is, generics) only over the types of kind *. Haskell is more general, allowing abstraction over primitive types via representation polymorphism.
---

Could this all be explained to a novice programmer? That would be a struggle. But it could indeed be explained to an intermediate programmer in another language just learning Haskell.

For point of comparison, Java is widely used as a teaching language. And yet one of the simplest programs is

public class HelloWorld
{
  public static void main(String[] args)
  {
    System.out.println("Hello, world!");
  }
}

When I taught Java (I taught high-school full time for 8 years), I would start with something similar to this and have to tell everyone to ignore 90% of what was written. My course never even got to arrays and `static`! That was painful, but everyone survived. This is just to point out that Haskell isn't the only language with this problem. Not to say we shouldn't try to improve!

We're in a bit of a bind in all this. We really need the fancy type for ($) so that it can be used in all situations where it is used currently. The old type for ($) was just a plain old lie. Now, at least, we're not lying. So, do we 1) lie, 2) allow the language to grow, or 3) avoid certain growth because it affects how easy the language is to learn? I don't really think anyone is advocating for (3) exactly, but it's hard to have (2) and not make things more complicated -- unless we have a beginners' mode or other features in, say, GHCi that aid learning. As I've said, I'm in full favor of adding these features.

Richard

On Feb 5, 2016, at 12:55 PM, Kyle Hanson <[hidden email]> wrote:

I am also happy the discussion was posted here. Although I don't teach Haskell professionally, one of the things I loved to do was show people how simple Haskell really was by inspecting types and slowly putting the puzzle pieces together. 

Summary of the problem for others:
From Takenobu Tani
Before ghc7.8:

  Prelude> :t foldr
  foldr :: (a -> b -> b) -> b -> [a] -> b

  Prelude> :t ($)
  ($) :: (a -> b) -> a -> b

  Beginners should only understand about following:

    * type variable (polymorphism)


After ghc8.0:

  Prelude> :t foldr
  foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b

  Prelude> :t ($)
  ($)
    :: forall (w :: GHC.Types.Levity) a (b :: TYPE w).
       (a -> b) -> a -> b


With this change it looks like I will no longer be able to keep `$` in my toolbox since telling a beginner its "magic" goes against what I believe Haskell is good at, being well defined and easy to understand (Not well defined in terms of Types but well defined in terms of ability to precisely and concisely explain and define whats going on).

It looks like where the discussion is going is to have these types show by default but eventually have an Alternative prelude for beginners.

From Richard Eisenberg:
- It's interesting that the solution to the two problems Takenobu pulls out below (but others have hinted at in this thread) is by having an alternate Prelude for beginners. I believe that having an alternate beginners' Prelude is becoming essential. I know I'm not the first one to suggest this, but a great many issues that teachers of Haskell have raised with me and posts on this and other lists would be solved by an alternate Prelude for beginners.
I don't like the idea of fragmenting Haskell into "beginners" and "advanced" versions. Its hard enough to get people to believe Haskell is easy. If they see that they aren't using the "real" prelude, Haskell will still be this magic black box that is too abstract and difficult to understand. If they have to use a "dumbed down" version of Haskell to learn, its not as compelling.

There is something powerful about using the same idiomatic tools as the "big boys" and have the tools still be able to be easy to understand.... by default. Adding complexity to the default Haskell runs the risk of further alienating newcomers to the language who have a misconception that its too hard.

Admittedly, I am not well informed of the state of GHC 8.0 development and haven't had time to fully look into the situation. I am very interested to see where this conversation and the default complexity of Haskell goes.

--
Kyle


On Fri, Feb 5, 2016 at 8:26 AM, Tom Ellis <[hidden email]> wrote:
On Fri, Feb 05, 2016 at 05:25:15PM +0100, Johannes Waldmann wrote:
> > What's changed?
>
> I was referring to a discussion on ghc-devs, see
> https://mail.haskell.org/pipermail/ghc-devs/2016-February/011268.html
> and mixed up addresses when replying.

I'm glad you did, because this is the first I've heard of it!
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe



_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe




_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: New type of ($) operator in GHC 8.0 is problematic

Artyom Kazak-2

Why not just make GHCi output a comment whenever the type involves levity?

> :t ($)

-- Note: the actual type is more generic:
--
--     ($) :: forall (w :: GHC.Types.Levity) a (b :: TYPE w).  (a -> b) -> a -> b
--
-- For the absolute majority of purposes the simpler type is correct.
-- See GHC Guide chapter X point Y to learn more about this.

($) :: (a -> b) -> a -> b

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: New type of ($) operator in GHC 8.0 is problematic

Mihai Maruseac
On Fri, Feb 5, 2016 at 6:22 PM, Artyom <[hidden email]> wrote:

> Why not just make GHCi output a comment whenever the type involves levity?
>
>> :t ($)
>
> -- Note: the actual type is more generic:
> --
> --     ($) :: forall (w :: GHC.Types.Levity) a (b :: TYPE w).  (a -> b) -> a
> -> b
> --
> -- For the absolute majority of purposes the simpler type is correct.
> -- See GHC Guide chapter X point Y to learn more about this.
>
> ($) :: (a -> b) -> a -> b
>

Wouldn't this look like a scary error to some users? Though, users can
get accustomed to this and this solution seems to be the best of both
worlds.

--
Mihai Maruseac (MM)
"If you can't solve a problem, then there's an easier problem you can
solve: find it." -- George Polya
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: New type of ($) operator in GHC 8.0 is problematic

Artyom Kazak-2

I agree, it’s a bit too heavy. In fact, since most users probably won’t ever ever ever need that type, let’s only impose it on those who explicitly agree to see it:

> :t ($)
-- Note: the actual type is slightly more generic; set -fshow-levity
-- or use :t# instead of :t to see the fully generic form.
($) :: (a -> b) -> a -> b

On 02/06/2016 03:06 AM, Mihai Maruseac wrote:

On Fri, Feb 5, 2016 at 6:22 PM, Artyom [hidden email] wrote:
Why not just make GHCi output a comment whenever the type involves levity?

:t ($)
-- Note: the actual type is more generic:
--
--     ($) :: forall (w :: GHC.Types.Levity) a (b :: TYPE w).  (a -> b) -> a
-> b
--
-- For the absolute majority of purposes the simpler type is correct.
-- See GHC Guide chapter X point Y to learn more about this.

($) :: (a -> b) -> a -> b

Wouldn't this look like a scary error to some users? Though, users can
get accustomed to this and this solution seems to be the best of both
worlds.




_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe