Discussion:
[Haskell-cafe] New type of ($) operator in GHC 8.0 is problematic
Johannes Waldmann
2016-02-05 16:14:16 UTC
Permalink
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.
Colin Adams
2016-02-05 16:22:26 UTC
Permalink
What's changed?

On 5 February 2016 at 16:14, Johannes Waldmann <
Post by Johannes Waldmann
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
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Johannes Waldmann
2016-02-05 16:25:15 UTC
Permalink
Post by Colin Adams
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.
Kyle Hanson
2016-02-05 17:55:08 UTC
Permalink
I am also happy the discussion was posted here. Although I don't teach
Haskell professionally, one of the things I loved to do was show people how
simple Haskell really was by inspecting types and slowly putting the puzzle
pieces together.

Summary of the problem for others:

From *Takenobu Tani*

Before ghc7.8:

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

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

Beginners should only understand about following:

* type variable (polymorphism)


After ghc8.0:

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

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



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

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

From *Richard Eisenberg:*

- It's interesting that the solution to the two problems Takenobu
pulls out below (but others have hinted at in this thread) is by
having an alternate Prelude for beginners. I believe that having an
alternate beginners' Prelude is becoming essential. I know I'm not the
first one to suggest this, but a great many issues that teachers of
Haskell have raised with me and posts on this and other lists would be
solved by an alternate Prelude for beginners.

I don't like the idea of fragmenting Haskell into "beginners" and
"advanced" versions. Its hard enough to get people to believe Haskell is
easy. If they see that they aren't using the "real" prelude, Haskell will
still be this magic black box that is too abstract and difficult to
understand. If they have to use a "dumbed down" version of Haskell to
learn, its not as compelling.

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

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

--
Kyle


On Fri, Feb 5, 2016 at 8:26 AM, Tom Ellis <
Post by Johannes Waldmann
Post by Colin Adams
What's changed?
I was referring to a discussion on ghc-devs, see
https://mail.haskell.org/pipermail/ghc-devs/2016-February/011268.html
and mixed up addresses when replying.
I'm glad you did, because this is the first I've heard of it!
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Christopher Allen
2016-02-05 17:59:18 UTC
Permalink
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.
Post by Kyle Hanson
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.
From *Takenobu Tani*
Prelude> :t foldr
foldr :: (a -> b -> b) -> b -> [a] -> b
Prelude> :t ($)
($) :: (a -> b) -> a -> b
* type variable (polymorphism)
Prelude> :t foldr
foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b
Prelude> :t ($)
($)
:: forall (w :: GHC.Types.Levity) a (b :: TYPE w).
(a -> b) -> a -> b
With this change it looks like I will no longer be able to keep `$` in my
toolbox since telling a beginner its "magic" goes against what I believe
Haskell is good at, being well defined and easy to understand (Not well
defined in terms of Types but well defined in terms of ability to precisely
and concisely explain and define whats going on).
It looks like where the discussion is going is to have these types show by
default but eventually have an Alternative prelude for beginners.
From *Richard Eisenberg:*
- It's interesting that the solution to the two problems Takenobu pulls out below (but others have hinted at in this thread) is by having an alternate Prelude for beginners. I believe that having an alternate beginners' Prelude is becoming essential. I know I'm not the first one to suggest this, but a great many issues that teachers of Haskell have raised with me and posts on this and other lists would be solved by an alternate Prelude for beginners.
I don't like the idea of fragmenting Haskell into "beginners" and
"advanced" versions. Its hard enough to get people to believe Haskell is
easy. If they see that they aren't using the "real" prelude, Haskell will
still be this magic black box that is too abstract and difficult to
understand. If they have to use a "dumbed down" version of Haskell to
learn, its not as compelling.
There is something powerful about using the same idiomatic tools as the
"big boys" and have the tools still be able to be easy to understand.... by
default. Adding complexity to the default Haskell runs the risk of further
alienating newcomers to the language who have a misconception that its too
hard.
Admittedly, I am not well informed of the state of GHC 8.0 development and
haven't had time to fully look into the situation. I am very interested to
see where this conversation and the default complexity of Haskell goes.
--
Kyle
On Fri, Feb 5, 2016 at 8:26 AM, Tom Ellis <
Post by Johannes Waldmann
Post by Colin Adams
What's changed?
I was referring to a discussion on ghc-devs, see
https://mail.haskell.org/pipermail/ghc-devs/2016-February/011268.html
and mixed up addresses when replying.
I'm glad you did, because this is the first I've heard of it!
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
--
Chris Allen
Currently working on http://haskellbook.com
Rustom Mody
2016-02-09 13:20:32 UTC
Permalink
Post by Christopher Allen
Post by Kyle Hanson
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.
From *Takenobu Tani*
Prelude> :t foldr
foldr :: (a -> b -> b) -> b -> [a] -> b
Prelude> :t ($)
($) :: (a -> b) -> a -> b
* type variable (polymorphism)
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
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.
Consider these two delightful pianists: Martha
and Rose


- Are they playing the same instruments?
- Would they need the same teachers?
- Ultimately, is the single moniker "pianist" meaningfully applicable to
both?


I believe we are too taken with the fact that programming language *theory*
has advanced in the last couple of decades, while we miss the fact that
programming *pedagogy* has regressed in the same period. And one of the big
regresses is the illusion that a *single *language that spans the spectrum
from beginner learning to serious software engineering is a neat idea: a
grand unified/universal language. Such a language already exists -- C++.
An earlier generation called it PL-1.

FP in ACM Curriculum 2013
<http://blog.languager.org/2015/06/functional-programming-moving-target.html>
spells out this – omnibus language – and such fallacies in more detail.

