Re: [Haskell-cafe] New type of ($) operator in GHC 8.0 is problematic

364 views
Skip to first unread message

Johannes Waldmann

unread,
Feb 5, 2016, 11:14:25 AM2/5/16
to haskell-cafe
As Manuel wrote:

> I expect that every single person teaching Haskell
> is going to be unhappy about it.

Indeed I am. (Will be teaching beginners next term.)

- J.W.
_______________________________________________
Haskell-Cafe mailing list
Haskel...@haskell.org
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe

Colin Adams

unread,
Feb 5, 2016, 11:22:34 AM2/5/16
to Johannes Waldmann, haskell-cafe
What's changed?

Johannes Waldmann

unread,
Feb 5, 2016, 11:25:23 AM2/5/16
to Colin Adams, haskell-cafe

> 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.

- J.

Tom Ellis

unread,
Feb 5, 2016, 11:26:59 AM2/5/16
to haskel...@haskell.org
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!

Kyle Hanson

unread,
Feb 5, 2016, 12:55:35 PM2/5/16
to Tom Ellis, haskel...@haskell.org
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

Christopher Allen

unread,
Feb 5, 2016, 12:59:25 PM2/5/16
to Kyle Hanson, Tom Ellis, Haskell Cafe
I don't want, nor do I think it's a good idea, to have a beginners' Prelude. My point about ($) was not expressly about beginners, it was about intermediate practitioners too.

--
Chris Allen
Currently working on http://haskellbook.com

Richard Eisenberg

unread,
Feb 5, 2016, 1:13:44 PM2/5/16
to Kyle Hanson, ghc-devs@haskell.org developers, Tom Ellis, haskel...@haskell.org
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

Christopher Allen

unread,
Feb 5, 2016, 2:05:36 PM2/5/16
to Richard Eisenberg, ghc-devs@haskell.org developers, Haskell Cafe, Tom Ellis
Changing the name doesn't fix the issue. The issue is the noise and the referent, not the referrer. There's a habit of over-focusing on names in programming communities. I think it'd be a mistake to do that here and risk missing the point.

You can make all of the keywords in the Java example salient early on, but you cannot make the implementation details you're exposing in the type of ($) relevant unless they already have a year or two of Haskell under their belts. Listing out the keywords:

1. public

2. class

3. (class name)

4. static

5. void

6. (method name)

7. (method arguments)

Explaining public, class, static, and void usually happens pretty soon after the basics in a Java course. Importantly, they're things you _need_ to know to get things done properly in Java. The same is not true of what is mentioned in the type of ($).

The implicit prenex form and forall are irrelevant for learners until they get to Rank2/RankN which is very much beyond, "I am learning Haskell" and into, "I am designing an API in Haskell for other people to use". * vs. # is something many working and hobbyist Haskellers I've known will scarcely know anything about.

There is a big difference, to my mind, between what is being exposed here in Java versus what is being exposed in the type ($). Consider that the boxed/unboxed distinction exists in Java but needn't come up in any beginner tutorials.


>Types of kind * have values represented by pointers. This is the vast majority of data in Haskell, because almost everything in Haskell is boxed.

We can't assume Haskell learners know what pointers are. This, again, creates unnecessary noise for learners by forcing exposure to things that are irrelevant for a very long time.


_______________________________________________
ghc-devs mailing list
ghc-...@haskell.org
http://mail.haskell.org/cgi-bin/mailman/listinfo/ghc-devs

Bardur Arantsson

unread,
Feb 5, 2016, 2:12:43 PM2/5/16
to haskel...@haskell.org, ghc-...@haskell.org
On 02/05/2016 08:05 PM, Christopher Allen wrote:
> Changing the name doesn't fix the issue. The issue is the noise and the
> referent, not the referrer. There's a habit of over-focusing on names in
> programming communities. I think it'd be a mistake to do that here and risk
> missing the point.
>

I think you're being a bit harsh, but I *do* think you're essentially
right. Beginners will have no idea what most the that means, so... *yes*
the type *will* need to be simplified for display purposes. (Unless, of
course, you opt-in to full signatures.)

Regards,

Christopher Allen

unread,
Feb 5, 2016, 2:17:08 PM2/5/16
to Bardur Arantsson, ghc-...@haskell.org, Haskell Cafe
I just showed the type of ($) to my boss in our company chat who has been using Haskell for 14 years. He'd played with Haskell prior to that, but 14 years ago is when he started postgrad and teaching Haskell. Here's what he said:

>...what?
>what does that do?

He's been using Haskell in production for the last 5 years as well, I think.

Please simplify the type unless a pragma specific to levity is turned on. As it happens, I like the name levity better than runtimerep, but neither solve any pedagogical issues. YMMV.

Tom Ellis

unread,
Feb 5, 2016, 2:19:33 PM2/5/16
to haskel...@haskell.org, ghc-devs@haskell.org developers
On Fri, Feb 05, 2016 at 01:13:23PM -0500, Richard Eisenberg wrote:
> 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.

Is there a list of situations where ($) is used currently that give rise to
this need?

Will Yager

