[英]Why doesn't this this instance declaration work in Haskell?
我正在尝试使数据类型代表Haskell中的抽象语法树。 我一直在阅读有关免费monad,GADT,混合Typeable / Dynamic以及其他可能解决此问题的方法的各种知识,但我主要是想知道我提出的解决方案是否可行(即使需要扩展),以及如果没有,为什么不呢?
本质上,我有一个类似的类型类:
data AST a b = AST a b
data Atom a = Atom a
class Eval e where
eval :: e a -> a
instance Eval Atom where
eval (Atom a) = a
然后对于AST的Eval实例,我想要这样的东西:
instance Eval (e1 (a -> b)), Eval (e2 a) => Eval (AST b) where
eval (AST f x) = eval f $ eval x
在英语中,作为Eval b
的实例表示可以将某物评估为b
,并且我希望AST作为Eval b
一个实例, Eval b
是其第一个参数可以评估为a -> b
并且第二个参数可以为评价为一个a
。 因此,想法是AST不一定是类型安全的,但是如果它不是类型安全的,则它不是Eval的实例,因此,如果您在非类型安全的AST上具有调用eval的代码,则它将无法编译。 甚至只是做一个像
typecheck :: Eval e => e a -> e a
typecheck = id
作为静态测试。 考虑到我在表示AST时遇到的所有其他情况,我假设这是不可能的,但是为什么不呢? 任何扩展都可以使这个基本思想可行吗? 对我正在做的事情的主要要求是,我需要能够在运行时生成AST,将AST转换为文本并随后对其进行检查(因此,我显然需要一些NamedFunction数据类型),并且我需要能够可以轻松地用它来表示任意的无点Haskell表达式(因此,任何可以由任意但有限的原始函数/值集合构成的任何东西,但不能包含let / where / case / lambdas / etc)。
编辑:我觉得问题的一部分是在上述情况下推断AST应该是Eval b
一个实例。 在Atom
的情况下,我只是声明instance Eval Atom where
,但是对于AST如果我有instance ... => Eval AST where
那我并没有真正指出它是一个Eval b
,只是一个Eval
,它赢了无论如何,在没有AST
另一个参数的情况下仍然无法进行编译,因此问题可能出在这里,但我仍然不确定是否有办法告诉编译器我真正追求的是什么。
您可以使用TypeFamilies
和FlexibleContexts
或FunctionalDependencies
, FlexibleInstances
和MultiParamTypeClasses
完成与发布的问题类似的操作。
TypeFamilies
和FunctionalDependencies
都是类型检查器可以完全基于另一种类型确定一种类型的机制。 这些将为我们解决两个问题。 您遇到的第一个问题是,在类的实例声明中,我们无法从类型a->b
中获得类型a
和b
。 第二个问题是,我们需要能够根据表达式的类型来判断其计算结果的类型。 TypeFamilies
允许我们创建可以解构类型的类型级别的函数。 FunctionalDependencies
允许我们声明一个类型可以从另一个类型恢复。
编辑: TypeFamilies
提供了比FunctionalDependencies
更好的解决方案。
TypeFamilies
使用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
不能应用于Int
。
-- 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
捕获并保留由所需类型的信息Eval
实例App
构建时App
。 用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")
即使为NoMonomorphismRestriction
,为FunctionalDependencies
添加类似的FunctionalDependencies
也不能解决类型推断问题。 这使TypeFamilies
明显胜过FunctionalDependencies
。
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
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.