And as regards prior art regarding the benefits for multiple close but
different languages for teaching, one could see the multiple teachpacks
<http://docs.racket-lang.org/teachpack/index.html?q=> of Scheme/Racket
And even closer to home, helium
<http://www.open.ou.nl/bhr/heeren-helium.pdf> is a haskell expressly
designed to make teaching easier by not over-generalizing types
Joachim Durchholz
2016-02-09 14:43:00 UTC
Permalink
Post by Rustom Mody
And one of the big
regresses is the illusion that a *single *language that spans the spectrum
from beginner learning to serious software engineering is a neat idea: a
grand unified/universal language.
It is still an ideal.
Not because it is such a good idea. I'm pretty much unconvinced whether
that is the case or not.
It is an ideal to approximatet because learning a new language, and
learning it well enough to use it in anger, is such a huge investment in
time and effort, and we simply cannot afford to build a language for
each domain. Also, we cannot do so because there isn't even a consensus
what the domains should be, and I'd expect such a list to be a moving
target anyway.
Post by Rustom Mody
Such a language already exists -- C++.
An earlier generation called it PL-1.
No no no.
C++ was never meant to be easy to learn. It was intended to be easy to
learn for C programmers, but C wasn't intended to be easy to learn, it
was intended to be efficient on PDP-series computers with a
non-optimizing compiler.
PL-1 wasn't intended to be easy to learn either - it was hoped it would
be, but the primary goal was to include as many language features as
humanly possible, in the hopes of creating something powerful.
So the two languages and the universal-easy-to-learn camp do not share
any design goals.
Post by Rustom Mody
FP in ACM Curriculum 2013
<http://blog.languager.org/2015/06/functional-programming-moving-target.html>
spells out this – omnibus language – and such fallacies in more detail.
He claims this, but he does not back that up with any arguments.
There's only reference to authority (Peter Naur).
Post by Rustom Mody
And as regards prior art regarding the benefits for multiple close but
different languages for teaching, one could see the multiple teachpacks
<http://docs.racket-lang.org/teachpack/index.html?q=> of Scheme/Racket
And even closer to home, helium
<http://www.open.ou.nl/bhr/heeren-helium.pdf> is a haskell expressly
designed to make teaching easier by not over-generalizing types
I think the link you gave is making a subtly but fundamentally different
point: That to teach programming, you need a different and simplified
language to get the core points across. There are actually good points
to be made in favor of that approach.

However, this is about making Haskell the working language easy to
learn. The demography includes people who know what a type system is,
what a function is, and they will usually even know what a side effect
is and already avoid that if they can.
Tell them that they see a simplified prelude and they will want to see
the real one. Show them the real one and they will run away, flailing
and scream, just as ten years ago, you could achieve the same effect by
mentioning monads.

If the type system is starting to make it hard to learn the language
well enough to use it professionally, or to even understand what the
professional library writers did and why, then the type system has
become too difficult.
Carter Schonwald
2016-02-09 17:04:04 UTC
Permalink
I'd like to opine that I personally like that our types are getting more
honest in reflecting how things work, and that even the impredicativety
hack might be on track to being a userland expressible construct in a later
ghc release (if my fuzzy understanding of the impredicative subsumption
work thats in progress is correct and that it actually pans out )

i like the bike shed, i dont care how we mix the paint colors, as long as
it doesn't create more confusion

cheers
_Carter
Post by Joachim Durchholz
Post by Rustom Mody
And one of the big
regresses is the illusion that a *single *language that spans the spectrum
from beginner learning to serious software engineering is a neat idea: a
grand unified/universal language.
It is still an ideal.
Not because it is such a good idea. I'm pretty much unconvinced whether
that is the case or not.
It is an ideal to approximatet because learning a new language, and
learning it well enough to use it in anger, is such a huge investment in
time and effort, and we simply cannot afford to build a language for each
domain. Also, we cannot do so because there isn't even a consensus what the
domains should be, and I'd expect such a list to be a moving target anyway.
Post by Rustom Mody
Such a language already exists -- C++.
An earlier generation called it PL-1.
No no no.
C++ was never meant to be easy to learn. It was intended to be easy to
learn for C programmers, but C wasn't intended to be easy to learn, it was
intended to be efficient on PDP-series computers with a non-optimizing
compiler.
PL-1 wasn't intended to be easy to learn either - it was hoped it would
be, but the primary goal was to include as many language features as
humanly possible, in the hopes of creating something powerful.
So the two languages and the universal-easy-to-learn camp do not share any
design goals.
FP in ACM Curriculum 2013
Post by Rustom Mody
<
http://blog.languager.org/2015/06/functional-programming-moving-target.html
spells out this – omnibus language – and such fallacies in more detail.
He claims this, but he does not back that up with any arguments.
There's only reference to authority (Peter Naur).
And as regards prior art regarding the benefits for multiple close but
Post by Rustom Mody
different languages for teaching, one could see the multiple teachpacks
<http://docs.racket-lang.org/teachpack/index.html?q=> of Scheme/Racket
And even closer to home, helium
<http://www.open.ou.nl/bhr/heeren-helium.pdf> is a haskell expressly
designed to make teaching easier by not over-generalizing types
I think the link you gave is making a subtly but fundamentally different
point: That to teach programming, you need a different and simplified
language to get the core points across. There are actually good points to
be made in favor of that approach.
However, this is about making Haskell the working language easy to learn.
The demography includes people who know what a type system is, what a
function is, and they will usually even know what a side effect is and
already avoid that if they can.
Tell them that they see a simplified prelude and they will want to see the
real one. Show them the real one and they will run away, flailing and
scream, just as ten years ago, you could achieve the same effect by
mentioning monads.
If the type system is starting to make it hard to learn the language well
enough to use it professionally, or to even understand what the
professional library writers did and why, then the type system has become
too difficult.
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Rustom Mody
2016-02-09 17:13:36 UTC
Permalink
On Tue, Feb 9, 2016 at 10:34 PM, Carter Schonwald <
Post by Carter Schonwald
I'd like to opine that I personally like that our types are getting more
honest in reflecting how things work,
Me too!
I just want to +1 the alternative/noob prelude suggestion which I believe
came from many people; not to reduce the (default) honesty of the *standard*
prelude
Joachim Durchholz
2016-02-09 17:17:37 UTC
Permalink
Post by Carter Schonwald
I'd like to opine that I personally like that our types are getting more
honest in reflecting how things work,
I'm pretty much in the same boat with that.
I just have a feeling that the typing is getting complicated because the
compiler isn't good enough to infer whether a type is lifted or not,
boxed or not. Essentially, there's that constant temptation to give
programmers access to machine integers.
It's a valid concern, but it complicates the type system tremendously.

