Data.Fixed.Fixed constructor

I'm currently working on updating the Data.Fixed module in base. I am adding Typeable and Data instances (automatically derived), and several new HasResolution types (including one for 10^-2, monetary currencies being an obvious Fixed use). The Fixed type is simply a newtype of Integer. Should I expose its MkFixed constructor? Against: * MkFixed is not used in numerical calculation, and isn't itself a conversion function from Integer (obviously). * Ratio doesn't expose its constructor. For: * It's possible to recreate it anyway. * There's a Data instance, so morally the constructor is exposed. -- Ashley Yakeley

Ashley Yakeley wrote:
I'm currently working on updating the Data.Fixed module in base. I am adding Typeable and Data instances (automatically derived), and several new HasResolution types (including one for 10^-2, monetary currencies being an obvious Fixed use).
Is an automatic Data instance wise? Morally speaking does a Data.Fixed really "include" a Integer? Compare the question: should the automatic Data instance for Complex a be used, since this exposes the fact that Complex is stored using two components in real/imaginary components? I think that abstract types like Complex and Fixed should be treated as atoms by Data rather than being traversable, but I'm not sure. Jules

On Thu, 2009-07-23 at 14:07 +0100, Jules Bean wrote:
Ashley Yakeley wrote:
I'm currently working on updating the Data.Fixed module in base. I am adding Typeable and Data instances (automatically derived), and several new HasResolution types (including one for 10^-2, monetary currencies being an obvious Fixed use).
Is an automatic Data instance wise?
Morally speaking does a Data.Fixed really "include" a Integer?
Compare the question: should the automatic Data instance for Complex a be used, since this exposes the fact that Complex is stored using two components in real/imaginary components?
I think that abstract types like Complex and Fixed should be treated as atoms by Data rather than being traversable, but I'm not sure.
Could you give me an example of a Data instance for either Complex or Fixed that you approve of? Data instances unavoidably expose concrete structure. That may be the actual internal structure of the type, or it may be some constructed concrete structure, but it must be something. -- Ashley Yakeley

On Thu, Jul 23, 2009 at 3:29 PM, Ashley Yakeley
On Thu, 2009-07-23 at 14:07 +0100, Jules Bean wrote:
Ashley Yakeley wrote:
I'm currently working on updating the Data.Fixed module in base. I am adding Typeable and Data instances (automatically derived), and several new HasResolution types (including one for 10^-2, monetary currencies being an obvious Fixed use).
Is an automatic Data instance wise?
Morally speaking does a Data.Fixed really "include" a Integer?
Compare the question: should the automatic Data instance for Complex a be used, since this exposes the fact that Complex is stored using two components in real/imaginary components?
I think that abstract types like Complex and Fixed should be treated as atoms by Data rather than being traversable, but I'm not sure.
Could you give me an example of a Data instance for either Complex or Fixed that you approve of?
Data instances unavoidably expose concrete structure. That may be the actual internal structure of the type, or it may be some constructed concrete structure, but it must be something.
For Fixed, perhaps something along the lines of the Double instance?
doubleType :: DataType
doubleType = mkFloatType "Prelude.Double"
instance Data Double where
toConstr = mkFloatConstr floatType -- that should probably be "doubleType"
gunfold _ z c = case constrRep c of
(FloatConstr x) -> z x
_ -> error "gunfold"
dataTypeOf _ = doubleType
You'd just need to insert some appropriate conversions between Double and Fixed.
Alternatively, you could make Fixed non-representable like Ptr,
instance Typeable a => Data (Ptr a) where
toConstr _ = error "toConstr"
gunfold _ _ = error "gunfold"
dataTypeOf _ = mkNorepType "GHC.Ptr.Ptr"
Note that both these instances use the default definition for gfoldl,
which does not attempt to descend into the structure of the data. I
think this is appropriate, although whoever wrote the instance for
Ratio appears to disagree with me.
--
Dave Menendez

