簡體   English   中英

為什么這個實例聲明在Haskell中不起作用?

[英]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另一個參數的情況下仍然無法進行編譯,因此問題可能出在這里,但我仍然不確定是否有辦法告訴編譯器我真正追求的是什么。

您可以使用TypeFamiliesFlexibleContextsFunctionalDependenciesFlexibleInstancesMultiParamTypeClasses完成與發布的問題類似的操作。

TypeFamiliesFunctionalDependencies都是類型檢查器可以完全基於另一種類型確定一種類型的機制。 這些將為我們解決兩個問題。 您遇到的第一個問題是,在類的實例聲明中,我們無法從類型a->b中獲得類型ab 第二個問題是,我們需要能夠根據表達式的類型來判斷其計算結果的類型。 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM