簡體   English   中英

多態返回類型,接口,回調?

[英]Polymorphic return type, interfaces, callbacks?

假設Goo是我的類型類,通常聲稱它是與C ++,Java或C#等語言等效的接口:

class Goo goo where ham :: goo -> String

data Hoo = Hoo
instance Goo Hoo where ham _ = "Hoo!"
                       mustard _ = "Oh oh."

data Yoo = Yoo
instance Goo Yoo where ham _ = "Yoo!"
                       mustard _ = "Whew"

但我不能回復Goo

paak :: (Goo goo) => String -> goo
paak g = (Yoo)

-- Could not deduce (goo ~ Yoo)
-- from the context (Goo goo)
--  bound by the type signature for paak :: Goo goo => String -> goo
--  at frob.hs:13:1-14
--  `goo' is a rigid type variable bound by
--        the type signature for paak :: Goo goo => String -> goo
--        at frob.hs:13:1
-- In the expression: (Yoo)
-- In an equation for `paak': paak g = (Yoo)

我找到了這個啟發性的陳述,它解釋了為什么:

類型paak :: (Goo goo) => String -> goo並不意味着該函數可能返回它想要的任何Goo 這意味着該函數將返回用戶想要的Goo

(從sepp2k的答案音譯到這里

但是,我怎么能返回或存儲滿足Goo約束的東西,但可以是HooYooMooBoo或任何其他Goo

我只是在自己的編程背景中糾纏不清,需要完全不同的思考,比如求助於類似C的界面:

data WhewIamAGoo = WhewIamAGoo {
    ham' :: String
    mustard' :: String
}

paak :: String -> WhewIamAGoo
paak g = let yoo = Yoo 
         in WhewIamAGoo { ham' = ham yoo
                          mustard' = mustard ham
                        }

但這似乎很尷尬。

在我的具體情況下,我Goo這樣使用Goo

let x = someGoo ....
in ham x ++ mustard x

即呼叫者不應該知道所有的Yoo和諸如此類的東西。


編輯:澄清:我正在尋找Haskell程序員在這種情況下的方式。 如何以慣用的方式處理它?

有兩種解決這個問題的方法我認為是慣用的Haskell:

  1. 代數數據類型

     data Goo = Hoo | Yoo ham Hoo = "Hoo!" ham Yoo = "Yoo!" mustard Hoo = "Oh oh." mustard Yoo = "Whew" 

    親:輕松添加新操作
    Con:添加新的“類型”可能需要修改許多現有功能

  2. 記錄支持的操作

     data Goo = Goo { ham :: String, mustard :: String } hoo = Goo { ham = "Hoo!", mustard = "Oh oh." } yoo = Goo { ham = "Yoo!", mustard = "Whew" } 

    親:輕松添加新的“類型”
    Con:添加新操作可能需要修改許多現有功能

你當然可以混合搭配這些。 一旦你習慣於考慮函數,數據和組合而不是接口,實現和繼承,這些在大多數情況下都是足夠好的。

類型類是為重載而設計的。 使用它們來模擬Haskell中面向對象的編程通常是一個錯誤。

類型類似於Java風格的接口,但是你並沒有像使用接口那樣使用它們,所以它不是學習它們的好方法。

接口一種類型(因為OO語言具有子類型,因此其他類型可以是接口的子類型,這是您完成任何操作的方式)。 所有類型在Haskell中都是不相交的,因此類型類不是類型。 它是一類型(實例聲明是您聲明集合成員的位置)。 試着用這種方式來思考它們。 它使得簽名類型的正確讀取更加自然( String -> a表示“接受一個String並返回您想要的任何類型的值”,而SomeTypeClass a => String -> a表示“接受一個String並返回一個值”您想要的任何類型的SomeTypeClass “)的成員。

現在你無法以你想要的方式做你想做的事,但我不確定你為什么需要按你想要的方式去做。 為什么paak只能有類型String -> Yoo

你說你正在嘗試做類似的事情:

let x = someGoo ....
in ham x ++ mustard x

如果someGoo ...paak "dummy string" ,那么x將是Yoo類型。 但是YooGoo的成員,所以你可以在上面調用像hammustard這樣的Goo方法。 如果您稍后更改paak以返回不同Goo類型的值,那么編譯器將告訴您使用任何Yoo特定功能的所有位置,並且愉快地接受未更改的任何調用paak但僅使用Goo功能的位置。

為什么你需要輸入“某些未知類型,這是Goo的成員”? 從根本上說, paak調用者不會Goo以任何類型操作,他們只能根據paak實際返回的內容進行操作,這是一個Yoo

您有一些對具體類型進行操作的函數,它們可以調用這些具體類型上的函數以及來自類型類的函數,這些函數類型是具體類型的成員。 或者你有一些函數可以在任何類型上運行,這些類型是某個類型類的成員,在這種情況下你可以調用的函數是在類類中的任何類型上工作的函數。

首先,通常沒有必要! 你的WhenIAmGoo方法很好; 因為Haskell是懶惰的,它沒有任何真正的缺點,但往往更清晰。

但它仍然可能:

{-# LANGUAGE RankNTypes              #-}

paak' :: String -> (forall goo . Goo goo => goo -> r) -> r
paak' g f = f Yoo

看起來很復雜

要理解這個問題,你需要知道Haskell的基於Hindley-Milner的類型系統與C ++和Java的工作方式有着根本的不同。 在這些語言中,正如您所知,多態性基本上是一種有限的動態類型:如果您傳遞一個帶有“接口類型”的對象,您實際上是在對象周圍傳遞一個包裝器,它知道接口方法是如何實現的在里面。

在Haskell中,它是不同的。 明確編寫的多態簽名如下所示:

paak :: { forall goo . (Goo goo) } => {String -> goo}

這意味着,該函數實際上存在一個完全獨立的額外參數,即“字典參數”。 這就是用於訪問界面的內容。 因為這確實是傳遞函數的參數,所以函數顯然無法選擇它。

要將字典函數中傳出 ,你需要像我上面那樣使用這些邪惡的技巧:你不直接返回多態結果,而是要求調用者“你將如何使用它?但是請注意,我可以不要告訴你將會得到什么樣的具體類型......“即,你需要它們給你自己一個多態函數,然后你可以插入你選擇的具體類型。

這樣的功能可以這樣使用:

myHamAndMustard = paak' arg (\myGoo -> ham myGoo ++ mustard myGoo )

這不是很好。 同樣,通常更好的方法是為所有可能的輸出提供一個透明的非多態容器。 很多時候,這仍然不是最佳的; 你可能已經從太多的OO角度接近了整個問題。

根據目前提供的信息,C風格的界面(==帶功能的記錄)似乎是要走的路。

但是,為了使其更甜美,添加一個智能構造函數並使AnyGoo成為Goo一個實例:

data AnyGoo = AnyGoo {
    ham' :: String 
}
instance Goo AnyGoo where
    ham = ham' 


anyGoo :: (Goo goo) => goo -> AnyGoo 
anyGoo goo = AnyGoo { ham' = ham goo }

那么你可以統一稱為所有Goo ham

> let x = anyGoo Hoo
> let y = anyGoo Yoo
> ham x
"Hoo!"
> ham y
"Yoo!"

然后paak將返回AnyGoo而不是Goo

paak :: String -> AnyGoo
paak _ = anyGoo Yoo

但是,你(我)將再次傳遞某種類型,因此可以更好地回歸hammar的建議。

我想支持回答@phresnel,但我想補充一些一般性的想法。

你應該理解的是,使用那個簽名paak :: (Goo goo) => String -> goo你試圖用類型系統來控制你未來的評估。 類型僅在編譯時存在,如C#,C ++和其他OOP偏向語言。 為了在運行時以不同的方式表示類型,這些語言使用虛函數表等。在Haskell中,你應該做同樣的事情並將其包裝在某些東西中。

data AnyGoo = forall a . (Goo a) => AnyGoo a

paak :: String -> AnyGoo
paak g = AnyGoo Yoo

在這種情況下,編譯器(在ExistentialQuantification和其他東西的幫助下)具有多個構造函數(比如具有實現一個接口的類的多個構造函數),用於AnyGoo,對於在Goo類型類中具有實例的任何類型都是開放的。

但在這種情況下,它足以使用數據值(如虛函數)。

data Goo = Goo { ham :: String }

-- ham :: Goo -> String
yoo = Goo { ham = "Yoo!" }
paak :: String -> AnyGoo
paak g = Goo yoo

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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