unread,
Feb 5, 2016, 2:20:32 PM2/5/16
to Christopher Allen, Tom Ellis, Haskell Cafe, ghc-devs@haskell.org developers
Why must ($) be kind-polymorphic? It seems as though there is a small enough base of unboxed code that having e.g. ($#) would be fine. 

If that won't work, would it be possible to have something like

($) :: forall k a (b :: k) . (a -> b) -> a -> b

I don't know if this is possible in Haskell now, but I believe the currently popular dependently typed languages allow this sort of thing. 

Kyle Hanson

unread,
Feb 5, 2016, 2:46:45 PM2/5/16
to Richard Eisenberg, Tom Ellis, ghc-devs@haskell.org developers, haskel...@haskell.org
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

Marcin Mrotek

unread,
Feb 5, 2016, 3:54:21 PM2/5/16
to Kyle Hanson, Tom Ellis, Haskell-Cafe, ghc-devs@haskell.org developers
How do you explain `forall (r :: RuntimeRep) (a :: *) (b :: TYPE r).` using simple english?

"for all 'a's that are lifted types and 'b's that are types of any runtime representation 'r'..."

I don't really want to argue what is "simple english". I'd agree that Haskell's syntax is becoming more and more inadequate for expressing ideas that are being introduced to the language, though.
 
Best regards,
Marcin Mrotek

Omari Norman

unread,
Feb 5, 2016, 3:58:42 PM2/5/16
to Kyle Hanson, ghc-devs@haskell.org developers, haskell Cafe
On Fri, Feb 5, 2016 at 2:46 PM, Kyle Hanson <m...@khanson.io> wrote:
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.


I'm sympathetic, but the same arguments were made against the Foldable-Traversable Proposal. See for instance


Since that wound up going in, I think this ship has sailed.  Types are going to become increasingly polymorphic in the Prelude.  Though I wish this weren't so I've come to accept it, and I doubt attacking it head on is going to get anywhere.
 

Theodore Lief Gannon

unread,
Feb 5, 2016, 6:14:40 PM2/5/16
to haskel...@haskell.org
(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 <m...@khanson.io> 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

Mike Izbicki

unread,
Feb 5, 2016, 6:21:24 PM2/5/16
to Richard Eisenberg, ghc-devs@haskell.org developers, Haskell Cafe, Tom Ellis
> 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.

The old type for ($) is only a lie when the MagicHash extension is
turned on. Otherwise, it is not a lie. I think the best solution is
to pretty print the type depending on what language pragmas are in
use. In GHCI, this would be trivial. The much harder case is haddock
documentation.

I think a good way around this would be an eventual patch to haddock
that allows the user to select which extensions they want to use when
browsing documentation. There's a lot of usability issues that would
need to be resolved with this still, but it reduces this technical
discussion we're having down to a design discussion. It also nicely
lets the user specify the level of difficulty they want their prelude
to be without causing incompatibilty with users who want a different
level of prelude.

Artyom

unread,
Feb 5, 2016, 6:22:15 PM2/5/16
to Theodore Lief Gannon, haskel...@haskell.org

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.

Imants Cekusins

unread,
Feb 5, 2016, 6:40:37 PM2/5/16
to Haskell Cafe, ghc-devs@haskell.org developers
off-topic but still may be relevant. my apologies if it isn't:

isn't a good programming language - simple and unambiguous?

isn't programming language as much about communicating with other
programmers as about instructing compiler?

look at the game of chess: limited number of clear rules allow for
many complex and rich games.

clarity, convenience + library coverage would draw users and keep
them, I think. Good shiny tools attract interest. However it's what
you could make with these tools matters.

Mihai Maruseac

unread,
Feb 5, 2016, 7:07:12 PM2/5/16
to Artyom, haskell
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

Richard Eisenberg

unread,
Feb 5, 2016, 7:10:05 PM2/5/16
to ghc-devs@haskell.org developers, Haskell Cafe
It may come as a surprise to many of you that I, too, am very worried about Haskell becoming inaccessible to newcomers. If we can't induct new people into our ranks, we will die. It is for this reason that I have always been unhappy with the FTP. But that ship has sailed.

I fully agree with George's suggestion below that the default Prelude should be the beginner's Prelude. I believe I have argued this stance in the past, but louder voices prevailed. Perhaps I was wrong in branding: we should have a proper Prelude as the default, and make available a super whiz-bang advanced Prelude as well. I'm never very good about branding. I'd lend strong support to someone who articulates a concrete move in this direction, but I don't have the bandwidth to spearhead it myself.

Despite the various arguments saying that the bits in Java are easier to understand than the bits in ($), I'm quite unconvinced. (Particularly about `static`. Even `class` is hard for true beginners.) And the boxed/unboxed distinction does come up early in Java: just try to write an ArrayList<int> and now you need to know about boxed types and unboxed ones.

Chris's point that "it's not about the name" is valid. The Levity --> RuntimeRep change is not about the name, but about the functionality. Levity distinguished only between lifted and unlifted; RuntimeRep distinguishes between boxed/lifted, boxed/unlifted, and all the unboxed types with their different widths. I'm just clarifying that it's not simply a cosmetic name-change. 

The old type of ($) was always a lie. -XMagicHash just changes the parser, allowing the # suffix. It is only by convention that most (all?) unlifted things end in #. The old type of ($) was perhaps a harmless lie, but a lie nonetheless.

Are we comfortable with lying? (Believe me, I'm not trying to impose some moral dimension to simplifying output!) In my mind, lying about types like this is in the same space as having a beginner's Prelude. And people will constantly discover that we're lying and get very confused. Having a whole host of flags that tell GHC to lie less is somewhat like having two versions of the language... only the differences manifest only in output instead of input.

If we are comfortable with lying in this way: as I've offered, I can hide the type of ($) (and other representation-polymorphic things) behind a flag. Easy to do.

Another great question that has come up is about Haddock output (Hackage). I think Haddock needs to add a facility where library authors can include specializations of an overly general type. This can be done in commentary, but it's not as prominent. Such a new feature would address the ($) problem, as ($) :: forall (a :: *) (b :: *). (a -> b) -> a -> b is a specialization of its real type. It would also help a great deal with FTP-related generalizations.

I also want to respond directly to Kyle's comments:

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. 

Rest assured, I want my "customers" to be everyone who wants to program. I've volunteered to teach a bit of Haskell to high schoolers, and I'd love a shot at a course where I teach it to people who have never programmed.


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.

On an intuitive level, this rings true for me. But when I think about the details, I'm less convinced. For example, take Scratch (scratch.mit.edu), which is wonderfully easy to learn and gives kids (and adults!) a great deal of fun. Yet it's painful to use when you know more. And the Racket folks have invested a lot of time in coming up with a curriculum to go with their language, and they explicitly have expertise levels. Needing these levels may just be part of the game.

So, rest assured, I remain very receptive to these concerns. And I'd love concrete help in putting them to rest.

Richard


On Feb 5, 2016, at 6:30 PM, George Colpitts <george....@gmail.com> wrote:

+1 for Christopher's email
Richard, I disagree with  "But it could indeed be explained to an intermediate programmer in another language just learning Haskell." Your explanation is good but it assumes you have already explained "types of kind *" and the boxed vs unboxed distinction. Admittedly the latter should be understood by most Java programmers but I doubt that intermediate programmers in other languages do. If I did have to explain "$" I would say, for now think of it in terms of it's pre 8.0 type. Alternatively avoid mentioning "$" to beginners. I don't believe it is in Hutton's book or any of Bird's although I might be wrong.

Most intermediate programmers in another language struggle a lot with learning monads, witness all the monad tutorials. Absorbing monads is central, there is a lot that has to be explained before that. Minimizing that material would be a good thing. 

I have mixed feelings about a beginner's prelude best summarized by saying the proposed beginner's prelude should be the standard prelude and the current one should be an advanced prelude. If we have a beginner's prelude I feel we are saying that this is a hard to understand research language and we hope that someday you have enough education, energy and tenacity to get to the point where you understand it. If we do it the other way we are saying you have what you need but if you want more there is lots!

Artyom

unread,
Feb 5, 2016, 7:16:11 PM2/5/16
to Mihai Maruseac, haskell-cafe

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

Mihai Maruseac

unread,
Feb 5, 2016, 7:28:28 PM2/5/16
to Richard Eisenberg, ghc-devs@haskell.org developers, Haskell Cafe
On Fri, Feb 5, 2016 at 7:09 PM, Richard Eisenberg <e...@cis.upenn.edu> wrote:
>
> Another great question that has come up is about Haddock output (Hackage). I
> think Haddock needs to add a facility where library authors can include
> specializations of an overly general type. This can be done in commentary,
> but it's not as prominent. Such a new feature would address the ($) problem,
> as ($) :: forall (a :: *) (b :: *). (a -> b) -> a -> b is a specialization
> of its real type. It would also help a great deal with FTP-related
> generalizations.


This goes hand in hand with Artyom's suggestion of a warning in GHCi
after showing the simpler type.

I'm thinking of a flag which enables/disables printing the simplest
type with warning (in GHCi) or footnote (or otherwise, in Haddock). We
can have the default behavior of the flag be either printing the
simpler type + extra (warning/footnote) or printing the longer type
and include a reference in our learning materials that beginners and
people confused by the long, complex and real type, can use
--use-simpler-types flag.

--
Mihai Maruseac (MM)
"If you can't solve a problem, then there's an easier problem you can
solve: find it." -- George Polya

Takenobu Tani

unread,
Feb 5, 2016, 7:29:10 PM2/5/16
to Richard Eisenberg, ghc-devs@haskell.org developers, Haskell Cafe
Hi,

I tried to draw informal illustrations about Foldable signatures for beginners [1].
I'll also try to draw simple illustrations about new ($).

Of course I like Haskell's beautiful abstraction :)
Thank you for your great efforts.


Regards,
Takenobu

Artyom

unread,
Feb 5, 2016, 7:47:52 PM2/5/16
to Mihai Maruseac, Richard Eisenberg, ghc-devs@haskell.org developers, Haskell Cafe

I’ve amended my suggestion to say basically “this type is a slight lie, here’s a flag/command to see the true type” – this way we aren’t scaring people with implementation guts, merely letting them see the guts for themselves and then think “I don’t care about this” (which is, I think, exactly what should happen; the worst scenario here is that the beginner falls into the “I’m an advanced user, I need all features, I need to know everything, so I’ll enable the flag” trap – which is why it’s important not to call it “an advanced type” or mention “if you know what you’re doing” or anything else like that).

I don’t agree that levity can be compared to Java’s “class” or “static” – not because it’s harder to understand, but because it’s much less widely used; I don’t feel that you need to know about levity in order to be a good Haskeller. Also, unboxed types don’t imply knowledge of levity – for instance, I’ve been successfully using unboxed types for a while, but I only found out about the true type of ($) by complete accident (I think I queried the kind of -> and then got curious about question marks). Of

Richard Eisenberg

unread,
Feb 5, 2016, 8:51:39 PM2/5/16
to Artyom, ghc-devs@haskell.org developers, Haskell Cafe
A bit of time away from my keyboard has revealed a natural way to solve this problem and others: be more like Idris.

Normally, of course, I'm thinking about how Haskell's type system can be more like Idris's. But that's not what I mean here. I want Haskell's interface to be more like Idris's. Imagine this interchange:

λ> :t ($)
($) :: (a -> b) -> a -> b
-- click on the type
($) :: forall a b. (a -> b) -> a -> b
-- click on the a
($) :: forall (a :: *) b. (a -> b) -> a -> b
-- click on the b
($) :: forall (a :: *) (b :: *). (a -> b) -> a -> b   -- where b's kind has a different color than usual
-- click on b's kind
($) :: forall {r :: RuntimeRep} (a :: *) (b :: TYPE r). (a -> b) -> a -> b
-- mouseover RuntimeRep or TYPE reveals a tooltip
"($) is representation-polymorphic, meaning that `b` can have an arbitrary runtime representation. Please see http://.... for more details."

Similarly, classes would render in a special color, allowing you to click on them and choose to instantiate the type at a few in-scope instances of the class at hand, changing Foldable f => f a -> Int to the much simpler [a] -> Int.

This is not a minor engineering project, but it would reap wonderful rewards, addressing the problems in this thread and more. No more lying (because all lies are clickable), no more fragmented language, no more brakes on development.

Evidently, Chris already agrees with this proposal: #10073 (https://ghc.haskell.org/trac/ghc/ticket/10073)


Any volunteers to implement this? :)

Richard

M Farkas-Dyck

unread,
Feb 5, 2016, 9:24:54 PM2/5/16
to Richard Eisenberg, ghc-devs@haskell.org developers, Haskell Cafe
On 05/02/2016, Richard Eisenberg <e...@cis.upenn.edu> wrote:
> -- click on the type

The question so remains: what would we write to a purely textual terminal?

Mihai Maruseac

unread,
Feb 5, 2016, 9:29:52 PM2/5/16
to M Farkas-Dyck, Haskell Cafe, ghc-devs@haskell.org developers
On Fri, Feb 5, 2016 at 9:24 PM, M Farkas-Dyck <m.fark...@gmail.com> wrote:
>> -- click on the type
>
> The question so remains: what would we write to a purely textual terminal?


We could write the simplest type with a "type :expand / :ex (or
similar) to expand signature", eventually underlining the part that
will be expanded first.

--
Mihai Maruseac (MM)
"If you can't solve a problem, then there's an easier problem you can
solve: find it." -- George Polya

Marcin Mrotek

unread,
Feb 6, 2016, 3:37:50 AM2/6/16
to Mihai Maruseac, M Farkas-Dyck, ghc-devs@haskell.org developers, Haskell Cafe
I have a bad feeling this would complicate the parser and pretty printer a lot, and thus isn't feasible, but what about allowing wildcards in kind signatures?

I mean, in

forall (w :: Levity) a (b :: TYPE w). (a -> b) -> a -> b

the `w` variable seems superfluous, as it's only there to give it a kind signature. I think there's enough information in `TYPE w` to infer the kind (`TYPE` can only be parametrized with `Levity`?) so maybe something like this would work:

forall a (b :: TYPE _). (a -> b) -> a -> b

This could be "some" compromise between "lying" about the type and being scary to beginners, as at the very least all the type variables introduced are actually used in the type signature.

Best regards,
Marcin Mrotek

Tom Ellis

unread,
Feb 6, 2016, 6:59:29 AM2/6/16
to haskel...@haskell.org, ghc-devs@haskell.org developers
On Fri, Feb 05, 2016 at 07:19:25PM +0000, Tom Ellis wrote:
> On Fri, Feb 05, 2016 at 01:13:23PM -0500, Richard Eisenberg wrote:
> > 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.
>
> Is there a list of situations where ($) is used currently that give rise to
> this need?

Does anyone have any idea about this? What is it about ($) that means it
needs a new funky type whereas (apparently) nothing else does?

Tom Ellis

unread,
Feb 6, 2016, 7:20:59 AM2/6/16
to haskel...@haskell.org
On Sat, Feb 06, 2016 at 11:59:20AM +0000, Tom Ellis wrote:
> On Fri, Feb 05, 2016 at 07:19:25PM +0000, Tom Ellis wrote:
> > On Fri, Feb 05, 2016 at 01:13:23PM -0500, Richard Eisenberg wrote:
> > > 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.
> >
> > Is there a list of situations where ($) is used currently that give rise to
> > this need?
>
> Does anyone have any idea about this? What is it about ($) that means it
> needs a new funky type whereas (apparently) nothing else does?

For example, why should maybe not be extended from

maybe :: b -> (a -> b) -> Maybe a -> b

to
maybe :: forall (r :: RuntimeRep) (a :: *) (b :: TYPE r). b -> (a -> b) -> Maybe a -> b

That's strictly more general, is it not?

Tom

Ben Gamari

unread,
Feb 6, 2016, 7:27:16 AM2/6/16
to Tom Ellis, haskel...@haskell.org, ghc-devs@haskell.org developers
Tom Ellis <tom-lists-has...@jaguarpaw.co.uk> writes:

> On Fri, Feb 05, 2016 at 07:19:25PM +0000, Tom Ellis wrote:
>> On Fri, Feb 05, 2016 at 01:13:23PM -0500, Richard Eisenberg wrote:
>> > 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.
>>
>> Is there a list of situations where ($) is used currently that give rise to
>> this need?
>
> Does anyone have any idea about this? What is it about ($) that means it
> needs a new funky type whereas (apparently) nothing else does?

The first (albeit rather unconvincing) example I can think of is be
something like,

getI# :: Int -> Int#
getI# (I# n#) = n#

n# :: Int#
n# = getI# $ 5 + 8

Richard likely has something a bit less contrived though.

This does raise the question of why ($) is generalized, yet (.) is not,

(.) :: forall (l :: Levity) a b (c :: TYPE l).
(b -> c) -> (a -> b) -> (a -> c)
(.) f g x = f (g x)

Cheers,

- Ben
signature.asc

Marcin Mrotek

unread,
Feb 6, 2016, 7:31:27 AM2/6/16
to Tom Ellis, Haskell-Cafe
maybe :: forall (r :: RuntimeRep) (a :: *) (b :: TYPE r). b -> (a -> b) -> Maybe a -> b

`b` also is a type of an argument in that function, so I think being levity polymorphic on it in is excluded by "GC going haywire chasing values as if they were pointers" as described here: https://mail.haskell.org/pipermail/ghc-devs/2016-February/011269.html

Best regards,
Marcin Mrotek

Tom Ellis

unread,
Feb 6, 2016, 7:32:07 AM2/6/16
to haskel...@haskell.org
On Sat, Feb 06, 2016 at 01:27:00PM +0100, Ben Gamari wrote:
> Tom Ellis <tom-lists-has...@jaguarpaw.co.uk> writes:
> > On Fri, Feb 05, 2016 at 07:19:25PM +0000, Tom Ellis wrote:
> >> On Fri, Feb 05, 2016 at 01:13:23PM -0500, Richard Eisenberg wrote:
> >> > 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.
> >>
> >> Is there a list of situations where ($) is used currently that give rise to
> >> this need?
> >
> > Does anyone have any idea about this? What is it about ($) that means it
> > needs a new funky type whereas (apparently) nothing else does?
>
> The first (albeit rather unconvincing) example I can think of is be
> something like,
>
> getI# :: Int -> Int#
> getI# (I# n#) = n#
>
> n# :: Int#
> n# = getI# $ 5 + 8
>
> Richard likely has something a bit less contrived though.

I hope there's something less contrived, because if the benefit is "you get
to use $ to apply functions whose return type is not of kind *" then the
power to weight ratio of this is extremely low.

Is it also something to do with the special treatment that $ gets in the
compiler, to allow 'runST $ do'?

https://www.mail-archive.com/glasgow-ha...@haskell.org/msg18923.html

> This does raise the question of why ($) is generalized, yet (.) is not,
>
> (.) :: forall (l :: Levity) a b (c :: TYPE l).
> (b -> c) -> (a -> b) -> (a -> c)
> (.) f g x = f (g x)

Quite.

Tom Ellis

unread,
Feb 6, 2016, 7:33:17 AM2/6/16
to haskel...@haskell.org
Ah, "The levity polymorphic type never appears directly to the left of an
arrow.". Thanks.

Imants Cekusins

unread,
Feb 6, 2016, 7:40:12 AM2/6/16
to haskell Cafe
out of curiosity, what are
* and #?

I tried to search but did not find.

Thank you

Imants Cekusins

unread,
Feb 6, 2016, 7:48:29 AM2/6/16
to haskell Cafe
actually "#" worked on Hoogle:

https://wiki.haskell.org/Keywords#.23

Takenobu Tani

unread,
Feb 6, 2016, 7:59:07 AM2/6/16
to Imants Cekusins, haskell Cafe
Hi,

The '*' means kind "lifted".
The '#' means kind "unlifted".

Futhermore,
"TYPE 'Lifted" is alias to '*'
"TYPE 'Unlifted" is alias to '#'


Are these also useful?


Regards,
Takenobu

Imants Cekusins

unread,
Feb 6, 2016, 8:15:39 AM2/6/16
to haskell Cafe
Thank you Takenobu

the links are useful, yes.

Is knowledge of these terms necessary to program or are these terms of
most interest to compiler developers?

Ben Gamari

unread,
Feb 6, 2016, 8:30:23 AM2/6/16
to Tom Ellis, haskel...@haskell.org
Tom Ellis <tom-lists-has...@jaguarpaw.co.uk> writes:

> On Sat, Feb 06, 2016 at 01:27:00PM +0100, Ben Gamari wrote:
>>
>> The first (albeit rather unconvincing) example I can think of is be
>> something like,
>>
>> getI# :: Int -> Int#
>> getI# (I# n#) = n#
>>
>> n# :: Int#
>> n# = getI# $ 5 + 8
>>
>> Richard likely has something a bit less contrived though.
>
> I hope there's something less contrived, because if the benefit is "you get
> to use $ to apply functions whose return type is not of kind *" then the
> power to weight ratio of this is extremely low.
>
> Is it also something to do with the special treatment that $ gets in the
> compiler, to allow 'runST $ do'?
>
> https://www.mail-archive.com/glasgow-ha...@haskell.org/msg18923.html
>
To this the best of my knowledge, no. This would require impredicative polymorphism which
Richard's work does not provide. There is (was?), however, active work on this
front as well [1].

Cheers,

- Ben


[1] https://ghc.haskell.org/trac/ghc/wiki/ImpredicativePolymorphism/Impredicative-2015
signature.asc

Takenobu Tani

unread,
Feb 6, 2016, 9:13:18 AM2/6/16
to Imants Cekusins, haskell Cafe
Hi Imants and cafe,

> Is knowledge of these terms necessary to program or are these terms of
> most interest to compiler developers?


Ah, I think not necessary, but useful for abstraction and optimization of program :)