I feel that this is similar to expressing value constraints in the type
system, e.g. ranges or squareness of matrixes. Yes it can be done in
Haskell's type system, yes it does typecheck beautifully, but the type
declarations behind these kinds of feats will just make any ordinary
programmer go MEGO. Even the bright ones.
I conclude that the type system isn't the right place for that kind of
checking. To be understandable, such constraints need to be expressed as
boolean assertions, not as some inductive construct. YMMV.

Richard Eisenberg
2016-02-05 18:13:23 UTC
Permalink
Perhaps it will aid the discussion to see that the type of ($) will, for better or worse, be changing again before 8.0.
($) :: 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
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.
From Takenobu Tani
Prelude> :t foldr
foldr :: (a -> b -> b) -> b -> [a] -> b
Prelude> :t ($)
($) :: (a -> b) -> a -> b
* type variable (polymorphism)
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.
- 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
Post by Johannes Waldmann
Post by Colin Adams
What's changed?
I was referring to a discussion on ghc-devs, see
https://mail.haskell.org/pipermail/ghc-devs/2016-February/011268.html
and mixed up addresses when replying.
I'm glad you did, because this is the first I've heard of it!
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Christopher Allen
2016-02-05 19:05:30 UTC
Permalink
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.
Post by Richard Eisenberg
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.
Post by Richard Eisenberg
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
($) :: 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
---
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
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.
From *Takenobu Tani*
Prelude> :t foldr
foldr :: (a -> b -> b) -> b -> [a] -> b
Prelude> :t ($)
($) :: (a -> b) -> a -> b
* type variable (polymorphism)
Prelude> :t foldr
foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b
Prelude> :t ($)
($)
:: forall (w :: GHC.Types.Levity) a (b :: TYPE w).
(a -> b) -> a -> b
With this change it looks like I will no longer be able to keep `$` in my
toolbox since telling a beginner its "magic" goes against what I believe
Haskell is good at, being well defined and easy to understand (Not well
defined in terms of Types but well defined in terms of ability to precisely
and concisely explain and define whats going on).
It looks like where the discussion is going is to have these types show by
default but eventually have an Alternative prelude for beginners.
From *Richard Eisenberg:*
- It's interesting that the solution to the two problems Takenobu pulls out below (but others have hinted at in this thread) is by having an alternate Prelude for beginners. I believe that having an alternate beginners' Prelude is becoming essential. I know I'm not the first one to suggest this, but a great many issues that teachers of Haskell have raised with me and posts on this and other lists would be solved by an alternate Prelude for beginners.
I don't like the idea of fragmenting Haskell into "beginners" and
"advanced" versions. Its hard enough to get people to believe Haskell is
easy. If they see that they aren't using the "real" prelude, Haskell will
still be this magic black box that is too abstract and difficult to
understand. If they have to use a "dumbed down" version of Haskell to
learn, its not as compelling.
There is something powerful about using the same idiomatic tools as the
"big boys" and have the tools still be able to be easy to understand.... by
default. Adding complexity to the default Haskell runs the risk of further
alienating newcomers to the language who have a misconception that its too
hard.
Admittedly, I am not well informed of the state of GHC 8.0 development and
haven't had time to fully look into the situation. I am very interested to
see where this conversation and the default complexity of Haskell goes.
--
Kyle
On Fri, Feb 5, 2016 at 8:26 AM, Tom Ellis <
Post by Johannes Waldmann
Post by Colin Adams
What's changed?
I was referring to a discussion on ghc-devs, see
https://mail.haskell.org/pipermail/ghc-devs/2016-February/011268.html
and mixed up addresses when replying.
I'm glad you did, because this is the first I've heard of it!
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
_______________________________________________
ghc-devs mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/ghc-devs
--
Chris Allen
Currently working on http://haskellbook.com
Bardur Arantsson
2016-02-05 19:12:25 UTC
Permalink
Post by Christopher Allen
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
2016-02-05 19:16:56 UTC
Permalink
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
...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.
Post by Christopher Allen
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
Post by Christopher Allen
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,
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
--
Chris Allen
Currently working on http://haskellbook.com
Will Yager
2016-02-05 19:20:25 UTC
Permalink
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.
($) :: forall (r :: RuntimeRep) (a :: *) (b :: TYPE r). (a -> b) -> a -> b
Richard Eisenberg
2016-02-06 00:09:44 UTC
Permalink
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 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
+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!
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.
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.
Post by Richard Eisenberg
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.
Perhaps it will aid the discussion to see that the type of ($) will, for better or worse, be changing again before 8.0.
Post by Richard Eisenberg
($) :: forall (r :: RuntimeRep) (a :: *) (b :: TYPE r). (a -> b) -> a -> b
---
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
Post by Richard Eisenberg
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.
From Takenobu Tani
Prelude> :t foldr
foldr :: (a -> b -> b) -> b -> [a] -> b
Prelude> :t ($)
($) :: (a -> b) -> a -> b
* type variable (polymorphism)
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.
- 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
Post by Johannes Waldmann
Post by Colin Adams
What's changed?
I was referring to a discussion on ghc-devs, see
https://mail.haskell.org/pipermail/ghc-devs/2016-February/011268.html
and mixed up addresses when replying.
I'm glad you did, because this is the first I've heard of it!
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
_______________________________________________
ghc-devs mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/ghc-devs
--
Chris Allen
Currently working on http://haskellbook.com
_______________________________________________
ghc-devs mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/ghc-devs
Mihai Maruseac
2016-02-06 00:27:42 UTC
Permalink
Post by Richard Eisenberg
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
Artyom
2016-02-06 00:47:43 UTC
Permalink
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
Post by Mihai Maruseac
Post by Richard Eisenberg
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.
​
Richard Eisenberg
2016-02-06 01:51:13 UTC
Permalink
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)

