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 +27/−0
- lib/Data/OpenADT/Tutorial.hs +566/−0
- open-adt-tutorial.cabal +55/−0
- src/Main.hs +6/−0
+ 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'