On Thu, Jul 23, 2009 at 10:06 PM, David Menendez
On Thu, Jul 23, 2009 at 3:29 PM, Ashley Yakeley
wrote: On Thu, 2009-07-23 at 14:07 +0100, Jules Bean wrote:
Ashley Yakeley wrote:
I'm currently working on updating the Data.Fixed module in base. I am adding Typeable and Data instances (automatically derived), and several new HasResolution types (including one for 10^-2, monetary currencies being an obvious Fixed use).
Is an automatic Data instance wise?
Morally speaking does a Data.Fixed really "include" a Integer?
Compare the question: should the automatic Data instance for Complex a be used, since this exposes the fact that Complex is stored using two components in real/imaginary components?
I think that abstract types like Complex and Fixed should be treated as atoms by Data rather than being traversable, but I'm not sure.
Could you give me an example of a Data instance for either Complex or Fixed that you approve of?
Data instances unavoidably expose concrete structure. That may be the actual internal structure of the type, or it may be some constructed concrete structure, but it must be something.
For Fixed, perhaps something along the lines of the Double instance?
doubleType :: DataType doubleType = mkFloatType "Prelude.Double"
instance Data Double where toConstr = mkFloatConstr floatType -- that should probably be "doubleType" gunfold _ z c = case constrRep c of (FloatConstr x) -> z x _ -> error "gunfold" dataTypeOf _ = doubleType
You'd just need to insert some appropriate conversions between Double and Fixed.
Alternatively, you could make Fixed non-representable like Ptr,
instance Typeable a => Data (Ptr a) where toConstr _ = error "toConstr" gunfold _ _ = error "gunfold" dataTypeOf _ = mkNorepType "GHC.Ptr.Ptr"
Note that both these instances use the default definition for gfoldl, which does not attempt to descend into the structure of the data. I think this is appropriate, although whoever wrote the instance for Ratio appears to disagree with me.
I just checked the second SYB paper, http://www.cs.vu.nl/boilerplate/gmap2.pdf, and in section 5.2 it discusses primitive types. Based on that, I think StringRep is the most correct solution. Here's a quick sketch that seems to work:
import Data.Typeable import Data.Data import Data.Fixed
instance (HasResolution a) => Typeable (Fixed a) where -- omitted
fixedType :: DataType fixedType = mkStringType "Data.Fixed.Fixed"
instance (HasResolution a) => Data (Fixed a) where toConstr x = mkStringConstr fixedType (show x) dataTypeOf _ = fixedType
gunfold _ z c = case constrRep c of StringConstr s -> z (readFixed s) _ -> error "gunfold"
readFixed :: HasResolution a => String -> Fixed a readFixed s = realToFrac x where x :: Double x = read s
--
Dave Menendez

David Menendez wrote:
instance Data Double where toConstr = mkFloatConstr floatType -- that should probably be "doubleType" gunfold _ z c = case constrRep c of (FloatConstr x) -> z x _ -> error "gunfold" dataTypeOf _ = doubleType
I could do something like this for Fixed, but deriving Fixed automatically does essentially the same thing.
Alternatively, you could make Fixed non-representable like Ptr,
Why? Fixed morally is data. -- Ashley Yakeley

On Thu, Jul 23, 2009 at 11:21 PM, Ashley Yakeley
David Menendez wrote:
instance Data Double where toConstr = mkFloatConstr floatType -- that should probably be "doubleType" gunfold _ z c = case constrRep c of (FloatConstr x) -> z x _ -> error "gunfold" dataTypeOf _ = doubleType
I could do something like this for Fixed, but deriving Fixed automatically does essentially the same thing.
It depends on how abstract you want Fixed to be. If you use the
derived Data instances, then gshow and gread (from Data.Generics.Text)
expose the implementation.
*Fixed Data.Generics.Text> gshow (4::Micro)
"(MkFixed (4000000))"
*Fixed Data.Generics.Text> gread "(MkFixed (4))" :: [(Micro,String)]
[(0.000004,"")]
*Fixed Data.Generics.Text> gread "(MkFixed (4))" :: [(Pico,String)]
[(0.000000000004,"")]
In contrast, the StringRep version I posted elsewhere works fine with
gshow/gread while still hiding the implementation.
*Main Data.Generics.Text> gshow (4::Micro)
"(4.000000)"
*Main Data.Generics.Text> gread "(4)" :: [(Micro,String)]
[(4.000000,"")]
*Main Data.Generics.Text> gread "(4)" :: [(Pico,String)]
[(4.000000000000,"")]
--
Dave Menendez

On Fri, 2009-07-24 at 00:58 -0400, David Menendez wrote:
It depends on how abstract you want Fixed to be. If you use the derived Data instances, then gshow and gread (from Data.Generics.Text) expose the implementation.
But I'm going to expose the MkFixed constructor anyway. What's lost with a derived instance? -- Ashley Yakeley

On Fri, Jul 24, 2009 at 1:44 AM, Ashley Yakeley
On Fri, 2009-07-24 at 00:58 -0400, David Menendez wrote:
It depends on how abstract you want Fixed to be. If you use the derived Data instances, then gshow and gread (from Data.Generics.Text) expose the implementation.
But I'm going to expose the MkFixed constructor anyway. What's lost with a derived instance?
If the constructor is already exposed, then you don't lose abstraction
with a derived instance.
Out of curiosity, what made you decide to expose the constructor?
--
Dave Menendez

