packages feed

open-adt-tutorial (empty) → 1.0

raw patch · 4 files changed

+654/−0 lines, 4 filesdep +basedep +constraintsdep +deriving-compat

Dependencies added: base, constraints, deriving-compat, open-adt, open-adt-tutorial, recursion-schemes, row-types, template-haskell

Files

+ LICENSE view
@@ -0,0 +1,27 @@+Copyright 2018 Jordan Woehr++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:++  1. Redistributions of source code must retain the above copyright+     notice, this list of conditions and the following disclaimer.++  2. Redistributions in binary form must reproduce the above copyright+     notice, this list of conditions and the following disclaimer in the+     documentation and/or other materials provided with the distribution.++  3. Neither the name of the copyright holder nor the names of its+     contributors may be used to endorse or promote products derived+     from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ lib/Data/OpenADT/Tutorial.hs view
@@ -0,0 +1,566 @@+-- | Description : A short tutorial with code.++{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedLabels #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE ViewPatterns #-}++{-# OPTIONS_HADDOCK show-extensions #-}++module Data.OpenADT.Tutorial where++import           Data.OpenADT++#if !(MIN_VERSION_base(4,11,0))+import           Data.Semigroup                           ( (<>) )+#endif++-- row-types+import           Data.Row+import           Data.Row.Variants++-- recursion-schemes+import           Data.Functor.Foldable                    ( Fix(..)+                                                          , cata+                                                          )++-- deriving-compat+import           Text.Show.Deriving+import           Data.Eq.Deriving++-- * Introduction+--+-- $introduction+-- This module demonstrates how to create open algebraic data types (ADT), and+-- is intended to be read top-to-bottom. Throughout this tutorial, we will+-- create two different list types, 'List1' and 'List2', that share two of+-- their constructors. We will also see various ways that our structures can be+-- traversed and modified.+--+-- Note that several extensions are used, which should show up in the Haddock+-- generated documentation. In particular, I recommend being familiar with+-- OverloadedLabels and TypeApplications, otherwise some syntax may be+-- confusing.++-- | A type alias to reduce typing when writing the types of algebras.+type Alg f x = f x -> x++-- * Constructors+--+-- $constructors+-- The first step is to create data types that correspond to the constructors+-- of the types we want. We create three different types corresponding to+-- three list constructors: 'NilF', 'Cons1F', 'Cons2F'.+--+-- For example, a two elements cons, 'Cons2F', is defined as:+--+-- @+-- -- | A two element cons element.+-- data Cons2F a x = Cons2F' a a x+--   deriving (Eq, Functor, Show)+-- @+--+-- Note that the recursion of the structure is expressed in the type variable+-- @x@ rather than explicitly. The constructor should also be a functor.+--+-- For convenience we can use 'deriveShow1' from "Text.Show.Deriving" to derive+-- 'Show1' instances (and 'deriveEq1' from "Data.Eq.Deriving" for 'Eq1' if+-- desired).+--+-- @+-- 'deriveShow1' ''Cons2F+-- @++-- | The base case of a list type.+data NilF x = NilF'+  deriving (Eq, Functor, Show)++-- | A one element cons element.+data Cons1F a x = Cons1F' a x+  deriving (Eq, Functor, Show)++-- | A two element cons element.+data Cons2F a x = Cons2F' a a x+  deriving (Eq, Functor, Show)++deriveEq1 ''NilF+deriveEq1 ''Cons1F+deriveEq1 ''Cons2F++deriveShow1 ''NilF+deriveShow1 ''Cons1F+deriveShow1 ''Cons2F++-- * Rows+--+-- $rows+-- With constructors defined, we can define type synonyms that describe how+-- constructors are combined to create an ADT. These synonyms+-- are 'Row's, which are lists of labels (given by type-level strings) with+-- corresponding types. The type operator ('.==') is used to associate a label+-- with its type, the result of which is a row; ('.+') is used to combine two+-- rows into a new row. These operators, and the 'Row' type, are defined in+-- the row-types package.++-- | The row of the first list type. It defines a \"standard\" list type.+type List1RowF a = ( "nilF"   .== NilF+                  .+ "cons1F" .== Cons1F a+                   )++-- | The row of the second list type. This type re-uses the constructors of+-- 'List1RowF' and includes a third constructor: a two element cons.+type List2RowF a = ( "nilF"   .== NilF+                  .+ "cons1F" .== Cons1F a+                  .+ "cons2F" .== Cons2F a+                   )++-- * Types+--+-- $types+-- The 'VarF' newtype forms the basis of an open ADT. It wraps the variant+-- whose possible values are the constructors of the ADT. Its type constructor+-- takes two parameters:+--+-- - a row of types with the kind @(* -> *)@,+-- - and a type of kind @(*)@ that is applied to each element of the row.+--+-- 'VarF' has a functor instance when all the types of the row are functors.+-- Using 'VarF' we can define the base functor of our ADTs. Taking the fixpoint+-- of these functor types yields the ADT.++-- | The base functor of the 'List1' type. It is a 'VarF' with the row+-- 'List1RowF'.+type List1F a = VarF (List1RowF a)++-- | A list type. We obtain this type by taking the fixed point of its base+-- functor.+type List1  a = Fix (VarF (List1RowF a))++-- | The base functor of the 'List2' type.+type List2F a = VarF (List2RowF a)++-- | A list type with a two element cons. This type is defined with the+-- 'OpenADT' synonym, which is simply conveinience for @Fix (VarF r)@.+type List2  a = OpenADT (List2RowF a)++-- * Patterns+--+-- $patterns+-- In order to simplify our code, and avoid using 'VarF' directly, we can+-- define pattern synonyms for each constructor. Importantly, if written+-- generically enough, these patterns will work for a constructor regardless+-- the specific type it is used in. For example, we can write a single pattern+-- that matches the 'NilF' constructor in both the 'List1' and 'List2' types!+-- This is achieved with the pattern below:+--+-- @+-- pattern NilF :: (OpenAlg r "nilF" NilF x) => VarF r x+-- pattern NilF \<- VarF ('view' #nilF -\> Just NilF')+--   where NilF = VarF ('IsJust' #nilF NilF')+-- @+--+-- Note that in 'NilF', the variables @r@ and @x@ are left abstract. This+-- allows any row and type to be used with the pattern. In our+-- running example, @r@ could be either 'List1RowF' or 'List2RowF', while+-- @v@ could be either 'List1' or 'List2', respectively.+--+-- The pattern for the \"fixed\" constructor fixes the @x@ parameter to+-- @OpenADT r@ (below).+--+-- @+-- pattern Nil :: (OpenAlg r "nilF" NilF (OpenADT r)) => OpenADT r+-- pattern Nil = 'Fix' NilF+-- @+--+-- Since the patterns for each constructor are fairly repetative with only the+-- name changing, "Data.OpenADT.TH" provides a function, 'mkVarPattern', that+-- generates these patterns for you! The function takes four parameters:+--+-- 1. the type of the constructor,+-- 2. the label to be used for the constructor in the row,+-- 3. the name of the \"fixed\" pattern,+-- 4. and the name of the base functor pattern.+--+--+-- For example, the constructors for 'Cons1F' and 'Cons2F' can be created with:+--+-- @+-- mkVarPattern ''Cons1F \"cons1F\" \"Cons1\" \"Cons1F\"+-- mkVarPattern ''Cons2F \"cons2F\" \"Cons2\" \"Cons2F\"+-- @++pattern NilF :: (OpenAlg r "nilF" NilF x) => VarF r x+pattern NilF <- VarF (view #nilF -> Just NilF')+  where NilF = VarF (IsJust #nilF NilF')++pattern Nil :: (OpenAlg r "nilF" NilF (OpenADT r)) => OpenADT r+pattern Nil = Fix NilF++mkVarPattern ''Cons1F "cons1F" "Cons1" "Cons1F"+mkVarPattern ''Cons2F "cons2F" "Cons2" "Cons2F"++-- * Constructing Values+--+-- $constructing+-- The patterns that we have written can be used to construct values as if they+-- were normal ADTs. The one caveat is that GHC cannot infer the types since+-- the variable @r@ in the patterns can be any row. This is, in practice, not+-- much of an issue as top-level type declarations are sufficient in most+-- cases.+--+-- For the remaining of this tutorial we will use the following lists in+-- examples:+--+-- @+-- exList1 :: List1 Int+-- exList1 = Cons1 0 (Cons1 1 Nil)+-- @+--+-- @+-- exList2 :: List2 Int+-- exList2 = Cons2 2 3 (Cons1 4 Nil)+-- @++-- | Construct a 'List1'. The patterns 'Cons1' and 'Nil' are used.+--+-- > >>> print exList1+-- > Fix (VarF (Cons1F' 0 (Fix (VarF (Cons1F' 1 (Fix (VarF NilF')))))))+exList1 :: List1 Int+exList1 = Cons1 0 (Cons1 1 Nil)++-- | Construct a 'List2'.+--+-- > >>> print exList2+-- > Fix (VarF (Cons2F' 2 3 (Fix (VarF (Cons1F' 4 (Fix (VarF NilF')))))))+exList2 :: List2 Int+exList2 = Cons2 2 3 (Cons1 4 Nil)++-- * Adding Constructors+--+-- $lifting+-- Adding constructors to an open ADT is easy. We can use 'cata' from the+-- recursion-schemes library to define a __cata__morphism (a bottom-up+-- traversal) over our ADT. At each node in our structure, we apply the+-- function 'diversifyF' (see also 'diversify'), which adds constructors to our+-- variant (without making any changes).+--+-- In the following example, a 'List1' is changed to a 'List2'. Note how the+-- type applications extension (-XTypeApplications) can be used to easily+-- specify the first type argument to 'diversifyF', which is the row the+-- variant is extended with.+--+-- @+-- result1 :: List2 Int+-- result1 = 'cata'+--   ('Fix' . 'diversifyF' \@(\"cons2F\" .== Cons2F Int)) exList1+-- @++-- | The constructor 'Cons2F' is added to 'exList1' without changing its+-- structure.+--+-- > >>> print result1+-- > Fix (VarF (Cons1F' 0 (Fix (VarF (Cons1F' 1 (Fix (VarF NilF')))))))+result1 :: List2 Int+result1 = cata+  (Fix . diversifyF @("cons2F" .== Cons2F Int)) exList1++-- * Removing Constructors+--+-- $restricting+-- To convert an ADT of one type to another with /fewer/ constructors, we need+-- to specify how constructors are removed should they exist in the structure.+-- The function 'reduceVarF' removes the constructors in a structure+-- corresponding to the fields of the record ('Rec' from row-types) of its+-- first argument. This function may only be used to remove constructors; it+-- can not add or modify them. More specifically, it is constrained such that+-- the set of labels of the row of the input record must be exactly the set+-- of labels of the input 'VarF' /less/ the output 'VarF'. While more+-- constrained than strictly necessary, this allows GHC to infer types better.+-- Note in the example below that there are no annotations other than at the+-- top-level.+--+-- @+-- result2 :: List1 Int+-- result2 = 'cata' ('Fix' . 'reduceVarF' fns) exList2+--  where+--   fns = #cons2F .== \\(Cons2F' a b x) -> Cons1F a (Cons1 b x)+-- @+--+-- In the following sections we will see how to manipulate open ADT structures+-- more generally.++-- | Remove a constructor using 'reduceVarF' to convert a 'List2' to a 'List1'.+--+-- > >>> print result2+-- > Fix (VarF (Cons1F' 2 (Fix (VarF (Cons1F' 3 (Fix (VarF (Cons1F' 4 (Fix (VarF NilF'))))))))))+result2 :: List1 Int+result2 = cata (Fix . reduceVarF fns) exList2+ where+  fns = #cons2F .== \(Cons2F' a b x) -> Cons1F a (Cons1 b x)++-- * Pattern Matching+--+-- $matching+-- We previously used our constructor patterns to create structures, but the+-- patterns are bidirectional, so we can just as easily match with them. For+-- example, we can write a standard recursion scheme.+--+-- @+-- result3 :: List2 Int+-- result3 = 'cata' alg exList1+--  where+--   alg :: Alg (List1F Int) (List2 Int)+--   alg (Cons1F a x) = Cons2 a a x+--   alg NilF = Nil+--   alg _ = error+--     \"Unfortunately using these patterns will always result in non-\\\\+--     \\\\exhaustive pattern match errors, hence the default case. :(\"+-- @+--+-- Unfortunately, as noted in the default case of @alg@, GHC can not (does+-- not?) check the exhaustiveness of pattern synonyms. Using a default case+-- will get rid of non-exhaustiveness warnings, but could also hide bugs if it+-- is partial.++-- | Use \"traditional\" pattern matching to write an algebra over a 'List1'.+--+-- > >>> print result3+-- > Fix (VarF (Cons2F' 0 0 (Fix (VarF (Cons2F' 1 1 (Fix (VarF NilF')))))))+result3 :: List2 Int+result3 = cata alg exList1+ where+  alg :: Alg (List1F Int) (List2 Int)+  alg (Cons1F a x) = Cons2 a a x+  alg NilF = Nil+  alg _ = error $+    "Unfortunately using these patterns will always result in non-" <>+    "exhaustive pattern match errors, hence the default case. :("++-- * Explicit Cases+--+-- $cases+-- An alternative that does not produce incomplete pattern warnings is to use+-- the 'caseonF' or 'switchF' functions (which are versions of 'caseon' and+-- 'switch' that operate on a 'VarF'). Similar to 'reduceVarF', these functions+-- require a record of functions that are then applied to the variant. However,+-- 'caseonF' and 'switchF' are more general than 'reduceVarF' in the sense that+-- the return type of the functions are not restricted. Note that the labels+-- and functions of the provided record must exactly match that of the input+-- type: no constructors may be omitted, nor is it possible to write any kind+-- of default case.+--+-- @+-- result4 :: List1 String+-- result4 = 'cata' ('caseonF' r) exList1+--  where r = #nilF   .== (\\NilF' -> Nil)+--         .+ #cons1F .== (\\(Cons1F' a x) -> Cons1 (show \@Int a) x)+-- @++-- | An alternative way of writing 'result3' using 'caseonF'.+--+-- > >>> print result4+-- > Fix (VarF (Cons2F' 0 0 (Fix (VarF (Cons2F' 1 1 (Fix (VarF NilF')))))))+result4 :: List2 Int+result4 = cata (caseonF r) exList1+ where r = #nilF   .== (\NilF' -> Nil)+        .+ #cons1F .== (\(Cons1F' a x) -> Cons2 a a x)++-- * A Type Class Approach+--+-- $classes+-- An alternative approach to direct pattern matching is using type classes to+-- define each case of an open ADT. The type class function can then be applied+-- to each value in an ADT recursively with 'cata' as we have seen before.+--+-- This approach is beneficial compared to direct pattern matching because the+-- compiler can find \"non-exhaustive matches\" in the form of missing+-- instances. Similar to patterns, the typeclasses are generic enough to work+-- on any open ADT type provided all of that type's constructors satisfy the+-- constraint.+--+-- Consider, for example, the following class that defines an operation for+-- modifying the contents of a list.+--+-- @+-- class OverList a a' r (f :: * -> *) where+--   fmapList' :: (a -> a') -> f (OpenADT r) -> OpenADT r+--+-- instance ( OpenAlg r "nilF" NilF (OpenADT r)) => OverList a a' r NilF where+--   fmapList' _ NilF' = Nil+--+-- instance ( OpenAlg r "cons1F" (Cons1F a') (OpenADT r)) => OverList a a' r (Cons1F a) where+--   fmapList' f (Cons1F' a x) = Cons1 (f a) x+--+-- instance ( OpenAlg r "cons2F" (Cons2F a') (OpenADT r)) => OverList a a' r (Cons2F a) where+--   fmapList' f (Cons2F' a b x) = Cons2 (f a) (f b) x+--+-- instance (Forall v (OverList a a' r)) => OverList a a' r (VarF v) where+--   fmapList' f = varFAlg \@(OverList a a' r) (fmapList' f)+-- @+--+-- The class 'OverList' has instances for all the constructors of 'List1' and+-- 'List2'. The final step is to recursively apply 'fmapList'' to the ADT.+--+-- @+-- fmapList f = 'cata' (fmapList' f)+-- @+--+-- The function 'varFAlg' is used to apply 'fmapList'' to a 'VarF'. As long as+-- all constructors satisfy the required constraint ('OverList' in this case),+-- we do not need to know the exact constructor.+--+-- Note that the type of 'fmapList' is polymorphic in its input and output+-- rows. Using 'fmapList', we can operate on both 'List1' and 'List2' types, or+-- any type that combines the three constructors that have 'OverList'+-- instances.++-- | This type class defines a fmap-like operation over lists.+class OverList a a' r (f :: * -> *) where+  fmapList' :: (a -> a') -> f (OpenADT r) -> OpenADT r++instance ( OpenAlg r "nilF" NilF (OpenADT r)+         ) => OverList a a' r NilF where+  fmapList' _ NilF' = Nil++instance ( OpenAlg r "cons1F" (Cons1F a') (OpenADT r)+         ) => OverList a a' r (Cons1F a) where+  fmapList' f (Cons1F' a x) = Cons1 (f a) x++instance ( OpenAlg r "cons2F" (Cons2F a') (OpenADT r)+         ) => OverList a a' r (Cons2F a) where+  fmapList' f (Cons2F' a b x) = Cons2 (f a) (f b) x++instance (Forall v (OverList a a' r)) => OverList a a' r (VarF v) where+  fmapList' f = varFAlg @(OverList a a' r) (fmapList' f)++-- | Apply the 'fmapList'' function to any @(OpenADT r)@ provided all its+-- constructors satisfy the constraint @(OverList a a' s)@.+fmapList :: forall a a' r s.+            ( Forall r Functor+            , Forall r (OverList a a' s)+            )+            => (a -> a') -> OpenADT r -> OpenADT s+fmapList f = cata (fmapList' f)++-- | Demonstrate that 'fmapList' can be applied to 'exList1'.+--+-- > result5 = fmapList (show @Int) exList1 :: List1 String+--+-- > >>> print result5+-- > Fix (VarF (Cons1F' "0" (Fix (VarF (Cons1F' "1" (Fix (VarF NilF')))))))+result5  :: List1 String+result5 = fmapList (show @Int) exList1++-- | Demonstrate that 'fmapList' can be applied to 'exList2'.+--+-- > result6 = fmapList (show @Int) exList2 :: List2 String+--+-- > >>> print result6+-- > Fix (VarF (Cons2F' "2" "3" (Fix (VarF (Cons1F' "4" (Fix (VarF NilF')))))))+result6  :: List2 String+result6 = fmapList (show @Int) exList2++-- * Combining Explicit Cases and Type Classes+--+-- $cases2+-- Suppose we have an ADT where we want to operate on a subset of its+-- constructors. For example, a subset of constructors do not implement a type+-- class that the others do, or perhaps we just want to override a single+-- constructor while using some default implementation for the other cases.+-- These situations can be handled with the type class approach just discussed+-- with overlappable instances. Unfortunately, among other issues, we do not+-- receive compiler errors if we add a constructor but forget to write its+-- instance; the default instance automatically takes over.+--+-- Fortunately, we can use a combination of explicit cases and the type class+-- approach to prevent this from happening. The idea is to partition the ADT+-- variant and handle each set of constructors separately. To partition the+-- ADT, we can use 'multiTrialF' (see also 'multiTrial') one or more times.+--+-- @+-- result7 = 'cata' alg exList2 where+--   alg :: Alg (List2F Int) (List1 String)+--   alg w = case 'multiTrialF' \@("cons2F" .== Cons2F Int) w of+--+--     -- Explicit handling of specified constructors+--     Left v -> 'caseonF' r v+--+--     -- All others handled by fmapList'+--     Right leftovers -> 'fmapList'' (show \@Int) leftovers+--+--   r = #cons2F .== \\(Cons2F' a b x) ->+--         Cons1 ("(" <> show a <> " : " <> show b <> ")") x+-- @++-- | Demonstrate how to handle subsets of a 'VarF' individually.+--+-- Below is an alternate, but identical, implementation for the case where one+-- subset of variants matched is a single variant ('trialF' is used instead of+-- 'multiTrialF').+--+-- @+-- result7 = 'cata' alg exList2 where+--   alg w = case 'trialF' w #cons2F of+--     Left (Cons2F' a b x) -> Cons1 ("(" <> show a <> " : " <> show b <> ")") x+--     Right leftovers -> 'fmapList'' (show \@Int) leftovers+-- @+--+-- > >>> print result7+-- > Fix (VarF (Cons1F' "(2 : 3)" (Fix (VarF (Cons1F' "4" (Fix (VarF NilF')))))))+result7 :: List1 String+result7 = cata alg exList2 where+  alg :: Alg (List2F Int) (List1 String)+  alg w = case multiTrialF @("cons2F" .== Cons2F Int) w of++    -- Explicit handling of specified constructors+    Left v -> caseonF r v++    -- All others handled by fmapList'+    Right leftovers -> fmapList' (show @Int) leftovers++  r = #cons2F .== \(Cons2F' a b x) ->+        Cons1 ("(" <> show a <> " : " <> show b <> ")") x++-- | This is the function invoked by the executable in this package. It simply+-- prints out the examples.+main' :: IO ()+main' = do+  putStrLn "exList1:"+  print exList1++  putStrLn "\nexList2:"+  print exList2++  putStrLn "\nresult1:"+  print result1++  putStrLn "\nresult2:"+  print result2++  putStrLn "\nresult3:"+  print result3++  putStrLn "\nresult4:"+  print result4++  putStrLn "\nresult5:"+  print result5++  putStrLn "\nresult6:"+  print result6++  putStrLn "\nresult7:"+  print result7
+ open-adt-tutorial.cabal view
@@ -0,0 +1,55 @@+name:          open-adt-tutorial+version:       1.0+cabal-version: >= 1.10+category:      Data+build-type:    Simple+license:       BSD3+license-file:  LICENSE+copyright:     Copyright (c) 2018 Jordan Woehr+maintainer:    Jordan Woehr+homepage:      https://github.com/woehr/open-adt+bug-reports:   https://github.com/woehr/open-adt/issues++synopsis:      Open algebraic data type examples.++description:   Example usage of open-adt with haddock documentation. Read the+               "Data.OpenADT.Tutorial" module from top to bottom.++tested-with:   GHC == 8.2.1, GHC == 8.2.2,+               GHC == 8.4.1, GHC == 8.4.2, GHC == 8.4.3+--               GHC == 8.6.1++source-repository head+  type:     git+  location: https://github.com/woehr/open-adt++library+  build-depends: base              >= 4.9   && < 5+               , constraints       >= 0.8   && < 1+               , deriving-compat   >= 0.3   && < 1+               , open-adt          >= 1     && < 2+               , recursion-schemes >= 5     && < 6+               , row-types         >= 0.2.3 && < 1+               , template-haskell  >= 2.11  && < 3++  exposed-modules:  Data.OpenADT.Tutorial++  default-language: Haskell2010+  hs-source-dirs:   lib+  ghc-options:      -Wall+                    -Wcompat+                    -Wincomplete-uni-patterns+                    -Wincomplete-record-updates++executable open-adt-tutorial+  build-depends: open-adt-tutorial+               , base              >= 4.9++  main-is:          Main.hs++  default-language: Haskell2010+  hs-source-dirs:   src+  ghc-options:      -Wall+                    -Wcompat+                    -Wincomplete-uni-patterns+                    -Wincomplete-record-updates
+ src/Main.hs view
@@ -0,0 +1,6 @@+module Main where++import Data.OpenADT.Tutorial (main')++main :: IO ()+main = main'