您可以使用TypeFamilies 和FlexibleContexts 或FunctionalDependencies、FlexibleInstances 和MultiParamTypeClasses 完成与您在发布的问题中尝试的类似的操作。
TypeFamilies 和FunctionalDependencies 都是类型检查器可以完全基于另一种类型来确定一种类型的机制。这将为我们解决两个问题。您遇到的第一个问题是,我们无法在类的实例声明中从类型a->b 中获取类型a 和b。第二个问题是我们需要能够根据表达式的类型来判断它的计算结果是什么类型。 TypeFamilies 允许我们创建可以解构类型的类型级函数。 FunctionalDependencies 允许我们声明一个类型可以从另一个类型中恢复。
编辑:TypeFamilies 提供了比FunctionalDependencies 更好的解决方案。
类型族
使用TypeFamilies,我们可以从类型中提取出需要的类型
{-#LANGUAGE FlexibleContexts, TypeFamilies, UndecidableInstances #-}
module Main (
main
) where
data App ef ea = App ef ea
deriving (Show)
data Atom a = Atom a
deriving (Show)
class Eval e where
-- The type of what an expression evaluates to can be determined from the type of the expession
-- V is a type function that gets this type from the type of the expression
type V e :: *
eval :: e -> V e
instance Eval (Atom a) where
type V (Atom a) = a
eval (Atom a) = a
-- the class of functions from a to b
-- The only allowed f is (a->b)
-- This creates two type functions, A and B, which can be used to get the type arguments to ->
class (f ~ (A f -> B f)) => F f where
type A a :: *
type B b :: *
instance F (a->b) where
type A (a->b) = a
type B (a->b) = b
instance (Eval ef, F (V ef), Eval ea, V ea ~ A (V ef)) => Eval (App ef ea) where
-- B (V ef) is the only thing that requires UndecidableInstances.
-- It is probably decidable.
type V (App ef ea) = B (V ef)
eval (App ef ea) = eval ef $ eval ea
使用需要显式指定多态类型的类型,这不仅是因为单态限制,还因为App (Atom (Integer->Integer)) (Atom Int) 是合法类型,尽管Integer->Integer 不能应用于@ 987654339@.
-- Example code
instance Show (a->b) where
show _ = "->"
test1 :: (Num n) => App (Atom (n->n)) (Atom n)
test1 = App (Atom (+1)) (Atom 3)
test2 = App (App (Atom ((+) :: Int -> Int -> Int)) (Atom (1 :: Int))) (Atom (3 :: Int))
test3 = App (Atom reverse) (Atom "abc")
main = do
print test1
print $ eval test1
putStrLn ""
print test2
print $ eval test2
putStrLn ""
print test3
print $ eval test3
尝试使用不兼容类型的应用程序评估抽象语法树
-- This still type checks
appStringToString = App (Atom "def") (Atom "abc")
-- But this won't
fails = eval appStringToString
编译失败
Couldn't match type `A [Char]' with `[Char]'
In the expression: eval appStringToString
In an equation for `fails': fails = eval appStringToString
编辑: 定义以下内容,并在示例中使用它代替 App,让您在所有示例中放弃所有类型注释。
app :: (Eval ef, F (V ef), Eval ea, V ea ~ A (V ef)) => ef -> ea -> App ef ea
app = App
app 在构造App 时为App 捕获并保留Eval 实例所需的类型信息。用app 构造的表达式树在构造上是正确的。例如
appStringToString = app (Atom "def") (Atom "abc")
导致编译器错误:
Couldn't match type `A [Char]' with `[Char]'
Expected type: A (V (Atom [Char]))
Actual type: V (Atom [Char])
In the expression: app (Atom "def") (Atom "abc")
In an equation for `appStringToString':
appStringToString = app (Atom "def") (Atom "abc")
为FunctionalDependencies 添加一个类似的函数并不能解决类型推断问题,即使是NoMonomorphismRestriction。这使得TypeFamilies 明显胜过FunctionalDependencies。
函数依赖
编辑:TypeFamilies 提供了一个更好的解决方案。此部分仅用于比较。
使用FunctionalDependencies,我们声明该类型可以稍后恢复。它在处理多态性方面不如TypeFamilies。
{-#LANGUAGE FlexibleInstances, MultiParamTypeClasses, FunctionalDependencies, UndecidableInstances #-}
module Main (
main
) where
data App ef ea = App ef ea
deriving (Show)
data Atom a = Atom a
deriving (Show)
-- Expressions that evaluate to a
-- The type of what an expression evaluates to can be determined from the type of the expession
class Eval a e | e -> a where
eval :: e -> a
instance Eval a (Atom a) where
eval (Atom a) = a
-- the class of functions from a to b
class F a b f | f -> a, f -> b where
func :: f -> (a->b)
instance F a b (a->b) where
func = id
-- Class of expressions that evaluate to a function a->b
class (Eval f e, F a b f) => EvalF a b f e | e -> f, e -> a, e ->b
-- This requires UndecidaableInstances, but should be decidable
instance (Eval f e, F a b f) => EvalF a b f e
-- This requires UndecidaableInstances, but should be decidable
instance (EvalF a b f ef, Eval a ea) => Eval b (App ef ea) where
eval (App ef ea) = func (eval ef) $ eval ea
编译器在对test1求值时无法推断出数字的类型,所以示例需要额外的、繁琐的类型注解:
-- Example code
instance Show (a->b) where
show _ = "->"
test1 :: (Num n) => App (Atom (n->n)) (Atom n)
test1 = App (Atom (+1)) (Atom 3)
test2 = App (App (Atom ((+) :: Int -> Int -> Int)) (Atom (1 :: Int))) (Atom (3 :: Int))
test3 = App (Atom reverse) (Atom "abc")
main = do
print test1
print $ eval (test1 :: App (Atom (Int->Int)) (Atom Int))
putStrLn ""
print test2
print $ eval test2
putStrLn ""
print test3
print $ eval test3
尝试使用不兼容类型的应用程序评估抽象语法树
-- This still type checks
appStringToString = App (Atom "def") (Atom "abc")
-- But this won't
fails = eval appStringToString
编译失败
No instance for (F [Char] a0 [Char]) arising from a use of `eval'
Possible fix: add an instance declaration for (F [Char] a0 [Char])
In the expression: eval appStringToString
In an equation for `fails': fails = eval appStringToString