Opened 4 years ago

Closed 3 years ago

#10963 closed feature request (fixed)

Beginner-targeted language extension

Reported by: kanetw Owned by:
Priority: normal Milestone: 8.2.1
Component: Compiler Version: 7.10.2
Keywords: Cc:
Operating System: Unknown/Multiple Architecture: Unknown/Multiple
Type of failure: None/Unknown Test Case: ghci/scripts/T10963
Blocked By: Blocking:
Related Tickets: Differential Rev(s): Phab:D2136
Wiki Page: Design/GHCi/Type

Description

FMP significantly complicated type signatures such as length :: Foldable t => t a -> Int, which makes teaching Haskell more difficult.

I propose a language extension -XBeginner which instructs GHC(i) to specialize Foldable and Traversable to [] when possible and in case of an ambiguous instance, default those to [].

Change History (25)

comment:1 Changed 4 years ago by goldfire

I very much like your general idea here, but I have different suggestions as to the particulars. Here is my counter-proposal:

  • Extend -XExtendedDefaultRules to allow defaulting when one of the classes involved is Foldable or Traversable. (See the current rules here.) This will mean, essentially, that there will be two lists of default types, one list at kind * and one at kind * -> *. Both lists should be settable by the user by the default directive. (GHC can figure out which list is being set quite easily.) The default default list for * -> * will be [].
  • Add a new command to GHCi, :binfo (for "beginner info") (please suggest a better name) that is like :info but presents different information. It suppresses in-scope instances, but attempts to specialize a function's type and presents specializations, using heuristics. So when we say :binfo find, we'll see Foldable t => (a -> Bool) -> t a -> Maybe a but also (a -> Bool) -> [a] -> Maybe a.

Would this address your use-case? One advantage to my counter-proposal is it means that you don't actually need students to specify a language extension, because -XExtendedDefaultRules is on by default in GHCi.

comment:2 Changed 4 years ago by kanetw

Yes, that seems like a good idea. That might still leave confusing errors when writing code outside of GHCi, but I think that by the time students are advanced enough to use modules/write main themselves they won't be as scared of type class errors anymore.

comment:3 Changed 4 years ago by goldfire

In a fit of frustration at the thought of teaching Haskell in a year's time without this feature, I've implemented it. Phab patch to be posted shortly. Here's the user's guide description. Bikeshedding and design improvements welcome.

.. ghci-cmd:: :type-def; (expression)
.. ghci-cmd:: :td; (expression)

    Infers and prints the type of (expression), defaulting type variables
    if possible. In this mode, if the inferred type is constrained by
    any interactive class (``Num``, ``Show``, ``Eq``, ``Ord``, ``Foldable``,
    or ``Traversable``), the constrained type variable(s) are defaulted
    according to the rules described under :ghc-flag:`-XExtendedDefaultRules`.
    This mode is quite useful when the inferred type is quite general (such
    as for ``foldr``) and it may be helpful to see a more concrete
    instantiation.

comment:4 Changed 4 years ago by goldfire

Differential Rev(s): Phab:D2136
Status: newpatch

comment:5 Changed 4 years ago by simonpj

I'm anxious about having three forms of :type. That seems jolly confusing to me. It's bad enough having :info as well as :type, but there are good reasons for that. But I'd rather not make matters worse.

Suggestion:

  • Always do defaulting with :type. That means the :type e lines up with evaluating e. (In other words, adopt your proposed behaviour for :type.)
  • You can always use :info if you want the most general type of an identifier.
  • I am very doubtful about the usefulness of :type-spec.

Simon

comment:6 Changed 4 years ago by goldfire

