Local definitions in the class instances

I think it would be convenient to allow adding variables and functions, which are not members of the class, to a class instance so that they are visible only in the instance scope. It will help if the same functions are used by several class functions. Example: When implementing Num class for my datatype, I found that I routinely do unwrapping in each operator definition. I extracted it into functions, but as they are used only in instance definition, I want to put them there and restrict them to that scope. It would be neater than leaving them in the global scope or copypasting into each operator.
newtype Wrapped = Wrapped Integer deriving (Show, Eq)
instance Num Wrapped where (+) = lift2 (+) (-) = lift2 (-) (*) = lift2 (*) abs = lift abs signum = lift signum fromInteger = Wrapped lift2 f (Wrapped a) (Wrapped b) = Wrapped (f a b) lift f (Wrapped a) = Wrapped (f a)
The extension implementation should be very simple. -- Regards, Boris

Hello,
One issue I can see with such a change is that now it is not obvious which
declarations define methods in the instance, and which are just helper
functions. For example, currently, if I mistype the name of a method in a
class (which happens often), the program is rejected because there is no
such method in the class. With this change, the program would be accepted
because the method would be "undefined" and the mistyped implementation
would be considered as just another local declaration.
I have also encountered the underlying problem that you describe---wanting
more control over the scoping of declarations. Perhaps we should extend
Haskell with something like ML's "local" declarations: local D1 in D2. Such
a declaration defines what D2 defines, but the implementations in D2 may use
the names defined in D1 (i.e., it is like a "let" which scopes over
declarations rather then expressions). This would help with your problem:
instance Num Wrapped where
local
lift2 f (Wrapped a) (Wrapped b) = Wrapped (f a b)
lift f (Wrapped a) = Wrapped (f a)
in
(+) = lift2 (+)
(-) = lift2 (-)
(*) = lift2 (*)
abs = lift abs
signum = lift signum
It would also be useful in other situations. For example, currently if we
have a module which exports most of its functions but not all, we have to
write a long export list. This could be avoided with a local declaration:
module M where
local
not exported functions
in
exported functions
Of course, one could also scope the private functions more precisely. I am
not sure what would be good syntax for a concrete proposal but I think that
this is a nice construct to have.
-Iavor
On Thu, Jan 27, 2011 at 3:07 AM, Boris Lykah
I think it would be convenient to allow adding variables and functions, which are not members of the class, to a class instance so that they are visible only in the instance scope. It will help if the same functions are used by several class functions.
Example: When implementing Num class for my datatype, I found that I routinely do unwrapping in each operator definition. I extracted it into functions, but as they are used only in instance definition, I want to put them there and restrict them to that scope. It would be neater than leaving them in the global scope or copypasting into each operator.
newtype Wrapped = Wrapped Integer deriving (Show, Eq)
instance Num Wrapped where (+) = lift2 (+) (-) = lift2 (-) (*) = lift2 (*) abs = lift abs signum = lift signum fromInteger = Wrapped lift2 f (Wrapped a) (Wrapped b) = Wrapped (f a b) lift f (Wrapped a) = Wrapped (f a)
The extension implementation should be very simple.
-- Regards, Boris
_______________________________________________ Haskell-prime mailing list Haskell-prime@haskell.org http://www.haskell.org/mailman/listinfo/haskell-prime

On 27 January 2011 17:11, Iavor Diatchki
instance Num Wrapped where local lift2 f (Wrapped a) (Wrapped b) = Wrapped (f a b) lift f (Wrapped a) = Wrapped (f a) in (+) = lift2 (+) (-) = lift2 (-) (*) = lift2 (*) abs = lift abs signum = lift signum
Local declarations at module scope can be emulated using pattern bindings: """ (foo, bar) = (foo, bar) where foo = .. bar = .. private = ... """ If instance declarations supported pattern bindings you could get the same effect for your instances too. This would be a minimal change that avoided introducing any extra syntax. Cheers, Max

On Tue, Feb 1, 2011 at 9:52 AM, Max Bolingbroke
Local declarations at module scope can be emulated using pattern bindings:
""" (foo, bar) = (foo, bar) where foo = .. bar = .. private = ... """
If instance declarations supported pattern bindings you could get the same effect for your instances too. This would be a minimal change that avoided introducing any extra syntax.
This is kind of ugly, I think, and there are proposals to make pattern bindings monomorphic which would make this sort of thing no longer possible in general. I think I would be in favour of a declaration analogue to let.

On Tue, Feb 1, 2011 at 9:23 PM, Ben Millwood
On Tue, Feb 1, 2011 at 9:52 AM, Max Bolingbroke
wrote: Local declarations at module scope can be emulated using pattern
bindings:
""" (foo, bar) = (foo, bar) where foo = .. bar = .. private = ... """
If instance declarations supported pattern bindings you could get the same effect for your instances too. This would be a minimal change that avoided introducing any extra syntax.
It's a nice trick! Although it does look strange, it may be reasonable to allow pattern bindings in instance declarations regardless of the original proposal. Is it correct that, currently, pattern bindings are allowed everywhere but in instance declarations? If so, why not in instance declarations too?
This is kind of ugly, I think, and there are proposals to make pattern bindings monomorphic which would make this sort of thing no longer possible in general.
I think the proposals to make pattern bindings monomorphic only concern pattern bindings without type annotations. Instance methods do have type annotations in the class declaration so even if pattern bindings without type signatures would be monomorphic, instance methods bound using pattern bindings need not be.
I think I would be in favour of a declaration analogue to let.
I agree that using pattern bindings for the original task would work around a missing syntax extension. But at least this workaround may be easily implementable and making it possible seems to fix an inconsistency by making the use of pattern bindings more orthogonal. Sebastian

On 2 February 2011 02:25, Sebastian Fischer
It's a nice trick! Although it does look strange, it may be reasonable to allow pattern bindings in instance declarations regardless of the original proposal. Is it correct that, currently, pattern bindings are allowed everywhere but in instance declarations? If so, why not in instance declarations too?
Unfortunately, they are not allowed. I ran up against this limitation even before this thread. Here is an example: """ {-# LANGUAGE NoMonoPatBinds #-} data I a = I { unI :: a } instance Monad I where (return, (>>=)) = (I, \mx f -> f (unI mx)) main = return () """ And the error: """ /Users/mbolingbroke/Junk/InstancePattern.hs:5:5: Pattern bindings (except simple variables) not allowed in instance declarations (return, >>=) = (I, \ mx f -> f (unI mx)) """ This behaviour does seem to be as per the Haskell 98 spec, but I'm not sure of the motivation behind it.
I think the proposals to make pattern bindings monomorphic only concern pattern bindings without type annotations. Instance methods do have type annotations in the class declaration so even if pattern bindings without type signatures would be monomorphic, instance methods bound using pattern bindings need not be.
This was what I used to think too, but I recently found out (again, unrelated to this thread) that they won't work even with explicit signatures! See for example http://hackage.haskell.org/trac/ghc/ticket/4940 I'm personally not in favour of MonoPatBinds anyway, but it is *particularly* annoying that they don't work even with a signature. Cheers, Max