I share something about Kind, here :

[1] Learn You a Haskell for Great Good!, Kinds and some type-foo

[2] Haskell 2010 Language Report, 4.1.1 Kinds


Regards,
Takenobu

Richard Eisenberg

unread,
Feb 6, 2016, 10:17:48 AM2/6/16
to Haskell Cafe, ghc-...@haskell.org
I have made a ticket #11549 (https://ghc.haskell.org/trac/ghc/ticket/11549) requesting a -fshow-runtime-rep flag (recalling that the name levity will soon be outdated) as described in this thread. I will make sure this gets in for the release of 8.0.

Other points:

- You're quite right that (.) could be generalized. But I'll wait for someone to really want this.

- I don't have a non-contrived example of the use of ($) with unlifted types. It's quite possible that when adding the dirty runST hack, it was observed that an unlifted type would be OK. At that point, the type of ($) didn't need to become so elaborate. And now we're just trying not to change old (but perhaps unrequested) behavior.

- For the record, this debate is entirely unrelated to the runST impredicativity hack. (Except, as noted above, perhaps in history.) That hack remains, basically unchanged.

- On Feb 6, 2016, at 9:55 AM, Roman Cheplyaka <ro...@ro-che.info> wrote:
>
> I would call this a simplification rather than a lie.

This is a very convincing argument.

- Thanks, also, for the voice of support. What I love about the Haskell community is that we can have an impassioned debate full of strong opinions, and it all very rarely devolves into a proper flame war. All the posts I've seen in this thread have been constructive and helpful. Thanks.

Richard

Michał Antkiewicz

unread,
Feb 6, 2016, 10:32:59 AM2/6/16
to Richard Eisenberg, Haskell Cafe
Richard,

That is by far the best idea I've read in this entire thread!!!

There should be no more lies, no "beginner-only" preludes, etc. All
information should be available on request, effortlessly, as in your
example interaction with GHCi. I don't like having to set special
flags to see/hide certain info, as it was proposed. Having to use the
flags can easily mislead people who are not aware of them and also it
is too much work.

There was an issue raised with Haddocks. It's 2016 and we can easily
make the haddocks more interactive by embedding some JavaScript to
exactly recreate your interaction with GHCi or even, as a poor mans
substitute, simply show more details on mouse hoover or have similar
design like for showing instances, etc.

Every programmer should understand the difference between boxed and
unboxed values. Period. The fact that Haskell allows for levity
polimorphism is something we should be proud of and leverage in
teaching, not hide it or lie about it.

Finally, I wanted to highlight explicit type application as a great
didactic tool. We can now nicely provide types the same way as values
to the function and I find it a great way to explain type parameters.

Best,
Michał

Imants Cekusins

unread,
Feb 6, 2016, 10:54:50 AM2/6/16
to Haskell Cafe
in addition to Takenobu's links, Real World Haskell explains unboxing
and lifting on p.583

just to clarify, for practical use, is it safe to say that "boxed" and
"lifted" are synonyms? you see, term "boxed" is used in other
languages. I assumed "lifting" related to monads. Hence the confusion.

Alexey Vagarenko

unread,
Feb 6, 2016, 11:33:01 AM2/6/16
to Haskell-cafe, haskel...@haskell.org, ima...@gmail.com
 is it safe to say that "boxed" and 
"lifted" are synonyms?
No. 
Lifted means may contain bottom. Boxed means represented by a pointer.
`ByteArray#` is boxed but unlifted     

Edward Kmett

