I have often wished that an instance declaration could have extra bindings, i.e., bindings that are not the class methods. Today you would have to make such bindings global. I dislike making bindings have a larger scope than they have to, since that impedes understanding. It would be a bad idea to allow the extra bindings with no indication that they are extra, since that would make a misspelled method name not be an error. So there should be some syntax that indicates what is extra.
Example:
class C a where
m1 :: a -> a
m2 :: a -> a
instance C Int where
m1 x = f x + 1 -- method binding
m2 x = f x + 2 -- method binding
where
f x = x * x -- extra binding
This is just a suggested syntax, but it seems to make sense.
Is there anyone else who has missed this feature? I’ve implemented in MicroHs. It was trivial.
There are times where I just provide the implementation as a separate function that the end-user can call without going through the indirection of the typeclass constraint. Of course it’s not very practical for classes like Num with lots of operators, for classes with one function it usually provides a nicer interface with we try to keep most domain-related functions monomorphic.
Yes, although I don’t like the idea of making type classes even more of a special thing than they already are. I’m considering only ever defining type classes in the future that have a single method <Class>Impl, like this:
class C a where
cImpl :: CImpl a
data CImpl a = MkCImpl {
m1Impl :: a -> a,
m2Impl :: a -> a
}
instance C Int where
cImpl =
let f x = x * x
in MkCImpl {
m1Impl = f x + 1,
m2Impl = f x + 1
}
m1 :: C a => a -> a
m1 = m1Impl cImpl
m2 :: C a => a -> a
m2 = m2Impl cImpl
This is much more uniform and allows us to use any normal Haskell machinery to implement type classes and instances. For example you can do stuff like
fromF :: (a -> a) -> CImpl a
fromF f = MkCImpl { m1Impl = f x + 1, m2Impl = f x + 2 }
instance C Integer where
cImpl = fromF (\x -> x * x * x)
Yes, although I don’t like the idea of making type classes even more of a special thing than they already are.
There is another way to look at this that makes type classes less of a special thing. A type class conflates two things: it is both a data type (of dictionaries) and a constraint. The problem is that the language hides the “data type” side of type classes, with instance being an ad hoc syntax for defining dictionaries. If type classes were explicitly identified as data types, then a dictionary could be defined as just a value, and we could just use where clauses as usual.
It doesn’t have to be a special case for instance declarations. We can take a cue from Luca Cardelli’s version of ML from early 80s. Instead if having bindings simply be a list of bindings we could also allow
let
bindingsL
in
bindingsG
Which would make bindingsL and bindingsG visible to each other, but only bindingsGwould be visible outside. Similarely, we should have
bindingsG
where
bindingsL
Example:
let
let sqr x = x*x
in f x = sqr x + 1
g x = sqr x - 1
in. ... use f and g ... -- sqr not visible here
I’ve wished for this feature now and then as well. It would make modules without an export list nicer as well.
module M where. -- only exports f, g
f x = sqr x + 1
g x = sqr x - 1
where
sqr x = x * x
With this my proposed extension to instance would just be a use case.
Given how bloated GHC already is, I think (for everybody’s sanity) the usefulness threshold for proposed features should become higher and higher, especially regarding syntactic extensions. And I don’t think this proposal meets my own subjective threshold.
These extra bindings are a nice convenience, but ultimately they are not substantially different from a toplevel definition that is not exported. Yes, visibility restrictions cannot be more granular than the file level. But that’s a design choice that Haskell made a long time ago, and keeps at least this part of the language simple.
ML has local decls1 in decls2 end to limit the scope of decls1 to decls2. I think such a construct would be a principled solution to limiting scope. In particular, I’d like to also have local datatypes.
However, the problem with local definitions show up when you want to have checked documentation and tests for them. So, in practice, one simply makes .Internal modules that export everything (for testing and documentation) and then some official modules that export the actual API.
I agree. Small syntax extensions can make things much nice for Haskell experts whilst raising a barrier for non-experts. Ă–mer Sinan AÄźacan has written an article I like about this at osa1 - Some arguments against small syntax extensions in GHC
I would make an exception for syntax extensions that remove irrelevant exceptions, unify disparate concepts, or are otherwise an overall simplification. I haven’t looked at the proposal in this thread closely enough to know whether it satisfies that criterion.
On a minor note I’d prefer to not have the extra where keyword there as a separator; in particular it reads better to me to have helper functions on the top (again as with let..in). I’m not sure if a separator is needed for technical reasons (I see no such one now though).
instance C Int where
f x = x * x
m1 x = f x + 1
m2 x = f x + 2
(I guess this might prevent the future GHC proposal from breaking the parser.)
Extra shower thought: let’s generalize the where and let in instances and classes:
let m1 :: a -> a
m2 :: a -> a
in class C a
let f x = x * x
in instance C Int where
m1 x = ...
One thing I’ve thought about in the past is to just allow any normal bindings in the where block of an instance declaration. Then you can bundle your methods like this:
class C a where
m1 :: a -> a
m2 :: a -> a
data CImpl a = MkCImpl {
m1 :: a -> a,
m2 :: a -> a
}
fromF :: (a -> a) -> CImpl a
fromF f = MkCImpl { m1 = f x + 1, m2 = f x + 2 }
instance C Integer where
cube x = x * x * x -- cube isn't a method, so it is ignored
MkCImpl {..} = fromF cube -- defines the m1 and m2 methods
This is a simplification in some sense: it unifies the behaviour of instance ... where ... with module ... where ... and ... = ... where ....