Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to have recursive sum type, with each 'level' having distinct value?

Tags:

haskell

I was wondering if it is possible (I guess it is :) ) to have recursive sum type where we have a value of type X on every level, but somehow restrict ourselves that on every level of recursion we have distinct value of X?

For example, if I have

data MachineType = Worker | Flyer | Digger | Observer | Attacker
data Machine = Single MachineType | Multi MachineType Machine

type system would allow me to construct Machine with following type:

Multi Worker (Multi Worker (Single Worker))

but I want this to be restricted, so that only different MachineType-s are allowed.

Is there a way to encode this in type system?

You can just point me in the right direction, as I kinda don't know what to google :) (haskell set-like recursive sum types?)

like image 957
Nikola Maric Avatar asked Jun 29 '19 09:06

Nikola Maric


People also ask

What are the limitations of using recursive functions?

Limitations of Recursive Approach:Each function call requires push of return memory address, parameters, returned result,etc. and every function return requires that many pops. 2. Each time you call a function you use up some of your memory allocation may be in stack or heap.

Is it possible to have multiple recursive cases in a solution?

A recursive implementation may have more than one base case, or more than one recursive step. For example, the Fibonacci function has two base cases, n=0 and n=1.

Does every recursive function must have a return value?

All functions - recursive or not - have one or more return . The return may or may not include a value. It is for you to decide when you write the function. All explicit written return statements must return the correct type.


1 Answers

One solution is to specify that you can't extend a Machine with a duplicate MachineType. For that we first need a singleton type for MachineType:

{-# language TypeInType, GADTs, TypeOperators, ConstraintKinds,
    UndecidableInstances, TypeFamilies #-}

import Data.Kind
import GHC.TypeLits

data MachineType = Worker | Flyer | Digger | Observer | Attacker

data SMachineType t where
  SWorker   :: SMachineType Worker
  SFlyer    :: SMachineType Flyer
  SDigger   :: SMachineType Digger
  SObserver :: SMachineType Observer
  SAttacker :: SMachineType Attacker

Then we specify a constraint which is satisfiable if something is not contained in a list of MachineTypes, and otherwise throws a custom type error:

type family NotElem (x :: MachineType) (xs :: [MachineType]) :: Constraint where
  NotElem x '[]       = ()
  NotElem x (x ': xs) = TypeError
    (Text "Duplicate MachineTypes are not allowed in Machines" :$$:
    (Text "Can't add " :<>: ShowType x :<>: Text " to "
     :<>: ShowType (x ': xs)))
  NotElem x (y ': xs) = NotElem x xs

Then Machine is given as a GADT indexed by lists of MachineTypes:

data Machine (ts :: [MachineType]) where
  Single :: SMachineType t -> Machine '[ t ]
  Multi  :: NotElem t ts => SMachineType t -> Machine ts -> Machine (t ': ts)

The following definition has inferred type Machine '[ 'Flyer, 'Digger, 'Worker]:

m1 = Multi SFlyer (Multi SDigger (Single SWorker))

The following definition throws a type error:

m2 = Multi SFlyer (Multi SFlyer (Single SWorker))

With the error message being:

   Notes.hs:30:6: error: …
    • Duplicate MachineTypes are not allowed in Machines
      Can't add 'Flyer to '[ 'Flyer, 'Worker]
    ...
like image 116
András Kovács Avatar answered Nov 16 '22 02:11

András Kovács