I'm afraid I strongly disagree with the proposal in comment:5.

  • As we've discovered when looking at #11376 (specifically, see comment:31:ticket:11376), :info does not always give the type of a term (e.g. for data constructors, class methods, record selectors, pattern synonyms, and maybe more). It is not a good replacement for :type.
  • If we always default with :type, then, e.g., :type foldr (:) will report [a] -> [a] -> [a]. But it's actually much more useful than that, working over any Foldable. There would be no way to see that foldr (:) specializes the result container to be a list while not constraining the input container.
  • :type-spec is the answer to the issues in #11376. It's a generalization of comment:34:ticket:11376. You might argue that we shouldn't generalize here; your proposal on that ticket was to do :type-spec whenever the user asks for :type of a bare identifier. While I can't argue strongly against that proposal, I find the behavior unintuitive and frustratingly non-compositional.

I do agree that three forms of :type is unfortunate. My original design was to have two forms, :type and :type-info, where :type-info printed out both the output of my current :type-spec and :type-def. (It actually originally printed out :type too, for good measure.) But I realized that a user rarely wants both of these outputs, so they should just be able to ask for what they want.

It's very easy to go back to that other design, and I'm quite happy for new designs to be proposed. In particular, I don't think my current design is all that great. It's just better than Simon's, in my opinion. :)

comment:7 Changed 4 years ago by takenobu

Hi,

Is Alexander Kjeldaas's representation idea useful? [1]

[1] https://mail.haskell.org/pipermail/ghc-devs/2016-February/011398.html

Prelude> :t foldr
foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b
For example:
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr :: (a -> b -> b) -> b -> Maybe a -> b
foldr :: (a -> b -> b) -> b -> Identity a -> b
foldr :: (a -> b -> b) -> b -> (c, a) -> b
and more

comment:8 Changed 4 years ago by simonpj

I think we need feedback from a decent number of users before we can converge on a design. And to get that feedback we need some clearly-articulated choices. Which means we need a wiki-page to describe the choices, with pros and cons.

comment:9 Changed 4 years ago by goldfire

Thanks for linking to that. There may be two questions here:

  1. Do we want 1 specialization or many? I have to say I really like using the default list to prescribe the specializations, but perhaps another criterion (like load order, as originally proposed in the linked-to email) can be used.
  1. Do we want the output to come straight from :type, which would make every use of that very common command much longer?

Re (1): I chose to produce 1 specialization for two reasons:

  1. Suppose we have a type constrained by (Foldable f, Num a) => .... If we start printing out multiple specializations, we get a combinatorial number of them. Where do we cut this off? How are they ordered?
  1. Doing just 1 specialization could use the built-in defaulting mechanism very, very easily. Anything else would be quite a bit more challenging, I think. This isn't a reason to avoid other designs, exactly, but a point I wanted to make here.

And, yes, I agree with comment:8 (written concurrently with this one) that we should seek more input. I'll put together that wiki page and post more broadly later today.

comment:10 in reply to:  7 Changed 4 years ago by kanetw

Replying to takenobu:

Hi,

Is Alexander Kjeldaas's representation idea useful? [1]

[1] https://mail.haskell.org/pipermail/ghc-devs/2016-February/011398.html

Prelude> :t foldr
foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b
For example:
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr :: (a -> b -> b) -> b -> Maybe a -> b
foldr :: (a -> b -> b) -> b -> Identity a -> b
foldr :: (a -> b -> b) -> b -> (c, a) -> b
and more

I like this, but this is also kinda difficult. When I was planning on implementing this I ran into the problem that we have a very rapidly growing number of example specializations, and it's hard to decide which ones should be shown and which ones shouldn't. See also some of the discussion here and here.

I'm not sure whether I'd prefer this to be under :t, :i, or separate. :t right now is nicely copy-pastable and adding more info would impact that. :i can't evaluate expressions so it'd have to be overloaded. Having it separate makes it less obvious for beginners who might be using old course material that doesn't know of :<fancy new command>.

I think having it in :t would be the least worst option assuming people who write stuff that uses GHCi programatically agree.

comment:11 Changed 4 years ago by goldfire

Wiki page created: Design/GHCi/Type. Comment away.

comment:12 Changed 4 years ago by takenobu

Replying to goldfire and kanetw in comment:9 and comment:10:

Thank you for explaining by comments and wiki page.

I recognized that multiple specialization will confuse beginners.