Also see #8809 (https://ghc.haskell.org/trac/ghc/ticket/8809)

Any volunteers to implement this? :)

Richard
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
Post by Mihai Maruseac
Post by Richard Eisenberg
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.
M Farkas-Dyck
2016-02-06 02:24:47 UTC
Permalink
Post by Richard Eisenberg
-- click on the type
The question so remains: what would we write to a purely textual terminal?
Mihai Maruseac
2016-02-06 02:29:07 UTC
Permalink
Post by M Farkas-Dyck
Post by Richard Eisenberg
-- 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
2016-02-06 08:37:42 UTC
Permalink
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
Michał Antkiewicz
2016-02-06 15:32:52 UTC
Permalink
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ł
Post by Richard Eisenberg
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
λ> :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)
Also see #8809 (https://ghc.haskell.org/trac/ghc/ticket/8809)
Any volunteers to implement this? :)
Richard
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
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.
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Imants Cekusins
2016-02-06 15:54:44 UTC
Permalink
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
2016-02-06 16:33:01 UTC
Permalink
Post by Imants Cekusins
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
See
https://ghc.haskell.org/trac/ghc/wiki/Commentary/Compiler/TypeType#Classifyingtypes
Post by Imants Cekusins
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.
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Takenobu Tani
2016-02-08 10:14:05 UTC
Permalink
Hi Imants and cafe,

Related informal illustrations (and references) are here:

Lifted/unlifted, boxed/unboxed types
https://takenobu-hs.github.io/downloads/haskell_lazy_evaluation.pdf#page=182

Bottom
https://takenobu-hs.github.io/downloads/haskell_lazy_evaluation.pdf#page=164

Regards,
Takenobu
Post by Imants Cekusins
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.
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Imants Cekusins
2016-02-08 11:01:36 UTC
Permalink
Thank you very much Takenobu,

these are very clear and extensive explanations.
Ben Lippmeier
2016-02-07 08:26:26 UTC
Permalink
Post by Michał Antkiewicz
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
2016-02-07 09:22:29 UTC
Permalink
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.
Richard Eisenberg
2016-02-07 14:59:25 UTC
Permalink
Post by Evan Laforge
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
Post by Evan Laforge
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.
Post by Evan Laforge
3, Is anything other than ($) TypeRep-polymorphic?
Yes: undefined and error. These really are used at unlifted types.
Post by Evan Laforge
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.
Post by Evan Laforge
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
Daniel Gorín
2016-02-07 15:56:25 UTC
Permalink
Post by Richard Eisenberg
Post by Evan Laforge
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).
Daniel Gorín
2016-02-07 16:16:01 UTC
Permalink
[..]
Post by Daniel Gorín
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
2016-02-07 21:07:05 UTC
Permalink
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
2016-02-07 23:23:21 UTC
Permalink
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
2016-02-08 00:42:29 UTC
Permalink
Post by wren romano
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
2016-02-08 08:16:03 UTC
Permalink
what if use of ($) were confined to boxed & lifted types only?

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

would this not simplify the issue somewhat?
Richard A. O'Keefe
2016-02-08 23:54:04 UTC
Permalink
Post by Michael Orlitzky
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.
Michael Orlitzky
2016-02-09 00:34:31 UTC
Permalink
Post by Richard A. O'Keefe
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.
I was referring to grouping parentheses. Most uses of "$" are for stupid
things like "sin $ 1 + 2" where parentheses would be much more clear. By
"beginning of time" I thought it was clear I meant "I don't feel like
looking it up," but thanks -- we now know that people have been using
parentheses about 400 years longer than "$" for grouping.
Kosyrev Serge
2016-02-09 11:27:44 UTC
Permalink
Post by Michael Orlitzky
Most uses of "$" are for stupid
things like "sin $ 1 + 2" where parentheses would be much more clear.
"$" simplifies visual perception through two factors:

1. we are relieved from counting parentheses
2. it serves as a cue to treat the entire remaining part until ")" as
part of the same expression

Case in point (only slightly contrived) -- which one is easier to
visually parse to you:

foo (thInt $ fromIntegral $ c2hsValueInt cexp) (thInt $ fromIntegral $ c2hsValueInt cexp)

foo (thInt (fromIntegral (c2hsValueInt cexp))) (thInt (fromIntegral (c2hsValueInt cexp)))

Me, I get a blood pressure spike roughy here ---^
--
с уважениeм / respectfully,
Косырев Сергей
Geraldus
2016-02-09 11:48:59 UTC
Permalink
As for me, I don't like ubiquitous $ everywhere, often I prefer parentheses
over $ (this might be due to my Elisp practices), and I like very much dot
style.

