What is the difference between value constructors and tuples?

落花浮王杯 提交于 2019-12-04 02:22:25
Nikita Volkov

Well, coneptually there indeed is no difference and in fact other languages (OCaml, Elm) present tagged unions exactly that way - i.e., tags over tuples or first class records (which Haskell lacks). I personally consider this to be a design flaw in Haskell.

There are some practical differences though:

  1. Laziness. Haskell's tuples are lazy and you can't change that. You can however mark constructor fields as strict:

    data Tree a = EmptyTree | Node !a !(Tree a) !(Tree a)
    
  2. Memory footprint and performance. Circumventing intermediate types reduces the footprint and raises the performance. You can read more about it in this fine answer.

    You can also mark the strict fields with the the UNPACK pragma to reduce the footprint even further. Alternatively you can use the -funbox-strict-fields compiler option. Concerning the last one, I simply prefer to have it on by default in all my projects. See the Hasql's Cabal file for example.


Considering the stated above, if it's a lazy type that you're looking for, then the following snippets should compile to the same thing:

data Tree a = EmptyTree | Node a (Tree a) (Tree a)

data Tree a = EmptyTree | Node {-# UNPACK #-} !(a, Tree a, Tree a)

So I guess you can say that it's possible to use tuples to store lazy fields of a constructor without a penalty. Though it should be mentioned that this pattern is kinda unconventional in the Haskell's community.

If it's the strict type and footprint reduction that you're after, then there's no other way than to denormalize your tuples directly into constructor fields.

They're what's called isomorphic, meaning "to have the same shape". You can write something like

data Option a = None | Some a

And this is isomorphic to

data Maybe a = Nothing | Just a

meaning that you can write two functions

f :: Maybe a -> Option a
g :: Option a -> Maybe a

Such that f . g == id == g . f for all possible inputs. We can then say that (,,) is a data constructor isomorphic to the constructor

data Triple a b c = Triple a b c

Because you can write

f :: (a, b, c) -> Triple a b c
f (a, b, c) = Triple a b c

g :: Triple a b c -> (a, b, c)
g (Triple a b c) = (a, b, c)

And Node as a constructor is a special case of Triple, namely Triple a (Tree a) (Tree a). In fact, you could even go so far as to say that your definition of Tree could be written as

newtype Tree' a = Tree' (Maybe (a, Tree' a, Tree' a))

The newtype is required since you can't have a type alias be recursive. All you have to do is say that EmptyLeaf == Tree' Nothing and Node a l r = Tree' (Just (a, l, r)). You could pretty simply write functions that convert between the two.

Note that this is all from a mathematical point of view. The compiler can add extra metadata and other information to be able to identify a particular constructor making them behave slightly differently at runtime.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!