Greetings everyone, I’m currently studying computer mathematics at the University of Linz/Austria and am now working on my master-thesis: Refinement types for Elm.
In this post I will first explain the main concept and then propose a syntax. I would be grateful if you could give me feedback regarding the syntax.
What are Refinement Types?
Let’s look at the following example.
{-| @refine \int -> int /= 0
-}
type alias IntWithoutZero =
Int
dividedBy : IntWithoutZero -> Int -> Int
dividedBy a b = b // a
3 |> dividedBy 0
will normally return 0
, but using my type-checker it will now throw an error instead:
dividedBy expected the first element to be an IntWithoutZero
but was given an Int instead.
Hint:
I can't convert 0 to IntWithoutZero because 0 /= 0 is false.
These kinds of types are called refinement types. Liquid types are refinement types, where the refinement (int /= 0
) can only contain logical operators (not, and, or
), constants (like 0
), comparisons (<=,<,=, /=
) and +
. Also it only works for Bool
and Int
.
Such a type-checker does already exist for Haskell(LiquidHaskell) but its not very beginner friendly. The goal of my thesis would therefore be to make refinement type accessible with a smaller border of entry.
Expected result
Liquid types are still not perfect but I am expecting at least to be able to successfully check the IntWithoutZero
type from the example above.
I am also not expecting to have as many features as LiquidHaskell does, mainly because I would rather like to focus on good error messages.
The main work of this thesis will be about the mathematics behind the types and not so much the actual implementation.
Syntax
LiquidHaskell writes its types in a comment block before the normal specification. A similar syntax would look like this:
{-| Checks if the list contains a specific element -}
{- contains : a:Int -> List {b:Int | b >= 0} -> {out:Bool | a >= 0 || not out} -}
contains : Int -> List Int -> Bool
contains =
List.contains
This is the same syntax as proposed in the original paper, but I feel this might be quite hard to read. Also, this syntax does not go well with both the elm-docs and elm-format.
So instead, I would propose something like
{-| @refine \a -> a >= 0
-}
type alias Nat =
Int
{-| Checks if the list contains a specific element
@refine \a _ out -> a >= 0 || not out
-}
contains : Int -> List Nat -> Bool
contains x list =
..
By using alias types, someone not familiar with my type-checker can still understand what it does.
So I’m interesting to know:
- Does the proposed syntax look appealing to you?
- Do you have a better idea how to design the syntax around elm-format and elm-docs?
Edit:
Added \
to the examples. Thanks @Zimm_i48 for that.