unread,
Feb 6, 2016, 12:09:33 PM2/6/16
to Mike Izbicki, Haskell Cafe, ghc-devs@haskell.org developers, Tom Ellis
On Fri, Feb 5, 2016 at 6:21 PM, Mike Izbicki <mi...@izbicki.me> wrote:
> 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.

The old type for ($) is only a lie when the MagicHash extension is
turned on.  Otherwise, it is not a lie.  I think the best solution is
to pretty print the type depending on what language pragmas are in
use.  In GHCI, this would be trivial.  The much harder case is haddock
documentation.

Note: The old type of ($) has always been a lie, even without MagicHash, a much stronger lie because the true type of ($) can't even be written in the language today.

You can instantiate both the source and target types of ($) to polytypes, not just monotypes.

This lets us use ($) in situations like

runST $ do ...

Having it infer a RankNType through its magical type inference rule there doesn't require an extension on the behalf of the user, even if runST required them at the definition site.

-Edward
 
I think a good way around this would be an eventual patch to haddock
that allows the user to select which extensions they want to use when
browsing documentation.  There's a lot of usability issues that would
need to be resolved with this still, but it reduces this technical
discussion we're having down to a design discussion.  It also nicely
lets the user specify the level of difficulty they want their prelude
to be without causing incompatibilty with users who want a different
level of prelude.

Edward Kmett

unread,
Feb 6, 2016, 12:12:53 PM2/6/16
to Imants Cekusins, haskell Cafe
As you dig deeper into Haskell you'll eventually need to understand what these mean to reason about anything beyond first order code.

The primitives that GHC uses to implement arrays, references and the like live in #. We then wrap them in something in * before exposing them to the user, but you can shave a level of indirection by knowing what lives in # and what doesn't.

But even if you never care about #, Int, Double, etc. are of kind *, Functors are of kind * -> *, etc. so to talk about the type of types at all you need to be able to talk about these concepts at all with any rigor, and to understand why Maybe Maybe isn't a thing.

Tom Ellis

unread,
Feb 6, 2016, 12:19:48 PM2/6/16
to haskel...@haskell.org
On Sat, Feb 06, 2016 at 12:12:47PM -0500, Edward Kmett wrote:
> As you dig deeper into Haskell you'll eventually need to understand what
> these mean to reason about anything beyond first order code.
>
> The primitives that GHC uses to implement arrays, references and the like
> live in #. We then wrap them in something in * before exposing them to the
> user, but you can shave a level of indirection by knowing what lives in #
> and what doesn't.
>
> But even if you never care about #, Int, Double, etc. are of kind *,
> Functors are of kind * -> *, etc. so to talk about the type of types at all
> you need to be able to talk about these concepts at all with any rigor, and
> to understand why Maybe Maybe isn't a thing.

I think this is a bit pessimistic. I've been a professional Haskell
developer for some years without ever needing to know what # is. I'm pretty
sure that a professional Haskeller could be comfortable only know about * on
a vague, intuitive level.

Tom

Manuel Gómez

unread,
Feb 6, 2016, 1:15:43 PM2/6/16
to Edward Kmett, haskell Cafe
On Sat, Feb 6, 2016 at 12:42 PM, Edward Kmett <ekm...@gmail.com> wrote:
> The primitives that GHC uses to implement arrays, references and the like
> live in #. We then wrap them in something in * before exposing them to the
> user, but you can shave a level of indirection by knowing what lives in #
> and what doesn't.

Yes! Let’s not forget, of course, that these (or similar) have been
in GHC for many, many years, right in the Prelude:

```
> :i Int Char Float Double IO Integer
data Int = GHC.Types.I# GHC.Prim.Int#
data Char = GHC.Types.C# GHC.Prim.Char#
data Float = GHC.Types.F# GHC.Prim.Float#
data Double = GHC.Types.D# GHC.Prim.Double#
newtype IO a
= GHC.Types.IO (GHC.Prim.State# GHC.Prim.RealWorld
-> (# GHC.Prim.State# GHC.Prim.RealWorld, a #))
data Integer
= integer-gmp-1.0.0.0:GHC.Integer.Type.S# !GHC.Prim.Int#
| integer-gmp-1.0.0.0:GHC.Integer.Type.Jp# {-# UNPACK
#-}integer-gmp-1.0.0.0:GHC.Integer.Type.BigNat
| integer-gmp-1.0.0.0:GHC.Integer.Type.Jn# {-# UNPACK
#-}integer-gmp-1.0.0.0:GHC.Integer.Type.BigNat
```

Stepping outside the Prelude, yet well within beginner territory,
brings even more fun:

```
> :i Map Set
data Map k a
= containers-0.5.6.2:Data.Map.Base.Bin {-# UNPACK
#-}containers-0.5.6.2:Data.Map.Base.Size
!k
a
!(Map k a)
!(Map k a)
| containers-0.5.6.2:Data.Map.Base.Tip
data Set a
= containers-0.5.6.2:Data.Set.Base.Bin {-# UNPACK
#-}containers-0.5.6.2:Data.Set.Base.Size
!a
!(Set a)
!(Set a)
| containers-0.5.6.2:Data.Set.Base.Tip
```

Unboxed types, the UNPACK pragma, references to GHC.Prim (which easily
lead to confusing exploration), unboxed tuples, an unboxed State
monad, RealWorld, bang patterns, unexported constructors,
implementation details for abstract types… all of them available right
from the prompt of the Prelude using the main tool for exploratory
learning that beginners rely on.

I’m not saying this is a good thing and I’m not saying this should be
fixed. I’m not even saying this is comparable to the situation with $
and I’m likewise not saying presenting these concepts to beginners
should be thought of as comparable to presenting levity polymorphism
to beginners. It is nonetheless relevant context to this discussion;
the Prelude has always had concepts unfriendly to beginners readily
available, and Haskell beginner teachers have always had to work
around these issues. Students have always asked about these things.

> But even if you never care about #, Int, Double, etc. are of kind *,
> Functors are of kind * -> *, etc. so to talk about the type of types at all
> you need to be able to talk about these concepts at all with any rigor, and
> to understand why Maybe Maybe isn't a thing.

In my personal teaching experience, it is extremely helpful to discuss
kinds in the first introduction of type constructors, after covering
types with no parameters. This is especially helpful in discussing
how the hierarchy leading to Monad works, and why things like
«instance Functor (Tree Int) where …» don’t make sense and why
«instance Functor Tree where …» must be parametric in the type of the
thing in the tree, which in turn motivates a lot more discussion.

Teaching kinds is teaching Haskell basics. It is not an advanced
topic. It ought to be covered near the very first lessons on Haskell
for absolute beginners.

David Turner

unread,
Feb 6, 2016, 2:34:04 PM2/6/16
to haskell Cafe
By way of a counterpoint to the "showing complicated things alienates beginners" argument, remember that to a beginner there are already very many things on the screen that they won't (and needn't immediately) understand. For instance, this is what a simple `stack ghci` in my home directory says to me:

$ stack ghci
Run from outside a project, using implicit global project config
Using resolver: lts-2.22 from implicit global project's config file: /home/linuxadmin/.stack/global/stack.yaml
Error parsing targets: The specified targets matched no packages.
Perhaps you need to run 'stack init'?
Warning: build failed, but optimistically launching GHCi anyway
Configuring GHCi with the following packages:
GHCi, version 7.8.4: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Ok, modules loaded: none.
Prelude>

There's a lot of stuff there you don't need as a beginner. The line beginning 'Error' is a bit scary, as is the 'Warning'. The advice to run 'stack init' is not good advice. The advice to use :? for help is probably the most beginner-useful thing in all that and it looks like line noise rather than a thing you might want to actually type!

My point is that beginners have to start out ignoring things they don't understand anyway - part of the process of learning a new language is coming to terms with what's important and what's not in any given context. I'm not saying I'm a big fan of the addition to the type sig of ($), and would definitely appreciate a flag to switch it off, but I don't think this'll make it significantly harder to teach my next victims.

Cheers,

David

John Wiegley

unread,
Feb 6, 2016, 4:04:47 PM2/6/16
to Kyle Hanson, Tom Ellis, haskel...@haskell.org
>>>>> Kyle Hanson <m...@khanson.io> writes:

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

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

I wonder if it could elide that information when -fprint-explicit-foralls is
not enabled? The (a :: *) is not enough to warrant a forall in the former
case, so 'b :: TYPE w' where 'w :: GHC.Types.Levity' perhaps shouldn't be
enough to warrant it in the latter.

--
John Wiegley GPG fingerprint = 4710 CF98 AF9B 327B B80F
http://newartisans.com 60E1 46C4 BD1A 7AC1 4BA2

Marcin Mrotek

unread,
Feb 6, 2016, 7:55:31 PM2/6/16
to David Turner, haskell Cafe
There's a lot of stuff there you don't need as a beginner. The line beginning 'Error' is a bit scary, as is the 'Warning'. The advice to run 'stack init' is not good advice.

I understand your point about 'Error', but I still think most people are more likely to dismiss everything before `Prelude>` as line noise than to ignore stuff that is printed after they type `:t ($)`, especially if they've already used `:t` to check the types of other things.

