Structural typing of records in Haskell?

Are there statically typed languages that treat records with structural typing, either imperative or functional? Why should records not be structurally typed in Haskell? From what I understand, in the below foo cannot take a Rec2 even though Rec1 and Rec2 are essentially the same. data Rec1 = Rec1 { a :: Int, b :: Bool} data Rec2 = Rec2 { a :: Int, b :: Bool} foo :: Rec1 -> Bool Rec1 and Rec2 could be in totally different code libraries. I've read that preventing Rec2 being used in foo is good for the type safety in that Rec1 and Rec2 are likely intended to have semantically different meanings and allowing interchangeability breaks this. But then why is map structurally typed. map takes an argument of type a -> b and suppose some other higher order function bar also takes an argument of type a -> b. Should map instead have the below type which prevents a function of type a -> b semantically intended for bar from being accidentally used in map. newtype Mapper a b = Mapper { fn :: a -> b } map :: Mapper a b -> [a] -> [b] map _ [] = [] map f (x:xs) = (fn f) x : map f xs If there is a mechanism that prevents something of type Rec2 from accidentally being used in foo, then why shouldn't there be something analogous that prevents something of type a -> b (meant for bar) from accidentally being used in map?

Hi Cary,
Have you looked at
http://www.haskell.org/haskellwiki/Extensible_record and
https://ghc.haskell.org/trac/ghc/wiki/Records ? The named-fields stuff
in haskell 2010 is bad because none of the proposals/implementations
really stand out as the right choice.
You still have a choice whether you want to use newtypes to help you
keep things straight. Maybe your `newtype Mapper` could help you catch
some bugs. Or maybe it just makes some busywork for you, where you
just add some noise (`fn` and `Mapper`) whenever the types don't match
up.
Also consider that the type of "map" doesn't have to be changed to
catch mistakes of using the wrong function: if the concrete types that
end up replacing the `a` and `b` in your actual program are not all
String, the typechecker may still be able to point out a place where
you've been inconsistent (say by using the wrong function).
--
Adam
On Sun, Jan 12, 2014 at 8:00 PM, Cary Cherng
Are there statically typed languages that treat records with structural typing, either imperative or functional?
Why should records not be structurally typed in Haskell? From what I understand, in the below foo cannot take a Rec2 even though Rec1 and Rec2 are essentially the same.
data Rec1 = Rec1 { a :: Int, b :: Bool} data Rec2 = Rec2 { a :: Int, b :: Bool} foo :: Rec1 -> Bool
Rec1 and Rec2 could be in totally different code libraries. I've read that preventing Rec2 being used in foo is good for the type safety in that Rec1 and Rec2 are likely intended to have semantically different meanings and allowing interchangeability breaks this.
But then why is map structurally typed. map takes an argument of type a -> b and suppose some other higher order function bar also takes an argument of type a -> b. Should map instead have the below type which prevents a function of type a -> b semantically intended for bar from being accidentally used in map.
newtype Mapper a b = Mapper { fn :: a -> b } map :: Mapper a b -> [a] -> [b] map _ [] = [] map f (x:xs) = (fn f) x : map f xs
If there is a mechanism that prevents something of type Rec2 from accidentally being used in foo, then why shouldn't there be something analogous that prevents something of type a -> b (meant for bar) from accidentally being used in map? _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

On Sun, Jan 12, 2014 at 5:00 PM, Cary Cherng
Are there statically typed languages that treat records with structural typing, either imperative or functional?
OCaml uses structural typing for objects, and it's statically typed.
Why should records not be structurally typed in Haskell? From what I understand, in the below foo cannot take a Rec2 even though Rec1 and Rec2 are essentially the same.
data Rec1 = Rec1 { a :: Int, b :: Bool} data Rec2 = Rec2 { a :: Int, b :: Bool} foo :: Rec1 -> Bool
Rec1 and Rec2 could be in totally different code libraries. I've read that preventing Rec2 being used in foo is good for the type safety in that Rec1 and Rec2 are likely intended to have semantically different meanings and allowing interchangeability breaks this.
But then why is map structurally typed. map takes an argument of type a -> b and suppose some other higher order function bar also takes an argument of type a -> b. Should map instead have the below type which prevents a function of type a -> b semantically intended for bar from being accidentally used in map.
newtype Mapper a b = Mapper { fn :: a -> b } map :: Mapper a b -> [a] -> [b] map _ [] = [] map f (x:xs) = (fn f) x : map f xs
If there is a mechanism that prevents something of type Rec2 from accidentally being used in foo, then why shouldn't there be something analogous that prevents something of type a -> b (meant for bar) from accidentally being used in map?
Because it's not possible to break anything by passing a total function to map. Data structures can have internal invariants that functions meant for structurally identical values will break. For example: -- a natural number
data Nat = Nat { unNat :: Int }
if we used structural typing, then ( 1-2 :: Nat ) would work, violating an invariant that our custom API would preserve. However, breaking code like this simply isn't possible with map. For whatever 'a' type you're mapping over, a total function (a->b) will handle it properly. Or perhaps another way to think about it: map *cannot* care about the types of the values it's operating over. It's the function's responsibility to handle the input type appropriately, for whichever input it claims to take. So long as the function is actually a function, map will do the right thing.

In a sense, Haskell does have structurally typed records - we call them
tuples.
type Rec1 = (Int, Bool)
type Rec2 = (Int, Bool)
Now you can pass either to foo :: Rec1 -> Bool
On Sun, Jan 12, 2014 at 5:00 PM, Cary Cherng
Are there statically typed languages that treat records with structural typing, either imperative or functional?
Why should records not be structurally typed in Haskell? From what I understand, in the below foo cannot take a Rec2 even though Rec1 and Rec2 are essentially the same.
data Rec1 = Rec1 { a :: Int, b :: Bool} data Rec2 = Rec2 { a :: Int, b :: Bool} foo :: Rec1 -> Bool
Rec1 and Rec2 could be in totally different code libraries. I've read that preventing Rec2 being used in foo is good for the type safety in that Rec1 and Rec2 are likely intended to have semantically different meanings and allowing interchangeability breaks this.
But then why is map structurally typed. map takes an argument of type a -> b and suppose some other higher order function bar also takes an argument of type a -> b. Should map instead have the below type which prevents a function of type a -> b semantically intended for bar from being accidentally used in map.
newtype Mapper a b = Mapper { fn :: a -> b } map :: Mapper a b -> [a] -> [b] map _ [] = [] map f (x:xs) = (fn f) x : map f xs
If there is a mechanism that prevents something of type Rec2 from accidentally being used in foo, then why shouldn't there be something analogous that prevents something of type a -> b (meant for bar) from accidentally being used in map? _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
participants (4)
-
adam vogt
-
Cary Cherng
-
David Thomas
-
John Lato