Most uses of "$" are for stupid
things like "sin $ 1 + 2" where parentheses would be much more clear.

Agree.

"$" simplifies visual perception through two factors:

I think this does not hold for every single person.


1. we are relieved from counting parentheses

This is not big deal if your editor/IDE can highlight parentheses and even
better provide commands to jump to opening/closing parenthesis.

foo (thInt $ fromIntegral $ c2hsValueInt cexp) (thInt $ fromIntegral $
c2hsValueInt cexp)

foo (thInt (fromIntegral (c2hsValueInt cexp))) (thInt (fromIntegral
(c2hsValueInt cexp)))

Me, I get a blood pressure spike roughy here ---^

Personally, I find hard to read expressions like:

someFn $ someOtherFn $ more $ more $ val

(someFn . someOtherFn . more . more $ val)

seems better for me, but this is also good:

someFn (someOtherFn (more (more val)))

Parentheses annoys me only when I need to wrap something by parentheses in
Emacs with electric-pairs-mode :D
Joachim Durchholz
2016-02-09 12:17:22 UTC
Permalink
Post by Kosyrev Serge
Case in point (only slightly contrived) -- which one is easier to
foo (thInt $ fromIntegral $ c2hsValueInt cexp) (thInt $ fromIntegral $ c2hsValueInt cexp)
foo (thInt (fromIntegral (c2hsValueInt cexp))) (thInt (fromIntegral (c2hsValueInt cexp)))
You can always denest by naming subexpressions.
e.g.

let subexpr = thInt (fromIntegral (c2hsValueInt cexp)))
in foo subexpr subexpr

or

let subexpr = (thInt . fromIntegral . c2hsValueInt) cexp
in foo subexpr subexpr

or

let fn = thInt . fromIntegral . c2hsValueInt
in foo (fn cexp) (fn cexp)

I routinely do that kind of denesting as soon as a line goes beyond 72
characters. I have found that such code is usually far easier to read
regardless of what I'm doing in the line.

I think the problem is not that you cannot write readable code, it is
that people do not use existing facilities for that.
If that's correct, then adding another facility for the same purpose is
unlikely to help.
Michael Orlitzky
2016-02-09 13:26:13 UTC
Permalink
Post by Kosyrev Serge
Case in point (only slightly contrived) -- which one is easier to
foo (thInt $ fromIntegral $ c2hsValueInt cexp) (thInt $ fromIntegral $ c2hsValueInt cexp)
foo (thInt (fromIntegral (c2hsValueInt cexp))) (thInt (fromIntegral (c2hsValueInt cexp)))
Me, I get a blood pressure spike roughy here ---^
let th_exp = (thInt . fromIntegral . c2hsValueInt) cexp
foo th_exp th_exp

The parentheses are a warning sign, and using "$" above only lets you
make it look cleaner without fixing the problem. It has hidden the fact
that you're computing f(g(h(x))) twice, and there's a better way to do that.
Bryan Richter
2016-02-09 17:12:40 UTC
Permalink
Post by Kosyrev Serge
Post by Michael Orlitzky
Most uses of "$" are for stupid
things like "sin $ 1 + 2" where parentheses would be much more clear.
1. we are relieved from counting parentheses
2. it serves as a cue to treat the entire remaining part until ")" as
part of the same expression
Case in point (only slightly contrived) -- which one is easier to
foo (thInt $ fromIntegral $ c2hsValueInt cexp) (thInt $ fromIntegral $ c2hsValueInt cexp)
foo (thInt (fromIntegral (c2hsValueInt cexp))) (thInt (fromIntegral (c2hsValueInt cexp)))
My readability problem with this statement is line length. How about:

foo (thInt (fromIntegral (c2hsValueInt cexp)))
(thInt (fromIntegral (c2hsValueInt cexp)))

I apologize for playing syntax golf, but I do want to cast a small
vote for preferring parentheses over ($). It is this preference that
makes me side with Orlitzky's argument that the type of ($) is
irrelevant for beginners, since beginners should be encouraged to use
parentheses anyways. It's One Less Thing To Worry About(tm).
Rustom Mody
2016-02-08 13:02:08 UTC
Permalink
Post by wren romano
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

With justifications here: http://www.the-magus.in/Publications/ewd.pdf
Marcin Mrotek
2016-02-08 16:13:04 UTC
Permalink
Post by Rustom Mody
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
Michael Orlitzky
2016-02-08 16:32:17 UTC
Permalink
Post by Rustom Mody
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
2016-02-08 16:45:33 UTC
Permalink
Post by 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
2016-02-08 16:56:53 UTC
Permalink
Post by Joachim Durchholz
Post by 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
2016-02-08 17:15:14 UTC
Permalink
Post by Michael Orlitzky
Post by Joachim Durchholz
Post by 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?
Post by Michael Orlitzky
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.
Post by Michael Orlitzky
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.
Michael Orlitzky
2016-02-08 17:59:29 UTC
Permalink
Post by Joachim Durchholz
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 "$".
Joachim Durchholz
2016-02-08 19:28:23 UTC
Permalink
Post by 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
2016-02-08 20:39:20 UTC
Permalink
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 ($)?
Post by Joachim Durchholz
Post by 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.
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Joachim Durchholz
2016-02-08 22:51:43 UTC
Permalink
I'm not seeing a semicolon anywhere here...
Post by Matthias Hörmann
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 ($)?
Post by Joachim Durchholz
Post by 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.
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Will Yager
2016-02-08 17:21:26 UTC
Permalink
... 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
2016-02-08 17:33:46 UTC
Permalink
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
Michael Orlitzky
2016-02-08 17:45:46 UTC
Permalink
Post by Imants Cekusins
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!
Imants Cekusins
2016-02-08 18:01:37 UTC
Permalink
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
2016-02-08 18:16:58 UTC
Permalink
Post by Imants Cekusins
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
2016-02-08 18:31:16 UTC
Permalink
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
2016-02-08 18:33:22 UTC
Permalink
a) within { code block 1; code block 2; } - keep the current meaning :
new statement
Imants Cekusins
2016-02-08 18:46:34 UTC
Permalink
ghci> do { putStr "Hello, the answer is "; print (40 + 2); }

