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
- 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.
_______________________________________________
ghc-devs mailing list
ghc-...@haskell.org
http://mail.haskell.org/cgi-bin/mailman/listinfo/ghc-devs
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.
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
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.
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.
+1 for Christopher's emailRichard, 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!
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
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
maybe :: forall (r :: RuntimeRep) (a :: *) (b :: TYPE r). b -> (a -> b) -> Maybe a -> b
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ł
is it safe to say that "boxed" and
"lifted" are synonyms?
> 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.
Yes! Let’s not forget, of course, that these (or similar) have been
in GHC for many, many years, right in the Prelude:
```
> :i Int Char Float Double IO Integer
data Int = GHC.Types.I# GHC.Prim.Int#
data Char = GHC.Types.C# GHC.Prim.Char#
data Float = GHC.Types.F# GHC.Prim.Float#
data Double = GHC.Types.D# GHC.Prim.Double#
newtype IO a
= GHC.Types.IO (GHC.Prim.State# GHC.Prim.RealWorld
-> (# GHC.Prim.State# GHC.Prim.RealWorld, a #))
data Integer
= integer-gmp-1.0.0.0:GHC.Integer.Type.S# !GHC.Prim.Int#
| integer-gmp-1.0.0.0:GHC.Integer.Type.Jp# {-# UNPACK
#-}integer-gmp-1.0.0.0:GHC.Integer.Type.BigNat
| integer-gmp-1.0.0.0:GHC.Integer.Type.Jn# {-# UNPACK
#-}integer-gmp-1.0.0.0:GHC.Integer.Type.BigNat
```
Stepping outside the Prelude, yet well within beginner territory,
brings even more fun:
```
> :i Map Set
data Map k a
= containers-0.5.6.2:Data.Map.Base.Bin {-# UNPACK
#-}containers-0.5.6.2:Data.Map.Base.Size
!k
a
!(Map k a)
!(Map k a)
| containers-0.5.6.2:Data.Map.Base.Tip
data Set a
= containers-0.5.6.2:Data.Set.Base.Bin {-# UNPACK
#-}containers-0.5.6.2:Data.Set.Base.Size
!a
!(Set a)
!(Set a)
| containers-0.5.6.2:Data.Set.Base.Tip
```
Unboxed types, the UNPACK pragma, references to GHC.Prim (which easily
lead to confusing exploration), unboxed tuples, an unboxed State
monad, RealWorld, bang patterns, unexported constructors,
implementation details for abstract types… all of them available right
from the prompt of the Prelude using the main tool for exploratory
learning that beginners rely on.
I’m not saying this is a good thing and I’m not saying this should be
fixed. I’m not even saying this is comparable to the situation with $
and I’m likewise not saying presenting these concepts to beginners
should be thought of as comparable to presenting levity polymorphism
to beginners. It is nonetheless relevant context to this discussion;
the Prelude has always had concepts unfriendly to beginners readily
available, and Haskell beginner teachers have always had to work
around these issues. Students have always asked about these things.
> But even if you never care about #, Int, Double, etc. are of kind *,
> Functors are of kind * -> *, etc. so to talk about the type of types at all
> you need to be able to talk about these concepts at all with any rigor, and
> to understand why Maybe Maybe isn't a thing.
In my personal teaching experience, it is extremely helpful to discuss
kinds in the first introduction of type constructors, after covering
types with no parameters. This is especially helpful in discussing
how the hierarchy leading to Monad works, and why things like
«instance Functor (Tree Int) where …» don’t make sense and why
«instance Functor Tree where …» must be parametric in the type of the
thing in the tree, which in turn motivates a lot more discussion.
Teaching kinds is teaching Haskell basics. It is not an advanced
topic. It ought to be covered near the very first lessons on Haskell
for absolute beginners.
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.
On 7 Feb 2016, at 2:32 am, Michał Antkiewicz <mant...@gsd.uwaterloo.ca> wrote:Every programmer should understand the difference between boxed and
unboxed values. Period. The fact that Haskell allows for levity
polimorphism is something we should be proud of and leverage in
teaching, not hide it or lie about it.
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
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
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
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).
I have made a ticket #11549 (https://ghc.haskell.org/trac/ghc/ticket/11549) requesting a -fshow-runtime-rep flag (recalling that the name levity will soon be outdated) as described in this thread. I will make sure this gets in for the release of 8.0.
Other points:
- You're quite right that (.) could be generalized. But I'll wait for someone to really want this.
- I don't have a non-contrived example of the use of ($) with unlifted types. It's quite possible that when adding the dirty runST hack, it was observed that an unlifted type would be OK. At that point, the type of ($) didn't need to become so elaborate. And now we're just trying not to change old (but perhaps unrequested) behavior.
- For the record, this debate is entirely unrelated to the runST impredicativity hack. (Except, as noted above, perhaps in history.) That hack remains, basically unchanged.
- On Feb 6, 2016, at 9:55 AM, Roman Cheplyaka <ro...@ro-che.info> wrote:
>
> I would call this a simplification rather than a lie.
This is a very convincing argument.
- Thanks, also, for the voice of support. What I love about the Haskell community is that we can have an impassioned debate full of strong opinions, and it all very rarely devolves into a proper flame war. All the posts I've seen in this thread have been constructive and helpful. Thanks.
Richard
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.
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 is in fact otherwise called "id" ☺
> So my curiosity is this: why do
> we insist on considering ($) to be a function in the language rather
> than being syntax? We have overt syntax for other forms of whitespace,
> namely to deal with blocks and indentation, and we don't worry about
> what their types are, so why not treat ($) similarly?
These other symbols, the block delimiters, are not terms, and so have no types.
1. Delete the unnameable
3. Remove all specialness of it
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.
> On 7 Feb 2016, at 9:50 pm, Joachim Durchholz <j...@durchholz.org> wrote:
> For the Int/Int# concept, the approaches I have seen either ignore the efficiency and let the machine figure out what to do (Smalltalk, Python, pre-Int# Haskell), or they complicate the type system at the expense of polymorphism (Java, Eiffel), or they complicate the type system even more to regain some form of polymorphism (C++, today's Haskell).
Although I haven’t implemented it, I suspect another approach is to just specialise every polymorphic function at its unboxed type arguments. Boxed and unboxed value types would share the same kind. Of course, full specialisation of polymorphic code assumes that code is available in the interface files, but we’ve almost got that already. Dealing with mutual recursion could be a pain, though.
I don’t think specialisation was an option back when unboxed types were originally implemented. I believe GHC’s support for cross module inlining came some time after the unboxed types, if the publication dates of the relative papers are to be a guide.
Ben.