Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a data structure which represents a subset of numbers?

Tags:

types

elm

I was wondering if there's a way to make something like an Int type that can only represent a certain subset of numbers (like 0 ~ 29) and have the compiler throw an error if you try to do anything else with it.

I know I could do something like type MoonPhaseDay = Day1|Day2| ... |Day29 but that doesn't scale over a larger range.

I'm trying to keep in mind the advice, "make impossible states impossible to represent". I could get by with an Int but I'm curious if there's a better way.

like image 953
Eroc Avatar asked Jun 28 '19 19:06

Eroc


Video Answer


2 Answers

What you're looking for is sometimes known as "Dependent Types", and is not part of Elm today.

However, you can get something similar by creating a type in it's own module, and instead of exporting the raw type constructor, only exporting a custom function that you provide (making it an "Opaque Type"). This way, that module contains the only code that has to be guarded.


This answer by Nathan might be helpful when learning about Opaque types.

like image 60
Robert K. Bell Avatar answered Nov 15 '22 10:11

Robert K. Bell


You'd need to use a smart constructor. You enforce your constraints by controlling what you export from a module.

module Bounded exposing (Bounded, fromInt, toInt)


type Bounded
  = Bounded Int


fromInt : Int -> Maybe Bounded
fromInt n =
  if n < 0 || n > 29 then
    Nothing
  else
    Just (Bounded n)


toInt : Bounded -> Int
toInt (Bounded n) = n

Explanation

The Bounded type is defined as a union type with one data constructor. When exporting the type we don't export the constructor, i.e. it's not part of the public API. This is known as an opaque type.

Users of the module can only ever create Bounded types using fromInt. fromInt is called a smart constructor because it has some logic (the smarts) to enforce the constraints.

If you need to work with the integer it wraps you use toInt. toInt is guaranteed to always return an integer between 0 and 29 inclusive. There's no hidden backdoor that would allow any other integer to be wrapped.

The HaskellWiki has a nice write up on smart constructors.

Finally, Richard Feldman in his talk "Types and Tests at The Gilded Rose" goes through a nice example here that explains exactly what you want to do.

like image 40
Dwayne Crooks Avatar answered Nov 15 '22 09:11

Dwayne Crooks