So I like (1D, 2B, 3D) in Richard's wiki page.

  • 1D, 2B :

I think it's better that :type is simply keep for beginners.

  • 3D :

For instance, we adopt :type-verbose or :tv command. The command intuitively represents the relation between instantiation and generalization. It's good for middle users.

ghci> :tv length
length :: forall {t :: * -> *} {a}. Foldable t => t a -> Int

Specialized:
length :: [a] -> Int      -- with default (2B)
length :: Maybe a -> Int  -- with one of any order


My decision criteria:

  • Basic command (:t) is priority for beginners.
  • Additional command (:tv) intuitively provide the relation between instantiation and generalization for middle users.

comment:13 in reply to:  12 Changed 4 years ago by goldfire

Wiki Page: [wiki:Design/GHCi/Type]

comment:14 Changed 4 years ago by niteria

I like the idea in comment:10, but combinatorial explosion makes it hard to implement. I think providing some ability for library writers to annotate the function with common specializations would go a long way. That's what already happens in the lens library, GHC just isn't aware of it.

comment:15 Changed 4 years ago by aavogt

:set +t seems to have been forgotten. I think the defaulted type could be tacked on if you are shown a value, since that value was calculated with a particular instance. For example:

> :set +t
> 1+1
2 -- current output
it :: Num a => a
> :set +t
> 1+1
2 :: Integer -- proposed
it :: Num a => a

This case shouldn't change because nothing was evaluated

> :set +t
> let (x:xs) = [1 .. ]
x :: (Num t, Enum t) => t
xs :: (Num t, Enum t) => [t]

comment:16 in reply to:  7 Changed 4 years ago by Iceland_jack

Replying to takenobu:

Hi,

Is Alexander Kjeldaas's representation idea useful? [1]

[1] https://mail.haskell.org/pipermail/ghc-devs/2016-February/011398.html

Prelude> :t foldr
foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b
For example:
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr :: (a -> b -> b) -> b -> Maybe a -> b
foldr :: (a -> b -> b) -> b -> Identity a -> b
foldr :: (a -> b -> b) -> b -> (c, a) -> b
and more

See #11439

comment:17 Changed 3 years ago by Richard Eisenberg <eir@…>

In 8035d1a/ghc:

Fix #10963 and #11975 by adding new cmds to GHCi.

See the user's guide entry or the Note [TcRnExprMode] in TcRnDriver.

Test cases: ghci/scripts/T{10963,11975}

comment:18 Changed 3 years ago by Richard Eisenberg <eir@…>

In 4ae950fb/ghc:

Release notes for #11975 and #10963

comment:19 Changed 3 years ago by goldfire

Milestone: 8.0.2
Status: patchmerge

I would love to see this merged, so that we don't have to wait until 8.2. But I won't cry (hard) if it doesn't.

comment:20 Changed 3 years ago by takenobu

Thank you for great work!

comment:21 Changed 3 years ago by goldfire

NB: If this doesn't get merged, we'll need to move the release note from 8.0.2 to 8.2.

comment:22 Changed 3 years ago by goldfire

Test Case: ghci/scripts/T10963

comment:23 Changed 3 years ago by bgamari

goldfire, I'd be okay with including this in 8.0.2 given that it certainly makes users lives easier. That being said, the merge isn't exactly straightforward. Would you like to try your hand at cherry-picking it onto ghc-8.0? If not I'll probably punt on it since there seems to be a number of non-trivial conflicts in TcSimplify.

comment:24 Changed 3 years ago by thomie

From WorkingConventions/Releases:

[Minor releases] do not add or remove any features, or include new major versions of libraries. They only fix bugs and performance issues in the previous release on that branch.

I'd prefer we stick to this rule. It minimizes the chance of introducing bugs in the stable branch.

comment:25 Changed 3 years ago by bgamari

Milestone: 8.0.28.2.1
Resolution: fixed
Status: mergeclosed

I'm fine with that. Let's bump this off to 8.2.1.

Note: See TracTickets for help on using tickets.