On 2011-01-27 13:07 +0200, Boris Lykah wrote:
I think it would be convenient to allow adding variables and functions, which are not members of the class, to a class instance so that they are visible only in the instance scope. It will help if the same functions are used by several class functions.
Example: When implementing Num class for my datatype, I found that I routinely do unwrapping in each operator definition. I extracted it into functions, but as they are used only in instance definition, I want to put them there and restrict them to that scope. It would be neater than leaving them in the global scope or copypasting into each operator.
One problem with this proposal is that it hurts modularity, as there is no distinction in the instance declarations between the definitions of "instance-local" functions and those of class methods. This means that you cannot add new methods to an existing class (together with a default implementation in terms of the existing methods) without potentially breaking the existing instances. -- Nick Bowler, Elliptic Technologies (http://www.elliptictech.com/)

Thanks for pointing at the problems with new and default class functions. But if we distinct the local and class functions I see no other problems. Iavor, your local/in suggestion is very interesting. I would like to see local declarations in the Haskell standard some day. However, it affects much more than class instances and needs a lot of discussion. It can conflict with let/in construct. In the example with export I would rather use
module M hiding (not exported functions) where It is more coherent with the import syntax, though local definitions are more generic.
I know GHC internals a little and would like to learn more. When I
finish my diploma paper, I can try to implement some extensions which
the community considers useful.
On Thu, Jan 27, 2011 at 8:02 PM, Nick Bowler
On 2011-01-27 13:07 +0200, Boris Lykah wrote:
I think it would be convenient to allow adding variables and functions, which are not members of the class, to a class instance so that they are visible only in the instance scope. It will help if the same functions are used by several class functions.
Example: When implementing Num class for my datatype, I found that I routinely do unwrapping in each operator definition. I extracted it into functions, but as they are used only in instance definition, I want to put them there and restrict them to that scope. It would be neater than leaving them in the global scope or copypasting into each operator.
One problem with this proposal is that it hurts modularity, as there is no distinction in the instance declarations between the definitions of "instance-local" functions and those of class methods.
This means that you cannot add new methods to an existing class (together with a default implementation in terms of the existing methods) without potentially breaking the existing instances.
-- Nick Bowler, Elliptic Technologies (http://www.elliptictech.com/)
-- Regards, Boris

On Thu, Jan 27, 2011 at 3:07 AM, Boris Lykah
I think it would be convenient to allow adding variables and functions, which are not members of the class, to a class instance so that they are visible only in the instance scope. It will help if the same functions are used by several class functions. ....
newtype Wrapped = Wrapped Integer deriving (Show, Eq)
instance Num Wrapped where (+) = lift2 (+) (-) = lift2 (-) (*) = lift2 (*) abs = lift abs signum = lift signum fromInteger = Wrapped lift2 f (Wrapped a) (Wrapped b) = Wrapped (f a b) lift f (Wrapped a) = Wrapped (f a)
Is this meant to mean anything different than
instance Num Wrapped where (+) = lift2 (+) (-) = lift2 (-) (*) = lift2 (*) abs = lift abs signum = lift signum fromInteger = Wrapped
lift2 f (Wrapped a) (Wrapped b) = Wrapped (f a b) lift f (Wrapped a) = Wrapped (f a)
where lift2 and lift are not exported? I think the following syntax would make more sense.
instance Num Wrapped where (+) = lift2 (+) (-) = lift2 (-) (*) = lift2 (*) abs = lift abs signum = lift signum fromInteger = Wrapped where lift2 f (Wrapped a) (Wrapped b) = Wrapped (f a b) lift f (Wrapped a) = Wrapped (f a)
so 'where' indroduces the local instance scope. John

-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 On 1/30/11 05:54 , John Meacham wrote:
instance Num Wrapped where (+) = lift2 (+) (-) = lift2 (-) (*) = lift2 (*) abs = lift abs signum = lift signum fromInteger = Wrapped where lift2 f (Wrapped a) (Wrapped b) = Wrapped (f a b) lift f (Wrapped a) = Wrapped (f a)
so 'where' indroduces the local instance scope.
The double "where" strikes me as a bit odd. Also, not sure how the parser would deal with it, even given that using the second without the first is entirely pointless; Haskell structures all follow a similar pattern WRT where, and this confounds it in several ways. - -- brandon s. allbery [linux,solaris,freebsd,perl] allbery@kf8nh.com system administrator [openafs,heimdal,too many hats] allbery@ece.cmu.edu electrical and computer engineering, carnegie mellon university KF8NH -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.11 (Darwin) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/ iEYEARECAAYFAk1F8qgACgkQIn7hlCsL25UDmQCg0iyxts0dSvbhqdDosK0WKF/w CxkAnR5uxzTSYTmK4nvypRcIOtpxTDCm =Z8+V -----END PGP SIGNATURE-----
participants (8)
-
Ben Millwood
-
Boris Lykah
-
Brandon S Allbery KF8NH
-
Iavor Diatchki
-
John Meacham
-
Max Bolingbroke
-
Nick Bowler
-
Sebastian Fischer