reasonably easy to remember rule of thumb:

using braces? use parentheses, too!
Richard A. O'Keefe
2016-02-08 22:35:57 UTC
Permalink
Post by Imants Cekusins
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.
M Farkas-Dyck
2016-02-08 16:03:05 UTC
Permalink
Post by wren romano
Ultimately, ($) is just a name for what is otherwise unnameable: the
whitespace which means application.
It is in fact otherwise called "id" ☺
Post by wren romano
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.
Joachim Durchholz
2016-02-07 10:50:40 UTC
Permalink
Post by 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.
Post by Ben Lippmeier
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.
Post by Ben Lippmeier
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
Ben Lippmeier
2016-02-07 12:17:13 UTC
Permalink
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.
Edward Kmett
2016-02-08 17:39:51 UTC
Permalink
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
Post by Joachim Durchholz
Post by Joachim Durchholz
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.
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
M Farkas-Dyck
2016-02-08 16:28:35 UTC
Permalink
Post by Michał Antkiewicz
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.
Takenobu Tani
2016-02-06 00:28:58 UTC
Permalink
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.

[1] http://takenobu-hs.github.io/downloads/type_introduction_illustrated.pdf

Regards,
Takenobu
Post by Richard Eisenberg
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 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
+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!
Post by Christopher Allen
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
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.
Post by Richard Eisenberg
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.
Post by Richard Eisenberg
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.
($) :: 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
---
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
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.
From *Takenobu Tani*
Prelude> :t foldr
foldr :: (a -> b -> b) -> b -> [a] -> b
Prelude> :t ($)
($) :: (a -> b) -> a -> b
* type variable (polymorphism)
Prelude> :t foldr
foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b
Prelude> :t ($)
($)
:: forall (w :: GHC.Types.Levity) a (b :: TYPE w).
(a -> b) -> a -> b
With this change it looks like I will no longer be able to keep `$` in
my toolbox since telling a beginner its "magic" goes against what I believe
Haskell is good at, being well defined and easy to understand (Not well
defined in terms of Types but well defined in terms of ability to precisely
and concisely explain and define whats going on).
It looks like where the discussion is going is to have these types show
by default but eventually have an Alternative prelude for beginners.
From *Richard Eisenberg:*
- It's interesting that the solution to the two problems Takenobu pulls out below (but others have hinted at in this thread) is by having an alternate Prelude for beginners. I believe that having an alternate beginners' Prelude is becoming essential. I know I'm not the first one to suggest this, but a great many issues that teachers of Haskell have raised with me and posts on this and other lists would be solved by an alternate Prelude for beginners.
I don't like the idea of fragmenting Haskell into "beginners" and
"advanced" versions. Its hard enough to get people to believe Haskell is
easy. If they see that they aren't using the "real" prelude, Haskell will
still be this magic black box that is too abstract and difficult to
understand. If they have to use a "dumbed down" version of Haskell to
learn, its not as compelling.
There is something powerful about using the same idiomatic tools as the
"big boys" and have the tools still be able to be easy to understand.... by
default. Adding complexity to the default Haskell runs the risk of further
alienating newcomers to the language who have a misconception that its too
hard.
Admittedly, I am not well informed of the state of GHC 8.0 development
and haven't had time to fully look into the situation. I am very interested
to see where this conversation and the default complexity of Haskell goes.
--
Kyle
On Fri, Feb 5, 2016 at 8:26 AM, Tom Ellis <
Post by Johannes Waldmann
Post by Colin Adams
What's changed?
I was referring to a discussion on ghc-devs, see
https://mail.haskell.org/pipermail/ghc-devs/2016-February/011268.html
and mixed up addresses when replying.
I'm glad you did, because this is the first I've heard of it!
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
_______________________________________________
ghc-devs mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/ghc-devs
--
Chris Allen
Currently working on http://haskellbook.com
_______________________________________________
ghc-devs mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/ghc-devs
_______________________________________________
ghc-devs mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/ghc-devs
Richard Eisenberg
2016-02-06 15:17:36 UTC
Permalink
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.
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
2016-02-08 10:08:55 UTC
Permalink
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
Post by Richard Eisenberg
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.
- 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.
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
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Phil Ruffwind
2016-02-08 23:05:34 UTC
Permalink
Post by Richard Eisenberg
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.
Post by Richard Eisenberg
(^.) :: 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
Kyle Hanson
2016-02-05 19:46:17 UTC
Permalink
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
Post by Richard Eisenberg
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
($) :: 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
---
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
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.
From *Takenobu Tani*
Prelude> :t foldr
foldr :: (a -> b -> b) -> b -> [a] -> b
Prelude> :t ($)
($) :: (a -> b) -> a -> b
* type variable (polymorphism)
Prelude> :t foldr
foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b
Prelude> :t ($)
($)
:: forall (w :: GHC.Types.Levity) a (b :: TYPE w).
(a -> b) -> a -> b
With this change it looks like I will no longer be able to keep `$` in my
toolbox since telling a beginner its "magic" goes against what I believe
Haskell is good at, being well defined and easy to understand (Not well
defined in terms of Types but well defined in terms of ability to precisely
and concisely explain and define whats going on).
It looks like where the discussion is going is to have these types show by
default but eventually have an Alternative prelude for beginners.
From *Richard Eisenberg:*
- It's interesting that the solution to the two problems Takenobu pulls out below (but others have hinted at in this thread) is by having an alternate Prelude for beginners. I believe that having an alternate beginners' Prelude is becoming essential. I know I'm not the first one to suggest this, but a great many issues that teachers of Haskell have raised with me and posts on this and other lists would be solved by an alternate Prelude for beginners.
I don't like the idea of fragmenting Haskell into "beginners" and
"advanced" versions. Its hard enough to get people to believe Haskell is
easy. If they see that they aren't using the "real" prelude, Haskell will
still be this magic black box that is too abstract and difficult to
understand. If they have to use a "dumbed down" version of Haskell to
learn, its not as compelling.
There is something powerful about using the same idiomatic tools as the
"big boys" and have the tools still be able to be easy to understand.... by
default. Adding complexity to the default Haskell runs the risk of further
alienating newcomers to the language who have a misconception that its too
hard.
Admittedly, I am not well informed of the state of GHC 8.0 development and
haven't had time to fully look into the situation. I am very interested to
see where this conversation and the default complexity of Haskell goes.
--
Kyle
On Fri, Feb 5, 2016 at 8:26 AM, Tom Ellis <
Post by Johannes Waldmann
Post by Colin Adams
What's changed?
I was referring to a discussion on ghc-devs, see
https://mail.haskell.org/pipermail/ghc-devs/2016-February/011268.html
and mixed up addresses when replying.
I'm glad you did, because this is the first I've heard of it!
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Marcin Mrotek
2016-02-05 20:54:14 UTC
Permalink
Post by Kyle Hanson
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
2016-02-05 20:58:35 UTC
Permalink
Post by Kyle Hanson
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