Don't get me wrong, I'm generally all in for unlifted/unboxed types, data kinds, levity polymorphism, etc, it's just that I remember the time when the tipe signature of `>>=` looked scary and the `Foldable` and `Traversable` type classes seemed like black magic to me. I hesitate to suggest this, as someone who didn't write any Haskell for a couple of months now, but maybe the already mentioned suggestion of having a ($) that only works for lifted types and (#$) that is levity polymorphic would be a good choice?

Best regards,
Marcin Mrotek

Ben Lippmeier

unread,
Feb 7, 2016, 3:26:38 AM2/7/16
to mant...@gsd.uwaterloo.ca, Haskell Cafe

On 7 Feb 2016, at 2:32 am, Michał Antkiewicz <mant...@gsd.uwaterloo.ca> wrote:

Every programmer should understand the difference between boxed and
unboxed values. Period. The fact that Haskell allows for levity
polimorphism is something we should be proud of and leverage in
teaching, not hide it or lie about it.

When I first read the “Unboxed values as first class citizens..” paper I felt smart, like learning the difference between Int and Int# was helping me understand how compilers worked under the hood. Now, ~15 years later, I already know all these details and having to constantly worry about the difference between Int and Int# when writing application code makes me feel dumb. When I then read people praising the advanced Haskell type system and the fact that we can now use advanced Haskell type system features to write polymorphic functions that work with both Int and Int# I feel even dumber. This isn’t a criticism of the work on levity polymorphism, more that it feels to me like a solution to a problem that should not exist in the first place.

In my own mind, the boxed/unboxed problem is in the same bucket as the problem that ownership typing tries to address in the OO world. Every OO programmer can recite the great engineering benefits of splitting the fields and methods of an object into public and private. However, in most OO languages, if a public method returns a reference to mutable private state, then the caller can then update the internal state of the object. Ownership typing is an advanced type system extension for OO languages to prevent this from happening. 

It also feels like the same class of problem as what the segmentation registers in the x86 architecture try to address. Does anyone here know how the 8086 processor addresses more than 64k of memory with only 16-bit address registers? I sure wish I didn’t. As a budding young programmer, reading about these details was a way to learn how computers operate, but it’s not information I would inflict on any of my current students.

With Int/Int#, public/private and x86 segmentation, the abstraction never really worked in the first place. However, because there weren’t any other obvious solutions at the time, these “features” were grandfathered into the culture of the technology. For the disciples of a particular technology, the recognition of problems in the fundamental design of that technology tends to result in extensions to what they already have -- rather than backing up and reconsidering WTF they were thinking in the first place. These days when I read papers about advanced type system extensions I typically feel smart for a few days, but then after reflecting on it a few weeks or months later I often feel dumb again. I’m not sure if that’s something to be proud of...

Ben.

Evan Laforge

unread,
Feb 7, 2016, 4:22:56 AM2/7/16
to Ben Lippmeier, Haskell Cafe
1, Just to clarify, there are no actual known uses for a
TypeRep-polymorphic ($), right? I thought I saw someone say that.

2, ($) has had a fib in its type for a very long time, but did it ever
hurt anyone? The closest I saw was a generalized concern about it
being bad when people report a bug and then hear that things are more
general than they thought and I guess this makes their bug not a bug
or something? Results in some confusing back-and-forth? It would be
nice to get more specific about how much trouble the ($) lie has
caused.

3, Is anything other than ($) TypeRep-polymorphic?


If 2 is "not very much", an obvious solution is to keep lying and not
worry about it. But clearly someone had a reason strong enough to
make this change... what was it?

If 1 is "maybe not" then an obvious solution is to just make ($) not
TypeRep-polymorphic. I can't imagine that not being able to use ($)
is a serious problem if you're working with # types, given that there
are already tons of other restrictions.


I hesitate to get distracted from the specifics, but I agree with Ben
Lippemeier in that I like how haskell doesn't make me worry about
boxed vs. unboxed. I'm not saying one little type annotation on one
operator is suddenly making me worry since I've never even thought to
run :t ($). But I'm happy that # is mostly off in its own little
corner and you don't even have to know it exists until you need to get
into low level optimization.

Tom Ellis

unread,
Feb 7, 2016, 4:26:23 AM2/7/16
to haskel...@haskell.org
On Sun, Feb 07, 2016 at 01:55:25AM +0100, Marcin Mrotek wrote:
> I hesitate to suggest this, as someone who didn't write any Haskell for a
> couple of months now, but maybe the already mentioned suggestion of having
> a ($) that only works for lifted types and (#$) that is levity polymorphic
> would be a good choice?

If we're going to introduce levity polymorphism then I think separate
operators are a good place to start. Unifying them can happen once everyone
has had a chance to get used to the idea.

Joachim Durchholz

unread,
Feb 7, 2016, 5:50:49 AM2/7/16
to haskel...@haskell.org
Am 07.02.2016 um 09:26 schrieb Ben Lippmeier:
>
> It also feels like the same class of problem as what the segmentation
> registers in the x86 architecture try to address. Does anyone here
> know how the 8086 processor addresses more than 64k of memory with
> only 16-bit address registers? I sure wish I didn’t.

Yes I do, and yes I wish I didn't either.

For the Int/Int# concept, the approaches I have seen either ignore the
efficiency and let the machine figure out what to do (Smalltalk, Python,
pre-Int# Haskell), or they complicate the type system at the expense of
polymorphism (Java, Eiffel), or they complicate the type system even
more to regain some form of polymorphism (C++, today's Haskell).
I guess the world is still waiting for an approach that does not force
this choice on language designers.
Aside note: My own choice would be to have an annotation that tells the
compiler to keep something unboxed if it can, and if it cannot, have it
print a warning why not. Not seeing this choice in the wild means that
either language designers didn't find the right way to do this, or the
idea as such is dumb; I don't know which.

> With Int/Int#, public/private and x86 segmentation, the abstraction
> never really worked in the first place. However, because there
> weren’t any other obvious solutions at the time, these “features”
> were grandfathered into the culture of the technology.

As much as I agree that design misdecisions can perpetuate by becoming
part of the technology culture (see PHP's view on security, or C's
insistence on microoptimization), this did not happen for x86 16-bit
segment/offset addressing. Even Microsoft switched as fast as they
could, and that was in the old days when features were far more
important than security or even stability.

> For the
> disciples of a particular technology, the recognition of problems in
> the fundamental design of that technology tends to result in
> extensions to what they already have -- rather than backing up and
> reconsidering WTF they were thinking in the first place.

Word.

Problem is that it is possible to add features to existing language, but
almost impossible to remove them. Those working on practically useful
type systems want to solve an existing problem in an existing language,
so they don't have a motive to reconsider; even if they did, they'd
quickly drop the thought because whatever the change they'd want, it
would get rejected because it would break existing code left and right.
This seems to be a universal problem. Every language that I know has it,
including Haskell (which I don't really know well enough but the
discussion and options are just as with any other language).

Regards,
Jo

Geraldus

unread,
Feb 7, 2016, 6:08:59 AM2/7/16
to haskel...@haskell.org

Hi, friends! I want to share my own feelings about type signatures. It is always hard for me to read type signatures with class constraints, because first I need to spot that there is =>, then I have to split type signature in my mind to constraint part and actual signature part. I think having constraints before signature when defining things is something that eases source parsing and etc., but wouldn't type signatures become a bit more readable if we put constraints after actual signature when printing it in GHCi (and maybe in Haddock), e.g.:

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

Ivan Lazar Miljenovic

unread,
Feb 7, 2016, 6:46:28 AM2/7/16
to Geraldus, Haskell Cafe
If this is only how ghci types/prints it, then it makes it much more
difficult (if not impossible) to just copy/paste the resulting type
into your code.

This also makes it much more verbose: it might be useful for longer
type signatures (especially with large constraints) but not for the
majority of them. I also like being able to see the constraints first
so that I know what they are before reading the actual type.

--
Ivan Lazar Miljenovic
Ivan.Mi...@gmail.com
http://IvanMiljenovic.wordpress.com

Ben Lippmeier

unread,
Feb 7, 2016, 7:17:46 AM2/7/16
to Joachim Durchholz, haskel...@haskell.org

> On 7 Feb 2016, at 9:50 pm, Joachim Durchholz <j...@durchholz.org> wrote:

> For the Int/Int# concept, the approaches I have seen either ignore the efficiency and let the machine figure out what to do (Smalltalk, Python, pre-Int# Haskell), or they complicate the type system at the expense of polymorphism (Java, Eiffel), or they complicate the type system even more to regain some form of polymorphism (C++, today's Haskell).

Although I haven’t implemented it, I suspect another approach is to just specialise every polymorphic function at its unboxed type arguments. Boxed and unboxed value types would share the same kind. Of course, full specialisation of polymorphic code assumes that code is available in the interface files, but we’ve almost got that already. Dealing with mutual recursion could be a pain, though.

I don’t think specialisation was an option back when unboxed types were originally implemented. I believe GHC’s support for cross module inlining came some time after the unboxed types, if the publication dates of the relative papers are to be a guide.

Ben.

Richard Eisenberg

unread,
Feb 7, 2016, 9:59:39 AM2/7/16
to Evan Laforge, Haskell Cafe

On Feb 7, 2016, at 4:22 AM, Evan Laforge <qdu...@gmail.com> wrote:

> 1, Just to clarify, there are no actual known uses for a
> TypeRep-polymorphic ($), right? I thought I saw someone say that.

Here is where this treatment of ($) was introduced: https://ghc.haskell.org/trac/ghc/ticket/8739

>
> 2, ($) has had a fib in its type for a very long time, but did it ever
> hurt anyone? The closest I saw was a generalized concern about it
> being bad when people report a bug and then hear that things are more
> general than they thought and I guess this makes their bug not a bug
> or something? Results in some confusing back-and-forth? It would be
> nice to get more specific about how much trouble the ($) lie has
> caused.

I don't have data, but there is a real cost to lying. It shows up in the slow-ish but steady stream of posts / questions / bug reports that are produced saying something is weird. I've seen a good number of these come up in my years in the Haskell community. I'll note that there is also a real cost to telling the truth: witness this thread. This all adds up to a need to do both, which is what we would get by having a richer REPL environment.

>
> 3, Is anything other than ($) TypeRep-polymorphic?

Yes: undefined and error. These really are used at unlifted types.

>
>
> If 2 is "not very much", an obvious solution is to keep lying and not
> worry about it. But clearly someone had a reason strong enough to
> make this change... what was it?

The change to the user-visible type of ($) is due to updates around TypeInType. But ($)'s ability to handle this case has been around since 7.8. Note that if you say :i $ in GHCi 7.10 you get a mention of OpenKind, which is the ancestor of current representation-polymorphism. It's just that :t ($) did a nice job of hiding it.
>
>
> I hesitate to get distracted from the specifics, but I agree with Ben
> Lippemeier in that I like how haskell doesn't make me worry about
> boxed vs. unboxed. I'm not saying one little type annotation on one
> operator is suddenly making me worry since I've never even thought to
> run :t ($). But I'm happy that # is mostly off in its own little
> corner and you don't even have to know it exists until you need to get
> into low level optimization.

And this is what the new -fshow-runtime-rep flag will (re-)enable.

Richard

Tom Ellis

unread,
Feb 7, 2016, 10:11:43 AM2/7/16
to haskel...@haskell.org
On Sat, Feb 06, 2016 at 12:31:57PM +0000, Tom Ellis wrote:
> On Sat, Feb 06, 2016 at 01:27:00PM +0100, Ben Gamari wrote:
> > Tom Ellis <tom-lists-has...@jaguarpaw.co.uk> writes:
> > > On Fri, Feb 05, 2016 at 07:19:25PM +0000, Tom Ellis wrote:
> > >> On Fri, Feb 05, 2016 at 01:13:23PM -0500, Richard Eisenberg wrote:
> > >> > 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.
> > >>
> > >> Is there a list of situations where ($) is used currently that give rise to
> > >> this need?
> > >
> > > Does anyone have any idea about this? What is it about ($) that means it
> > > needs a new funky type whereas (apparently) nothing else does?
> >
> > The first (albeit rather unconvincing) example I can think of is be
> > something like,
> >
> > getI# :: Int -> Int#
> > getI# (I# n#) = n#
> >
> > n# :: Int#
> > n# = getI# $ 5 + 8
> >
> > Richard likely has something a bit less contrived though.
>
> I hope there's something less contrived, because if the benefit is "you get
> to use $ to apply functions whose return type is not of kind *" then the
> power to weight ratio of this is extremely low.

No one has suggested anything less contrived, so I'm going to assume this
encompasses all use cases for the new type. in which case may I make a
counter proposal:

Give Prelude.($) a truthful type of '(a -> b) -> a -> b' and put the
generalized version in a separate module, at least for now?

I note that with the Foldable/Traversable Prelude we had

* Foldable and Traversable in separate modules for a long, long time
* A long discussion about merging them
* Lots and lots of people using Foldable and Traversable

None of these cases seems to apply to generalized ($) so I don't think it
warrants inclusion in base (yet).

(This is not to suggest that the work on the type system to make this sort
of polymorphism is not technically excellent -- I just think we need to hold
fire regarding merging such changes to base).

Tom

Richard Eisenberg

unread,
Feb 7, 2016, 10:16:10 AM2/7/16
to Tom Ellis, haskel...@haskell.org

On Feb 7, 2016, at 10:11 AM, Tom Ellis <tom-lists-has...@jaguarpaw.co.uk> wrote:
>
> No one has suggested anything less contrived, so I'm going to assume this
> encompasses all use cases for the new type. in which case may I make a
> counter proposal:
>
> Give Prelude.($) a truthful type of '(a -> b) -> a -> b' and put the
> generalized version in a separate module, at least for now?

I would agree with this... except that the version of ($) in base in 7.8 and 7.10 already *was* generalized in this way. But no one got as itchy about the OpenKind that appears in 7.10's `:i $` as they are about the guck that appears in 8.0's `:t ($)`. So moving it out now would break code in the wild like https://ghc.haskell.org/trac/ghc/ticket/8739

Just to amplify this point: the generalization of ($) that we are debating **is not new**. The way it's rendered in GHCi is new, however.

Richard

Tristan Seligmann

unread,
Feb 7, 2016, 10:24:29 AM2/7/16
to haskel...@haskell.org
On Sun, 7 Feb 2016 at 17:16 Richard Eisenberg <e...@cis.upenn.edu> wrote:

I would agree with this... except that the version of ($) in base in 7.8 and 7.10 already *was* generalized in this way. But no one got as itchy about the OpenKind that appears in 7.10's `:i $` as they are about the guck that appears in 8.0's `:t ($)`. So moving it out now would break code in the wild like https://ghc.haskell.org/trac/ghc/ticket/8739

My GHC 7.10 doesn't seem to have any OpenKind stuff:

GHCi, version 7.10.3: http://www.haskell.org/ghc/ :? for help
Prelude> :i $
($) :: (a -> b) -> a -> b        -- Defined in ‘GHC.Base’
infixr 0 $

Tom Ellis

unread,
Feb 7, 2016, 10:39:17 AM2/7/16
to haskel...@haskell.org
On Sat, Feb 06, 2016 at 12:12:47PM -0500, Edward Kmett wrote:
> The primitives that GHC uses to implement arrays, references and the like
> live in #. We then wrap them in something in * before exposing them to the
> user, but you can shave a level of indirection by knowing what lives in #
> and what doesn't.
>
> But even if you never care about #, Int, Double, etc. are of kind *,
> Functors are of kind * -> *, etc. so to talk about the type of types at all
> you need to be able to talk about these concepts at all with any rigor, and
> to understand why Maybe Maybe isn't a thing.

(This question is for my own edification and is not meant to be a point in
the current debate)

If we were inventing a language from the beginning, would it be strictly
necessary to have two kinds? Could we have just an unboxed kind #, and have
a box be an explicit type constructor? If the type constructor were called
'P' (standing for pointer) then we could have

id :: P a -> P a

data [a] = (P a) : [a] | []

etc.

Does this thing seem remotely plausible to people who know clever type
theory?

Tom

Tom Ellis

unread,
Feb 7, 2016, 10:45:45 AM2/7/16
to haskel...@haskell.org
On Sun, Feb 07, 2016 at 10:15:49AM -0500, Richard Eisenberg wrote:
> On Feb 7, 2016, at 10:11 AM, Tom Ellis <tom-lists-has...@jaguarpaw.co.uk> wrote:
> > No one has suggested anything less contrived, so I'm going to assume this
> > encompasses all use cases for the new type. in which case may I make a
> > counter proposal:
> >
> > Give Prelude.($) a truthful type of '(a -> b) -> a -> b' and put the
> > generalized version in a separate module, at least for now?
>
> I would agree with this... except that the version of ($) in base in 7.8
> and 7.10 already *was* generalized in this way. But no one got as itchy
> about the OpenKind that appears in 7.10's `:i $` as they are about the
> guck that appears in 8.0's `:t ($)`. So moving it out now would break
> code in the wild like https://ghc.haskell.org/trac/ghc/ticket/8739

This sounds a bit like throwing good money after bad. The levity
polymorphic behaviour of ($) in 7.8 and 7.10 was not advertised in its type,
nor in the documentation:

https://hackage.haskell.org/package/base-4.8.2.0/docs/src/GHC.Base.html#%24

If people were relying on it to do something non-Haskell 2010, undocumented,
GHC specific, then that's sad for them. But there are probably one hundred
times as many people (literally) who would be startled to see the "correct"
type pop up in GHC 8.

Why not move the polymorphic version to a library and all those who really
need it can get it from there. In time, when it has demonstrated itself to
be an indisposable generalization, then it can be moved to base.

> Just to amplify this point: the generalization of ($) that we are debating
> **is not new**. The way it's rendered in GHCi is new, however.

Sure I get that. It's just that few really knew that's how it was, because
it wasn't advertised as being as it was, so few could question it :)

(I've known for a while ($) has some magic around runST, but two days ago
was the first time I heard about it being levity polymorphic.)

Tom

Daniel Gorín

unread,
Feb 7, 2016, 10:56:52 AM2/7/16
to Richard Eisenberg, Haskell Cafe

> On 7 Feb 2016, at 2:59 pm, Richard Eisenberg <e...@cis.upenn.edu> wrote:
>>
>> 2, ($) has had a fib in its type for a very long time, but did it ever
>> hurt anyone? The closest I saw was a generalized concern about it
>> being bad when people report a bug and then hear that things are more
>> general than they thought and I guess this makes their bug not a bug
>> or something? Results in some confusing back-and-forth? It would be
>> nice to get more specific about how much trouble the ($) lie has
>> caused.
>
> I don't have data, but there is a real cost to lying. It shows up in the slow-ish but steady stream of posts / questions / bug reports that are produced saying something is weird. I've seen a good number of these come up in my years in the Haskell community. I'll note that there is also a real cost to telling the truth: witness this thread. This all adds up to a need to do both, which is what we would get by having a richer REPL environment.

This reminds a lot of the FTP controversy and I feel that here too we could side step the problem by allowing modules to re-export type-specialized versions of imported symbols. E.g., Data.Function could define ($) with the new type, Prelude would re-export it with the old type (as a specialization) and anyone who needs to use the more general version would have to opt-in by importing Data.Function. In terms of documentation, the haddock version of (Data.Function.$) would show the more general type while the haddock for Prelude would show the current type (ideally with a link to the more general one).

Tom Ellis

unread,
Feb 7, 2016, 10:59:22 AM2/7/16
to haskel...@haskell.org
On Sun, Feb 07, 2016 at 03:56:25PM +0000, Daniel Gorín wrote:
> > On 7 Feb 2016, at 2:59 pm, Richard Eisenberg <e...@cis.upenn.edu> wrote:
> >>
> >> 2, ($) has had a fib in its type for a very long time, but did it ever
> >> hurt anyone? The closest I saw was a generalized concern about it
> >> being bad when people report a bug and then hear that things are more
> >> general than they thought and I guess this makes their bug not a bug
> >> or something? Results in some confusing back-and-forth? It would be
> >> nice to get more specific about how much trouble the ($) lie has
> >> caused.
> >
> > I don't have data, but there is a real cost to lying. It shows up in the
> > slow-ish but steady stream of posts / questions / bug reports that are
> > produced saying something is weird. I've seen a good number of these
> > come up in my years in the Haskell community. I'll note that there is
> > also a real cost to telling the truth: witness this thread. This all
> > adds up to a need to do both, which is what we would get by having a
> > richer REPL environment.
>
[..]
> Data.Function could define ($) with the new type, Prelude would re-export
> it with the old type (as a specialization)
[..]

Could you explain why re-exporting a specialized version is better than just
*defining* a specialized equivalent?

Tom

Daniel Gorín

unread,
Feb 7, 2016, 11:16:31 AM2/7/16
to Tom Ellis, haskel...@haskell.org
> [..]
>> Data.Function could define ($) with the new type, Prelude would re-export
>> it with the old type (as a specialization)
> [..]
>
> Could you explain why re-exporting a specialized version is better than just
> *defining* a specialized equivalent?

I guess that for the same reasons it was considered better not to have duplicated definitions in Data.List of the more general functions in Data.Foldable? I’m just saying this seems to be another instance of a recurrent problem that we had before and that we’ll probably face again in the future.

M Farkas-Dyck

unread,
Feb 7, 2016, 4:07:11 PM2/7/16
to Tom Ellis, haskel...@haskell.org
On 07/02/2016, Tom Ellis <tom-lists-has...@jaguarpaw.co.uk> wrote:
> Could you explain why re-exporting a specialized version is better than
> just *defining* a specialized equivalent?

No name clash, to my knowledge

wren romano

unread,
Feb 7, 2016, 6:23:28 PM2/7/16
to haskell
I'm curious...

Ultimately, ($) is just a name for what is otherwise unnameable: the
whitespace which means application. However, application whitespace is
a bit funny since it works uniformly for mono-/polymorphic arguments,
un/boxed arguments, functions/record fields, etc— which is why we keep
running into issues with typing ($). So my curiosity is this: why do
we insist on considering ($) to be a function in the language rather
than being syntax? We have overt syntax for other forms of whitespace,
namely to deal with blocks and indentation, and we don't worry about
what their types are, so why not treat ($) similarly? Sure, there are
higher-order uses of ($), as when people write things like fmap($x),
but afaict none of our typing hacks are worried about continuing to
work in those settings, so there's no particular reason to think that
those uses of a higher-order function capturing function application
should be considered identical to the ($) used with runST, Int#, etc.

--
Live well,
~wren

Michael Orlitzky

unread,
Feb 7, 2016, 7:42:39 PM2/7/16
to haskel...@haskell.org
On 02/07/2016 06:23 PM, wren romano wrote:
> I'm curious...
>
> Ultimately, ($) is just a name for what is otherwise unnameable: the
> whitespace which means application. However, application whitespace is
> a bit funny since it works uniformly for mono-/polymorphic arguments,
> un/boxed arguments, functions/record fields, etc— which is why we keep
> running into issues with typing ($).

I like the new type signature. It indicates that you're about to invoke
dark magic line noise to avoid something simple and well-understood and
in use since the beginning of time (parentheses).

Imants Cekusins

unread,
Feb 8, 2016, 3:16:17 AM2/8/16
to haskell Cafe
what if use of ($) were confined to boxed & lifted types only?

use (...) for unboxed / unlifted types.

would this not simplify the issue somewhat?

Takenobu Tani

unread,
Feb 8, 2016, 5:09:00 AM2/8/16
to Richard Eisenberg, ghc-...@haskell.org, Haskell Cafe
Hi Richard and devs,

What a wonderful (#11549) !
This is a beautiful solution for beginners/newcomers.
Beginners will not confuse and they can gradually go ahead.

I extremely appreciate that you are continuously improving the ghc for us.

Thank you very much,
Takenobu


2016-02-07 0:17 GMT+09:00 Richard Eisenberg <e...@cis.upenn.edu>:
I have made a ticket #11549 (https://ghc.haskell.org/trac/ghc/ticket/11549) requesting a -fshow-runtime-rep flag (recalling that the name levity will soon be outdated) as described in this thread. I will make sure this gets in for the release of 8.0.

Other points:

- You're quite right that (.) could be generalized. But I'll wait for someone to really want this.

- I don't have a non-contrived example of the use of ($) with unlifted types. It's quite possible that when adding the dirty runST hack, it was observed that an unlifted type would be OK. At that point, the type of ($) didn't need to become so elaborate. And now we're just trying not to change old (but perhaps unrequested) behavior.

- For the record, this debate is entirely unrelated to the runST impredicativity hack. (Except, as noted above, perhaps in history.) That hack remains, basically unchanged.

- On Feb 6, 2016, at 9:55 AM, Roman Cheplyaka <ro...@ro-che.info> wrote:
>
> I would call this a simplification rather than a lie.

This is a very convincing argument.

- Thanks, also, for the voice of support. What I love about the Haskell community is that we can have an impassioned debate full of strong opinions, and it all very rarely devolves into a proper flame war. All the posts I've seen in this thread have been constructive and helpful. Thanks.

Richard

Takenobu Tani

unread,
Feb 8, 2016, 5:14:11 AM2/8/16
to Imants Cekusins, Haskell Cafe
Hi Imants and cafe,

Related informal illustrations (and references) are here:

Lifted/unlifted, boxed/unboxed types

Bottom

Regards,
Takenobu


2016-02-07 0:54 GMT+09:00 Imants Cekusins <ima...@gmail.com>:
in addition to Takenobu's links, Real World Haskell explains unboxing
and lifting on p.583

just to clarify, for practical use, is it safe to say that "boxed" and
"lifted" are synonyms? you see, term "boxed" is used in other
languages. I assumed "lifting" related to monads. Hence the confusion.

Imants Cekusins

unread,
Feb 8, 2016, 6:01:44 AM2/8/16
to Haskell Cafe
Thank you very much Takenobu,

these are very clear and extensive explanations.

Rustom Mody

unread,
Feb 8, 2016, 8:02:34 AM2/8/16
to Haskell Cafe
On Mon, Feb 8, 2016 at 4:53 AM, wren romano <wr...@community.haskell.org> wrote:
I'm curious...

Ultimately, ($) is just a name for what is otherwise unnameable: the
whitespace which means application. However, application whitespace is
a bit funny since…


A view from the other side:
1. Delete the unnameable
2. First-class application (its not called '$' but '.' -- a minor difference)
3. Remove all specialness of it

http://blog.languager.org/2014/09/pugofer.html

M Farkas-Dyck

unread,
Feb 8, 2016, 11:03:14 AM2/8/16
to wr...@community.haskell.org, haskell
On 07/02/2016, wren romano <wr...@community.haskell.org> wrote:
> Ultimately, ($) is just a name for what is otherwise unnameable: the
> whitespace which means application.

It is in fact otherwise called "id" ☺

> So my curiosity is this: why do
> we insist on considering ($) to be a function in the language rather
> than being syntax? We have overt syntax for other forms of whitespace,
> namely to deal with blocks and indentation, and we don't worry about
> what their types are, so why not treat ($) similarly?

These other symbols, the block delimiters, are not terms, and so have no types.

Marcin Mrotek

unread,
Feb 8, 2016, 11:13:13 AM2/8/16
to Rustom Mody, Haskell Cafe
1. Delete the unnameable
3. Remove all specialness of it

... and end up with absolutely no way to apply functions that return unboxed values?

Best regards,
Marcin Mrotek 

M Farkas-Dyck

unread,
Feb 8, 2016, 11:28:49 AM2/8/16
to mant...@gsd.uwaterloo.ca, Haskell Cafe
On 06/02/2016, Michał Antkiewicz <mant...@gsd.uwaterloo.ca> wrote:
> Every programmer should understand the difference between boxed and
> unboxed values. Period.

Why? The performance of some code is not critical. Sometimes i need
not care about 1 more indirection in their code, and some authors need
never care, and in these cases i appreciate Haskell not forcing me to
care. I'm not saying every language ought to hide this ­– i quite like
Rust, for example – but most times hiding it works quite well for me
in Haskell.

Michael Orlitzky

unread,
Feb 8, 2016, 11:32:25 AM2/8/16
to haskel...@haskell.org
On 02/08/2016 11:13 AM, Marcin Mrotek wrote:
> 1. Delete the unnameable
> 3. Remove all specialness of it
>
>
> ... and end up with absolutely no way to apply functions that return
> unboxed values?
>

I think the point is that we don't need to worry about what the type of
" " is in the expression "f x", because it's syntax for function
application. If we had /explicit/ syntax for function application (read
the PDF, it's good), there would be no problem to begin with -- syntax
isn't typed.

The suggestion in the PDF is basically to drop the "f x" syntax and
always use "f $ x" which has a lot of merit if you rename "$" to
something less ugly and more obvious. The paper proposes "f.x", but you
could also equate "f." with "f()" to make something crazy like "f(x)" work.

Joachim Durchholz

unread,
Feb 8, 2016, 11:45:40 AM2/8/16
to haskel...@haskell.org
Am 08.02.2016 um 17:32 schrieb Michael Orlitzky:
> The suggestion in the PDF is basically to drop the "f x" syntax and
> always use "f $ x" which has a lot of merit if you rename "$" to
> something less ugly and more obvious.

I'm wondering what the merit would be.

Michael Orlitzky

unread,
Feb 8, 2016, 11:57:01 AM2/8/16
to haskel...@haskell.org
On 02/08/2016 11:45 AM, Joachim Durchholz wrote:
> Am 08.02.2016 um 17:32 schrieb Michael Orlitzky:
>> The suggestion in the PDF is basically to drop the "f x" syntax and
>> always use "f $ x" which has a lot of merit if you rename "$" to
>> something less ugly and more obvious.
>
> I'm wondering what the merit would be.

By making "f $ x" or more generally "f $" syntax, we avoid the very
issue that sparked this thread (that's what wren suggested...). We also
no longer need the voodoo hacks for things like runST. It's less
confusing for the parser and for students (explicit is better than
implicit), etc.

Joachim Durchholz

unread,
Feb 8, 2016, 12:15:22 PM2/8/16
to haskel...@haskell.org
Am 08.02.2016 um 17:56 schrieb Michael Orlitzky:
> On 02/08/2016 11:45 AM, Joachim Durchholz wrote:
>> Am 08.02.2016 um 17:32 schrieb Michael Orlitzky:
>>> The suggestion in the PDF is basically to drop the "f x" syntax and
>>> always use "f $ x" which has a lot of merit if you rename "$" to
>>> something less ugly and more obvious.
>>
>> I'm wondering what the merit would be.
>
> By making "f $ x" or more generally "f $" syntax, we avoid the very
> issue that sparked this thread (that's what wren suggested...).

Wouldn't the type of function application be independent of its syntax?

> We also
> no longer need the voodoo hacks for things like runST.

I can't comment on runST (my Haskell knowledge is really basic). But if
there's a problem because juxtaposition has no representation as an
operator symbol or function name, it should be easier to fix the
syntactic problem than to rewrite the type system.
Of course, if the type system got warts because of the existence of
juxtaposition, then that should be fixed.

> It's less
> confusing for the parser and for students (explicit is better than
> implicit), etc.

Sure, but you don't make that an absolute.
Otherwise you'd have to remove operator precedence, too. And the end
result would look like Lisp.

juxtaposition is so entrenched in almost all branches of math that using
that implicitness to reduce syntactic overhead is a net win.

Will Yager

unread,
Feb 8, 2016, 12:21:41 PM2/8/16
to Marcin Mrotek, Haskell Cafe
On Feb 8, 2016, at 10:13, Marcin Mrotek <marcin.j...@gmail.com> wrote:
> ... and end up with absolutely no way to apply functions that return unboxed values?

Besides juxtaposition, could you not make another operator (e.g. "$#") for this purpose?

--Will

Imants Cekusins

unread,
Feb 8, 2016, 12:33:54 PM2/8/16
to Haskell Cafe
would
f 1 ; 2 + 3

look ok instead of
f 1 $ 2 + 3

?

the semicolon would be part of syntax, not something with a type
the intuition is: pause application until the rest is applied

Edward Kmett

unread,
Feb 8, 2016, 12:39:56 PM2/8/16
to Ben Lippmeier, Haskell Cafe
This doesn't really work in a non-strict language like Haskell with uncontrolled recursion. We often need a lazy int that may be _|_ and shouldn't affect termination of the program unless demanded.

The result would be that you'd actually have to compile all of your code several ways times the number of type arguments and you'd get rather severely different semantics around evaluation as it switched between strictness and laziness. 

Moreover, cycles that happened to involve one of these values would have to tie the knot strictly, meaning you'd have issues like scheme where letrec secretly exposes extra, observable, #f cases when you encounter a cycle.

-Edward

On Sun, Feb 7, 2016 at 7:17 AM, Ben Lippmeier <be...@ouroborus.net> wrote:

> On 7 Feb 2016, at 9:50 pm, Joachim Durchholz <j...@durchholz.org> wrote:

> For the Int/Int# concept, the approaches I have seen either ignore the efficiency and let the machine figure out what to do (Smalltalk, Python, pre-Int# Haskell), or they complicate the type system at the expense of polymorphism (Java, Eiffel), or they complicate the type system even more to regain some form of polymorphism (C++, today's Haskell).

Although I haven’t implemented it, I suspect another approach is to just specialise every polymorphic function at its unboxed type arguments. Boxed and unboxed value types would share the same kind. Of course, full specialisation of polymorphic code assumes that code is available in the interface files, but we’ve almost got that already. Dealing with mutual recursion could be a pain, though.

I don’t think specialisation was an option back when unboxed types were originally implemented. I believe GHC’s support for cross module inlining came some time after the unboxed types, if the publication dates of the relative papers are to be a guide.

Ben.

Michael Orlitzky

unread,
Feb 8, 2016, 12:45:53 PM2/8/16
to haskel...@haskell.org
On 02/08/2016 12:33 PM, Imants Cekusins wrote:
> would
> f 1 ; 2 + 3
>
> look ok instead of
> f 1 $ 2 + 3
>
> ?
>
> the semicolon would be part of syntax, not something with a type
> the intuition is: pause application until the rest is applied

The semicolon is already appropriated sadly:

ghci> do { putStr "Hello" ; putStrLn " world!"; }
Hello world!

Michael Orlitzky

unread,
Feb 8, 2016, 12:59:36 PM2/8/16
to haskel...@haskell.org
On 02/08/2016 12:15 PM, Joachim Durchholz wrote:
>
> Sure, but you don't make that an absolute.
> Otherwise you'd have to remove operator precedence, too. And the end
> result would look like Lisp.
>
> juxtaposition is so entrenched in almost all branches of math that using
> that implicitness to reduce syntactic overhead is a net win.

I don't think anyone was seriously suggesting getting rid of "f x" for
function application. Rustom's post is interesting because it suggests
that if you're going to have only one of "f x" or "f $ x", then it
should be the latter. It's a fun thought experiment.

Since we're stuck with "f x", the question is: do we want *both* as
syntax? If most uses of "$" are for,

putStrLn $ "Hello" ++ " world!"

to avoid,

putStrLn ("Hello" ++ " world!")

then I think it's silly to worry about the type of "$". Most people
don't have to know, care, or use it -- the second example is much
clearer. And if instead you're doing some kind of fmap (((f $) $) $)
gymnastics, then you probably don't mind the type of "$".

Imants Cekusins

unread,
Feb 8, 2016, 1:01:45 PM2/8/16
to haskell Cafe
> The semicolon is already appropriated sadly:

ghci> do { putStr "Hello" ; putStrLn " world!"; }

is it only used for this purpose within braces?
would it be a bad taste to overload ;?

there are examples: dot, parentheses even have type in their
moonlighting capacity:
Prelude> :t ()
() :: ()

Mihai Maruseac

unread,
Feb 8, 2016, 1:17:43 PM2/8/16
to Imants Cekusins, haskell Cafe
On Mon, Feb 8, 2016 at 1:01 PM, Imants Cekusins <ima...@gmail.com> wrote:
> would it be a bad taste to overload ;?

Won't it create ambiguity problems like the following?

ghci> do { putStr "Hello, the answer is "; print $ 40 + 2 }

being changed into the invalid

ghci> do { putStr "Hello, the answer is "; print ; 40 + 2 }

--
Mihai Maruseac (MM)
"If you can't solve a problem, then there's an easier problem you can
solve: find it." -- George Polya

Imants Cekusins

unread,
Feb 8, 2016, 1:31:26 PM2/8/16
to haskell Cafe
> Won't it create ambiguity problems...?

we could set simple criteria for "; "
a) within code block - keep the current meaning : new statement
b) elsewhere: pause application ($)

$ would keep its current meaning and the new "honest, confusing" type.
Still be usable, would not break the code.

; may mark the code visually better than $

e.g. braces are overloaded: code block, data record

Imants Cekusins

unread,
Feb 8, 2016, 1:33:28 PM2/8/16
to haskell Cafe
a) within { code block 1; code block 2; } - keep the current meaning :
new statement

Imants Cekusins

unread,
Feb 8, 2016, 1:46:40 PM2/8/16
to haskell Cafe
ghci> do { putStr "Hello, the answer is "; print (40 + 2); }

reasonably easy to remember rule of thumb:

using braces? use parentheses, too!

Joachim Durchholz

unread,
Feb 8, 2016, 2:28:32 PM2/8/16
to haskel...@haskell.org
Am 08.02.2016 um 18:59 schrieb Michael Orlitzky:
> If most uses of "$" are for,
>
> putStrLn $ "Hello" ++ " world!"
>
> to avoid,
>
> putStrLn ("Hello" ++ " world!")
>
> then I think it's silly to worry about the type of "$". Most people
> don't have to know, care, or use it -- the second example is much
> clearer.

Only if you are on the standard programming language mindset.
I.e. those who learn Haskell first will find putStrLn "Hello" ++
"world!" more natural.

Matthias Hörmann

unread,
Feb 8, 2016, 3:39:26 PM2/8/16
to haskell-cafe
Are you seriously suggesting to overloaded semicolons to *avoid*
confusing newcomers to Haskell from other languages?

And all of that just in case that newcomer happens to look at the
output of :t ($)?

Richard A. O'Keefe

unread,
Feb 8, 2016, 5:36:11 PM2/8/16
to haskel...@haskell.org


On 9/02/16 6:33 am, Imants Cekusins wrote:
> would
> f 1 ; 2 + 3
>
> look ok instead of
> f 1 $ 2 + 3

No. Semicolon already means something in Haskell,
and that's not it.

Joachim Durchholz

unread,
Feb 8, 2016, 5:51:53 PM2/8/16
to haskel...@haskell.org
I'm not seeing a semicolon anywhere here...

Phil Ruffwind

unread,
Feb 8, 2016, 6:06:04 PM2/8/16
to Richard Eisenberg, ghc-devs, Haskell Cafe
> Another great question that has come up is about Haddock output (Hackage). I
> think Haddock needs to add a facility where library authors can include
> specializations of an overly general type. This can be done in commentary,
> but it's not as prominent.

I think a low-hanging fruit would be to add the ability for Haddock to
parse some sort of specialized types annotation (could be entirely in
the comments) and display them adjacent to the true type. The types
do have to be manually written, but at least they can be type-checked.

Lens does this in its documentation and they are very helpful for
learning the library.

> (^.) :: s -> Getting a s a -> a
> (^.) :: s -> Getter s a -> a
> (^.) :: Monoid m => s -> Fold s m -> m
> (^.) :: s -> Iso' s a -> a
> (^.) :: s -> Lens' s a -> a
> (^.) :: Monoid m => s -> Traversal' s m -> m

Richard A. O'Keefe

unread,
Feb 8, 2016, 6:54:12 PM2/8/16
to haskel...@haskell.org


On 8/02/16 1:42 pm, Michael Orlitzky wrote:
> I like the new type signature. It indicates that you're about to invoke
> dark magic line noise to avoid something simple and well-understood and
> in use since the beginning of time (parentheses).
So time began in the 15th century?
No, wait, that's text. For mathematics,
time must have begun in the 16th century.
http://jeff560.tripod.com/grouping.html

Wait, those were grouping parentheses, not application ones.
According to https://en.wikipedia.org/wiki/History_of_the_function_concept
something explicit resembling modern ideas of a "function" appeared
in the 17th century, so THAT's when time began.

I always wondered.

Richard A. O'Keefe

unread,
Feb 8, 2016, 7:32:18 PM2/8/16
to haskel...@haskell.org
As Manuel wrote:
>> I expect that every single person teaching Haskell
>> is going to be unhappy about it.

If I were in the happy position of still teaching Haskell, I would be
unhappy about it.

In my own use of Haskell, I've only played briefly with # types and then
decided to
leave them to library writers. I'd certainly never heard of "levity
polymorphism"
before.

I can agree that for a functional language, being able to apply any
reasonable
function to any reasonable matching argument has to be doable, but such a
fundamental operation surely needs to be simple to describe?

Is there a complete lit of all the affected functions?
Is a complete list even possible?
It is loading more messages.
0 new messages