Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Allowing only different 'any' types in Haskell

Tags:

haskell

Say we have a function with signature

foo :: a -> b -> Int

Is it possible to enforce a constraint ensuring that a and b are different? That is

foo :: Int -> String -> Int -- ok
foo :: Int -> Int    -> Int -- not ok

The purpose of this question is to learn more about Haskell and perhaps solve a design issue I'm facing. My particular case does not make sense if a == b, so I'd like to disallow that at the compiler level. I probably could make this issue go away with a different design altogether but that's besides the point now - the Pandoras box has been opened and I'd like to learn if an equality constraint on the type level is possible.

like image 313
Petras Purlys Avatar asked Jul 09 '21 11:07

Petras Purlys


People also ask

Can Haskell lists have different types?

Haskell also incorporates polymorphic types---types that are universally quantified in some way over all types. Polymorphic type expressions essentially describe families of types. For example, (forall a)[a] is the family of types consisting of, for every type a, the type of lists of a.

How do you assign a type in Haskell?

Haskell has three basic ways to declare a new type: The data declaration, which defines new data types. The type declaration for type synonyms, that is, alternative names for existing types. The newtype declaration, which defines new data types equivalent to existing ones.

What does == mean in Haskell?

The == is an operator for comparing if two things are equal. It is quite normal haskell function with type "Eq a => a -> a -> Bool". The type tells that it works on every type of a value that implements Eq typeclass, so it is kind of overloaded.

What is nil Haskell?

The Nil constructor is an empty list. It contains no objects. So any time you're using the [] expression, you're actually using Nil . Then the second constructor concatenates a single element with another list.

What are the different types of functions in Haskell?

Like pretty much any other language, Haskell has integers, floating point numbers, characters, and strings. We can define a function that uses all of these basic types: myFunction :: Int -> Float -> Char -> String -> String myFunction x y c str = (c : str) ++ “ “ ++ show x ++ show y myResult :: String myResult = myFunction 4 5.5 ‘a’ “bcd”

What is the difference between lists in Haskell?

First, lists in Haskell are homogenous. This means that a Haskell list can only hold elements of the same type Second, lists in Haskell are (internally) implemented as linked lists. This is different from many other languages, where the word "list" and "array" is used interchangably.

What is Haskell programming language?

Haskell is a functional language and it is strictly typed, which means the data type used in the entire application will be known to the compiler at compile time.

How do you append a list in Haskell?

Appending / Joining / Growing Haskell lists There are fourways to join / concatentate / append / grow Haskell lists: (++):: list1 -> list2 -> joined-list When you have a few known lists that you want to join, you can use the ++operator: [True, False] ++[False, False] [1, 2, 3] ++[4, 5, 6] ++[10, 20, 30, 50, 90]


Video Answer


3 Answers

Like this?

{-# LANGUAGE DataKinds #-}
module TyEq where

import Data.Type.Equality (type (==))
import GHC.TypeLits
import Data.Kind (Constraint)

class ((a == b) ~ eq) => Foo a b eq where
  foo :: a -> b -> Int

instance (TypeError ('Text "NAUGHTY!"), (a == b) ~ True) => Foo a b 'True where
  foo _ _ = error "The types have failed us!"

instance (a == b) ~ False => Foo a b 'False where
  foo _ _ = 42

--_ = foo True True -- NAUGHTY!
_ = foo () True
_ = foo True (42 :: Int)

Notice how the eq index is needed in the Foo type class, or we're getting 'duplicate instance' errors despite differing instance contexts (only heads are taken into account).

We can streamline that a bit using purpose-specific type family:

type family Check a b :: Constraint where
  Check a a = TypeError ('Text "VERY NAUGHTY!")
  Check a b = ()

foo' :: Check a b => a -> b -> Int
foo' _ _ = 42

--_ = foo' True True -- VERY NAUGHTY!
_ = foo' () True
_ = foo' True (42 :: Int)

I'm pretty sure I've learnt that all from Sandy Maguire's 'Thinking with Types', which I sincerely recommend to everyone interested :)

like image 104
Artur Gajowy Avatar answered Nov 15 '22 23:11

Artur Gajowy


The type a :~: b -> Void is inhabited (by a total function) exactly when a and b are different, so one lightweight way would be to take one as an argument. You can then ask the compiler to check whether callers supply a total argument with -Wincomplete-patterns. A complete example might look like this:

{-# LANGUAGE EmptyCase #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TypeOperators #-}

import Data.Void
import Data.Type.Equality

foo :: (a :~: b -> Void) -> a -> b -> Int
foo _ _ _ = 0

fooUseGood :: Int
fooUseGood = foo (\case) True () -- accepted, no warning

fooUseBad :: Int
fooUseBad = foo (\case) () () -- warning: Patterns not matched: Refl
like image 21
Daniel Wagner Avatar answered Nov 15 '22 22:11

Daniel Wagner


Here's a riff on Artur's version. It uses machinery developed for the trivial-constraints package.

{-# LANGUAGE DataKinds, KindSignatures, MultiParamTypeClasses, FlexibleInstances, FlexibleContexts, AllowAmbiguousTypes, TypeApplications, ScopedTypeVariables, ExplicitNamespaces, TypeOperators, UndecidableSuperClasses, UndecidableInstances #-}


import Data.Type.Equality (type (==))
import GHC.TypeLits
import GHC.Exts (Any)

-- The Any constraint prevents malicious/ignorant
-- instantiation with no = undefined. The only
-- way to produce an instance of this class is in a local context
-- using something deeply unsafe like unsafeCoerce, magicDict, or unsafePerformIO.
class Any => Bottom where
  no :: a

-- Why not just put the `no` method in this class?
-- Because then `no` will need to be applied to the type argument `e`, which is
-- very annoying and unnecessary.
class (TypeError e, Bottom) => Oops e

class Foo a b (eq :: Bool) where
  foo' :: a -> b -> Int

foo :: forall a b. Foo a b (a == b) => a -> b -> Int
foo = foo' @a @b @(a == b)

instance Oops ('Text "NAUGHTY!") => Foo a b 'True where
  foo' = no

instance Foo a b 'False where
  foo' _ _ = 42
like image 38
dfeuer Avatar answered Nov 15 '22 23:11

dfeuer