Why doesn't Haskell have structurally typed records?

I heard Haskell describe structural typing. Entries are an exception to this, although I understand. For example, foo cannot be called with something like HRec2 , although HRec and HRec2 only nominally different in their fields.

 data HRec = HRec { x :: Int, y :: Bool } data HRec2 = HRec2 { p :: Int, q :: Bool } foo :: HRec -> Bool 

Is there any explanation for not extending the extension of structural typing to everything, including records?

Are there statically typed languages ​​with structural typing even for records? Maybe I can read some debate about this for all statically typed languages ​​in general?

+7
haskell record structural-typing
source share
4 answers

Haskell has structured types, but not structural typing, and that is unlikely to change. *

Failure to allow nominally different, but structurally similar types, as interchangeable arguments, is called type safety. It's good. Haskell even has a new type declaration to provide types that are only nominally different so you can apply more type safety. Security type is an easy way to catch errors before resolving them at runtime.

In addition to amindfv’s good answer, which includes ad-hoc polymorphism via typeclasses (actually equivalent to a feature of a programmer’s function), there is parametric polymorphism where you allow absolutely any type, so [a] allows you to use any type in your list and BTree a allows you to use any type in your binary tree.

This gives three answers to the question: "Are these types interchangeable?"

  • Not; the programmer did not say that.
  • Equivalent for a specific purpose, because the programmer said that.
  • Don't care - I can do the same with this dataset because it does not use any property of the data itself.

No 4: the compiler overloads the programmer because they had to use a pair of Ints and String, as in this other function.

* I said that Haskell is unlikely to change to structural typing. There is some discussion for introducing some form of expandable entries, but there are no plans to make (Int,(Int,Int)) the same as (Int, Int, Int) or Triple {one::Int, two::Int, three::Int} the same as Triple2 {one2::Int, two2::Int, three2::Int} .

+8
source share

Haskell entries are actually not “less structural” than the rest of the type system. Each type is either fully defined or "definitely vague" (i.e. defined using a class).

To allow both HRec and HRec2 to f , you have several options:

Algebraic types:

Here you define the HRec and HRec2 as part of the HRec type:

 data HRec = HRec { x :: Int, y :: Bool } | HRec2 { p :: Int, q :: Bool } foo :: HRec -> Bool 

(alternately and perhaps more idiomatically :)

 data HRecType = Type1 | Type2 data HRec = HRec { hRecType :: HRecType, x :: Int, y :: Bool } 

Type classes

Here you define foo as capable of accepting any type as input if an instance of typeclass was written for this type:

 data HRec = HRec { x :: Int, y :: Bool } data HRec2 = HRec2 { p :: Int, q :: Bool } class Flexible a where foo :: a -> Bool instance Flexible HRec where foo (HRec a _) = a == 5 -- or whatever instance Flexible HRec2 where foo (HRec2 a _) = a == 5 

Using type classes allows you to go beyond the usual structural input - you can accept types that have the necessary information built into them, even if the types do not look the same outward, for example:

 data Foo = Foo { a :: String, b :: Float } data Bar = Bar { c :: String, d :: Integer } class Thing a where doAThing :: a -> Bool instance Thing Foo where doAThing (Foo xy) = (x == "hi") && (y == 0) instance Thing Bar where doAThing (Bar xy) = (x == "hi") && ((fromInteger y) == 0) 

We can run fromInteger (or any arbitrary function) to get the data we need from what we have!

+9
source share

To answer your last question, Go and Scalas definitely have structural typing. Some people (including me) would call this a “statically dangerous type” because it implicitly declares all the same named methods in the program to have the same semantics, which implies a “frightening action at a distance”, linking the code in the source file with the code in some a library that the program has never seen.

IMO, it is better to require that the methods of the same name explicitly state that they conform to the named semantic "model" of behavior.

Yes, the compiler will ensure that the method can be called, but this is not much safer than saying:

  f :: [a] -> Int 

And let the compiler choose an arbitrary implementation, which may or may not be length .

(A similar idea can be made secure with the Scala "implicits" package or Haskell (GHC?) ".)

+1
source share

I am aware of two library implementations of structurally typed records in Haskell:

HList is older, and explained in an excellent article: Haskell skipped the object system (free on the Internet, but SO won't let me include more links)

vinyl is newer and uses the new GHC features. There is at least one library, vinyl-gl, using it.

I cannot answer part of the language design of your question.

0
source share

All Articles