[Haskell-cafe] Overloading

Richard A. O'Keefe ok at cs.otago.ac.nz
Mon Mar 11 02:24:05 CET 2013


On 11/03/2013, at 12:10 AM, Peter Caspers wrote:

> thanks, this was the core of my question. So by example, if I define a Date type as
> 
> data Date = Date Int deriving Show
> 
> representing a date by its serial number and want two constructors (conditions are only examples here)
> 
> -- smart constructor with serialNumber
> date serialNumber
>         | serialNumber > 0 = Date serialNumber
>         | otherwise = error ("invalid serialNumber " ++ show serialNumber)
> 
> -- smart constructor with day month year
> date2 day month year
>        | month >= 1 && month <=12 = undefined
>        | otherwise = error ("invalid month " ++ show month)
> 
> there is no way of naming both functions date (instead of date2 above, which compiles), right ?

Right.
> I still think the basic reason is that
> 
> date 5
> 
> would then either refer to the first constructor (i.e. representing a date with serial number 5) or a partial application of the second
> constructor (i.e. representing a function taking month and year and returning the date "5th month, year").

I am having real trouble understanding why you think this.
Yes, for an *untyped* language, "date 27" would not know whether
to return a date or a closure.  But Haskell is *not* an untyped
language.  The one-identifier-one-visible-interface rule is about
making a practical type inference algorithm.

I'm also having some trouble understanding why negative serial
numbers would be illegal.  Dates are a Z-torsor; to convert
integers to dates you have to choose an arbitrary origin.
My Dershowitz-and-Reingold-inspired Smalltalk calendar library
lets you use Julian day number (shifted by 0.5), modified Julian
day number, rata die, and ahargana.  I've been thinking of allowing
a fifth origin: COBOL's 0=31-Dec-1600.  "serialNumber" is a bad
name because the origin is arbitrary and the name does not reveal
what the origin is.

You can easily write

date :: Either Int (Int Int Int) -> Date

date (Left days_since_epoch) = Date days_since_epoch
date (Right (year,month,day))
  | 1 <= month && month <= 12 && 1 <= day &&
    day <= days_in_month year month
    = …
  | otherwise = error ("bad date")

Or even set up your own interface type:

import System.Time  -- to get Month; pity Data.Time doesn't offer that.

data Date_Presentation
   = Julian_Day_Number Int
   | Modified_Julian_Day_Number Int
   | Rata_Die Int
   | Ahargana Int
   | Days_Since_COBOL_Epoch Int
   | Gregorian Int Month Int
   | Julian Int Month Int
   | Revised_Julian Int Month Int -- more accurate than Gregorian 

date :: Date_Presentation -> Date

date (Julian_Day_Number j) = …
…
date (Revised_Julian y m d) = …

You will notice that this list offers 5 date presentations that
use a single number and three that use two numbers and a month name.
Overloading is no help with that!

> If this is the case, what would be the natural Haskell way of organizing the smart constructors ? Just number them as above ? Or naming them
> dateFromSerialNumber, dateFromDayMonthYear ?

As noted above, there is NO unique "serial number" for a date
and NO unique day/month/year representation either.

Smalltalk-80 introduced baStudlyCaps namesThatIsNamesWithInternalCapitals
because it was implemented on a machine that used the ASCII 63
left arrow and up arrow instead of the ASCII 67 underscore and caret.
So it used the codepoint we associate with underscore for the assignment
symbol.  In C and C++ and SML and Haskell, we are allowed to use
underscores.  ThereisnoneedtorunyourwordstogetherOrUseInternalCaps.
Nobody_will_shoot_you_for_writing_readably.

You should probably take advantage of the module name and call your
functions
Date.from_julian_day_number :: Int -> Date
Date.from_gregorian         :: Int -> Month -> Int -> Date

> Or would you do it differently from the start ?

One question is support for different calendars.

I would probably have a My_Date module that just offers
julian day number, modified julian day number, ahagarna,
rata die, and maybe a couple of other epochs.  I would
create nested modules My_Date.Gregorian, My_Date.Julian,
My_Date.Revised_Julian, My_Date.Mayan, and so on, so that
a new calendar could be supported by just plugging in a
new module, not by changing anything.

For something without so many alternatives, I might make a different choice.





More information about the Haskell-Cafe mailing list