http://neilmitchell.blogspot.com/2014/10/why-traversablefoldable-should-not-be.html

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.
Mike Izbicki
2016-02-05 23:21:00 UTC
Permalink
Post by Richard Eisenberg
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.
Imants Cekusins
2016-02-05 23:40:31 UTC
Permalink
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.
Edward Kmett
2016-02-06 17:09:22 UTC
Permalink
Post by Richard Eisenberg
Post by Richard Eisenberg
We're in a bit of a bind in all this. We really need the fancy type for
($)
Post by Richard Eisenberg
so that it can be used in all situations where it is used currently. The
old
Post by Richard Eisenberg
type for ($) was just a plain old lie. Now, at least, we're not lying.
So,
Post by Richard Eisenberg
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
Post by Richard Eisenberg
anyone is advocating for (3) exactly, but it's hard to have (2) and not
make
Post by Richard Eisenberg
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
Post by Richard Eisenberg
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
Post by Richard Eisenberg
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.
_______________________________________________
ghc-devs mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/ghc-devs
Ben Gamari
2016-02-06 12:27:00 UTC
Permalink
Post by Richard Eisenberg
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
Ben Gamari
2016-02-06 13:30:03 UTC
Permalink
Post by Ben Gamari
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'?
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
Richard Eisenberg
2016-02-07 15:15:49 UTC
Permalink
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
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
2016-02-07 15:24:10 UTC
Permalink
Post by Richard Eisenberg
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 $
Marcin Mrotek
2016-02-06 12:31:14 UTC
Permalink
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
Imants Cekusins
2016-02-06 12:40:06 UTC
Permalink
out of curiosity, what are
* and #?

I tried to search but did not find.

Thank you
Imants Cekusins
2016-02-06 12:48:23 UTC
Permalink
actually "#" worked on Hoogle:

https://wiki.haskell.org/Keywords#.23
Takenobu Tani
2016-02-06 12:59:01 UTC
Permalink
Hi,

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

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


Are these also useful?

[1] https://wiki.haskell.org/Kind
[2] https://ghc.haskell.org/trac/ghc/wiki/Commentary/Compiler/TypeType
[3] https://ghc.haskell.org/trac/ghc/wiki/UnliftedDataTypes
[4]
https://takenobu-hs.github.io/downloads/haskell_lazy_evaluation.pdf#page=192

Regards,
Takenobu
Post by Imants Cekusins
https://wiki.haskell.org/Keywords#.23
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Imants Cekusins
2016-02-06 13:15:34 UTC
Permalink
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?
Takenobu Tani
2016-02-06 14:13:11 UTC
Permalink
Hi Imants and cafe,
Post by Imants Cekusins
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

http://learnyouahaskell.com/making-our-own-types-and-typeclasses#kinds-and-some-type-foo

[2] Haskell 2010 Language Report, 4.1.1 Kinds
https://www.haskell.org/definition/haskell2010.pdf

[3]
http://takenobu-hs.github.io/downloads/type_introduction_illustrated.pdf#page=69

Regards,
Takenobu
Post by Imants Cekusins
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?
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Edward Kmett
2016-02-06 17:12:47 UTC
Permalink
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.
Post by Imants Cekusins
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?
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Manuel Gómez
2016-02-06 18:14:56 UTC
Permalink
Post by Edward Kmett
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:

```
Post by Edward Kmett
: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:

```
Post by Edward Kmett
: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.
Post by Edward Kmett
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
2016-02-06 19:33:53 UTC
Permalink
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
Post by Edward Kmett
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
Post by Edward Kmett
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
```
Post by Edward Kmett
: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,
```
Post by Edward Kmett
: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.
Post by Edward Kmett
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
Post by Edward Kmett
you need to be able to talk about these concepts at all with any rigor,
and
Post by Edward Kmett
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.
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Marcin Mrotek
2016-02-07 00:55:25 UTC
Permalink
Post by David Turner
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
Geraldus
2016-02-07 11:08:41 UTC
Permalink
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
2016-02-07 11:46:16 UTC
Permalink
Post by Geraldus
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
($) :: (a -> b) -> a -> b
forall r :: RuntimeRep
a :: *
b :: TYPE r
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
***@gmail.com
http://IvanMiljenovic.wordpress.com
John Wiegley
2016-02-06 21:04:34 UTC
Permalink
Prelude> :t ($)
Post by Kyle Hanson
($) :: (a -> b) -> a -> b
Prelude> :t ($)
Post by Kyle Hanson
($)
:: 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
Theodore Lief Gannon
2016-02-05 23:14:34 UTC
Permalink
(Sorry you got this twice, Kyle!)
Post by Kyle Hanson
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.
Post by Kyle Hanson
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
Post by Kyle Hanson
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
Post by Richard Eisenberg
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.
($) :: 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
---
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
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.
From *Takenobu Tani*
Prelude> :t foldr
foldr :: (a -> b -> b) -> b -> [a] -> b
Prelude> :t ($)
($) :: (a -> b) -> a -> b
* type variable (polymorphism)
Prelude> :t foldr
foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b
Prelude> :t ($)
($)
:: forall (w :: GHC.Types.Levity) a (b :: TYPE w).
(a -> b) -> a -> b
With this change it looks like I will no longer be able to keep `$` in my
toolbox since telling a beginner its "magic" goes against what I believe
Haskell is good at, being well defined and easy to understand (Not well
defined in terms of Types but well defined in terms of ability to precisely
and concisely explain and define whats going on).
It looks like where the discussion is going is to have these types show
by default but eventually have an Alternative prelude for beginners.
From *Richard Eisenberg:*
- It's interesting that the solution to the two problems Takenobu pulls out below (but others have hinted at in this thread) is by having an alternate Prelude for beginners. I believe that having an alternate beginners' Prelude is becoming essential. I know I'm not the first one to suggest this, but a great many issues that teachers of Haskell have raised with me and posts on this and other lists would be solved by an alternate Prelude for beginners.
I don't like the idea of fragmenting Haskell into "beginners" and
"advanced" versions. Its hard enough to get people to believe Haskell is
easy. If they see that they aren't using the "real" prelude, Haskell will
still be this magic black box that is too abstract and difficult to
understand. If they have to use a "dumbed down" version of Haskell to
learn, its not as compelling.
There is something powerful about using the same idiomatic tools as the
"big boys" and have the tools still be able to be easy to understand.... by
default. Adding complexity to the default Haskell runs the risk of further
alienating newcomers to the language who have a misconception that its too
hard.
Admittedly, I am not well informed of the state of GHC 8.0 development
and haven't had time to fully look into the situation. I am very interested
to see where this conversation and the default complexity of Haskell goes.
--
Kyle
On Fri, Feb 5, 2016 at 8:26 AM, Tom Ellis <
Post by Johannes Waldmann
Post by Colin Adams
What's changed?
I was referring to a discussion on ghc-devs, see
https://mail.haskell.org/pipermail/ghc-devs/2016-February/011268.html
and mixed up addresses when replying.
I'm glad you did, because this is the first I've heard of it!
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
_______________________________________________
Haskell-Cafe mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Artyom
2016-02-05 23:22:06 UTC
Permalink
Why not just make GHCi output a comment whenever the type involves levity?

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

​
Mihai Maruseac
2016-02-06 00:06:25 UTC
Permalink
Post by Artyom
Why not just make GHCi output a comment whenever the type involves levity?
Post by Kyle Hanson
:t ($)
--
-- ($) :: forall (w :: GHC.Types.Levity) a (b :: TYPE w). (a -> b) -> a
-> b
--
-- For the absolute majority of purposes the simpler type is correct.
-- See GHC Guide chapter X point Y to learn more about this.
($) :: (a -> b) -> a -> b
Wouldn't this look like a scary error to some users? Though, users can
get accustomed to this and this solution seems to be the best of both
worlds.
--
Mihai Maruseac (MM)
"If you can't solve a problem, then there's an easier problem you can
solve: find it." -- George Polya
Artyom
2016-02-06 00:15:56 UTC
Permalink
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 |
Post by Mihai Maruseac
Post by Artyom
Why not just make GHCi output a comment whenever the type involves levity?
Post by Kyle Hanson
:t ($)
--
-- ($) :: forall (w :: GHC.Types.Levity) a (b :: TYPE w). (a -> b) -> a
-> b
--
-- For the absolute majority of purposes the simpler type is correct.
-- See GHC Guide chapter X point Y to learn more about this.
($) :: (a -> b) -> a -> b
Wouldn't this look like a scary error to some users? Though, users can
get accustomed to this and this solution seems to be the best of both
worlds.
​
Richard A. O'Keefe
2016-02-09 00:32:11 UTC
Permalink
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?
Marcin Mrotek
2016-02-09 09:15:47 UTC
Permalink
Post by Richard A. O'Keefe
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?
But it is already rather simple conceptually, it's just that Haskell's kind
signature syntax makes it look hairy. I mean, if you squint enough, this:

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

could be written like:

($) :: RuntimeRep r => (a :: TYPE Lifted) (b :: TYPE r). (a -> b) -> a -> b

or like I've previously suggested (if * is the default kind and wildcards
are allowed in kinds):

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

I mean, perhaps the syntax could be improved, but the information that:
* a is lifted and boxed
* b can have any kind that has a runtime representation (so unlifted and
unboxed types are ok, but data kinds are not)
has to go somewhere if the ability to write levity-polymorphic functions is
to be given to the user, rather than only available as a one-off hack in
the compiler.

Perhaps going fully dependently typed and giving up on the distinction
between values and types would change that; I'm not sure how for example
Idris would handle levity polymorphism.

Best regards,
Marcin Mrotek
Loading...