On Fri, 2009-07-24 at 02:03 -0400, David Menendez wrote:
If the constructor is already exposed, then you don't lose abstraction with a derived instance.
Out of curiosity, what made you decide to expose the constructor?
As a general rule, if there is a correct Data instance, then it is always possible to recreate the constructors. Since I am providing a Data instance, I might as well provide the MkFixed constructor. Consider: your Data instance is StringRep-based. My automatically derived one is Integer-based. Both suggest concrete representations, but the Integer one makes much more sense to me. And as it happens, that's the actual concrete representation. -- Ashley Yakeley

David Menendez wrote:
Out of curiosity, what made you decide to expose the constructor?
Ashley Yakeley wrote:
Since I am providing a Data instance, I might as well provide the MkFixed constructor.
So what? Most of the time the Data instance won't be used. Please don't do this, unless you think there might be a real use case. People want to be able to look at the haddocks/export list and see at a glance how to write neat, bullet-proof code. If someone uses the Data instance, they should be aware of the risks. But if you expose the constructor, it will cause average users of this module to do the wrong thing sometimes. Thanks, Yitz

On Sun, 2009-07-26 at 14:54 +0300, Yitzchak Gale wrote:
People want to be able to look at the haddocks/export list and see at a glance how to write neat, bullet-proof code. If someone uses the Data instance, they should be aware of the risks. But if you expose the constructor, it will cause average users of this module to do the wrong thing sometimes.
I'm not really seeing the problem, here. Ratio exposes its constructor, and Fixed is much like Rational with a fixed denominator. -- Ashley

Ashley Yakeley wrote:
On Fri, 2009-07-24 at 00:58 -0400, David Menendez wrote:
It depends on how abstract you want Fixed to be. If you use the derived Data instances, then gshow and gread (from Data.Generics.Text) expose the implementation.
But I'm going to expose the MkFixed constructor anyway. What's lost with a derived instance?
What's lost is that if I have the user-defined type: data Foo = Foo Integer Micro and I use, for example "everywhere" on it, then my everywhere functionn gets applied to the secret Integer subterm inside Fixed. For example: everywhere (mkT ((+1) :: Integer -> Integer)) (I hope I got this right, I'm very rusty on the details of generics) is intended to add one to all Integer subterms - and only Integer subterms - no matter how deep they are. When this actually alters the integer inside my Micro, I'm going to be pretty surprised. Or maybe I'm not that surprised, but the point is, it exposes the internals of Fixed. It's an abstraction hole. Jules

On Fri, 2009-07-24 at 12:13 +0100, Jules Bean wrote:
When this actually alters the integer inside my Micro, I'm going to be pretty surprised.
Or maybe I'm not that surprised, but the point is, it exposes the internals of Fixed. It's an abstraction hole.
The whole point of SYB is to break abstraction. A "subterm" is a rather vague concept, and it makes a rather arbitrary distinction between "loosely holding" the contained value (will be touched by 'everywhere') and "tightly using" it (won't be touched). Does Complex hold or use its real and imaginary parts? Does Ratio hold or use its numerator and denominator? In both cases, the whole means something more than the aggregate of its parts, but this is typically the case with data-types. If you want a data-type that means nothing more than "aggregate", us (,) tuples. Generally I recommend KYB if you care about abstraction, because YB is actually meaningful code that should not be Sd. -- Ashley Yakeley

Ashley Yakeley wrote:
On Fri, 2009-07-24 at 12:13 +0100, Jules Bean wrote:
When this actually alters the integer inside my Micro, I'm going to be pretty surprised.
Or maybe I'm not that surprised, but the point is, it exposes the internals of Fixed. It's an abstraction hole.
The whole point of SYB is to break abstraction. A "subterm" is a rather vague concept, and it makes a rather arbitrary distinction between "loosely holding" the contained value (will be touched by 'everywhere') and "tightly using" it (won't be touched).
I disagree with you entirely and completely. I've made my argument and there is nothing more to add. You can ignore it if you wish. The whole point of SYB is not to break abstraction. A Data instance for Data.Fixed which exposes the Integer inside would render it useless for the kind of things I expect people do with SYB. Jules

On Mon, 2009-07-27 at 16:32 +0100, Jules Bean wrote:
The whole point of SYB is not to break abstraction.
A Data instance for Data.Fixed which exposes the Integer inside would render it useless for the kind of things I expect people do with SYB.
Um, well I'm just doing the same thing as Complex and Ratio. They also expose their constructors explicitly and both their Data instances expose their constituent parts. And a Fixed really does contain an Integer, just as Rational contains two. -- Ashley Yakeley
participants (4)
-
Ashley Yakeley
-
David Menendez
-
Jules Bean
-
Yitzchak Gale