簡體   English   中英

建立AST的非合法Monoid實例不被視為有害?

[英]non-lawful Monoid instances for building up AST not considered harmful?

我已經看到一個數據類型定義如下,具有相應的Monoid實例:

data Foo where
  FooEmpty :: String -> Foo
  FooAppend :: Foo -> Foo -> Foo

-- | Create a 'Foo' with a specific 'String'.
foo :: String -> Foo
foo = FooEmpty

instance Monoid Foo where
  mempty :: Foo
  mempty = FooEmpty ""

  mappend :: Foo -> Foo -> Foo
  mappend = FooAppend

你可以找到一個完整的代碼要點 Github上。

這就是Foo使用方式:

exampleFoo :: Foo
exampleFoo =
  (foo "hello" <> foo " reallylongstringthatislong") <>
  (foo " world" <> mempty)

exampleFoo最終成為一個如下所示的樹:

FooAppend
  (FooAppend
    (FooEmpty "hello")
    (FooEmpty " reallylongstringthatislong"))
  (FooAppend
    (FooEmpty " world")
    (FooEmpty ""))

Foo可用於將Monoid操作( memptymappend )的序列轉換為AST。 然后可以將此AST解釋為其他一些Monoid

例如,這里是將Foo轉換為String ,以確保字符串追加將以最佳方式發生:

fooInterp :: Foo -> String
fooInterp = go ""
  where
    go :: String -> Foo -> String
    go accum (FooEmpty str) = str ++ accum
    go accum (FooAppend foo1 foo2) = go (go accum foo2) foo1

這真的很好。 我們可以確保String附加將以正確的順序發生。 我們不必擔心與左相關的mappend

但是,令我擔心的一件事是, FooMonoid實例不是合法的Monoid實例。

例如,采取第一個Monoid法則:

mappend mempty x = x

如果我們讓xFooEmpty "hello" ,我們得到以下結果:

mappend mempty (FooEmpty "hello") = FooEmpty "hello"
mappend (FooEmpty "") (FooEmpty "hello") = FooEmpty "hello"  -- replace mempty with its def
FooAppend (FooEmpty "") (FooEmpty "hello") = FooEmpty "hello" -- replace mappend with its def

你可以看到FooAppend (FooEmpty "") (FooEmpty "hello")不等於FooEmpty "hello" 其他Monoid法律也因類似原因而不成立。

Haskellers通常反對非合法的情況。 但我覺得這是一個特例。 我們只是試圖構建一個可以解釋為另一個Monoid Foo的情況下,我們可以確保在fooInterp函數中Monoid定律適用於String

  1. 是否可以使用這些類型的非合法實例來構建AST?
  2. 使用這些類型的非合法實例時是否需要注意哪些具體問題?
  3. 是否有另一種方法來編寫使用類似Foo代碼? mappend方法能夠解釋mappend結構,而不是直接在類型上使用mappend

在類似的問題上引用這個答案

您可以從另一種觀點來考慮它:法律(a <> b)<> c = a <>(b <> c)沒有指定應該使用哪個等式,即=表示什么特定關系。 從結構上的平等來考慮它是很自然的,但請注意,很少有類型規則實際上支持結構相等(例如,嘗試證明fmap的fmap id = id而不是forall x .fmap id x = id x) 。

例如,如果你不導出Foo的構造函數,並且只導出從用戶的角度來看,就好像Foo是一個幺半群的函數,那么它幾乎沒有問題。 但大部分時間都有可能提出一種結構上是幺半群的表示,在實踐中足夠好,但可能不那么普遍(下面,你不能在事后任意重新關聯,因為解釋與建構混合在一起)。

type Foo = Endo String

foo :: String -> Foo
foo s = Endo (s <>)

unFoo :: Foo -> String
unFoo (Endo f) = f ""

Data.Monoid.Endo


這是另一個SO問題,其中首先考慮非結構性結構( Alternative )。

這將出現在大多數非平凡的數據結構中。 我能想到的唯一例外是(某些)類似於trie的結構。

  1. 平衡樹數據結構允許多數值的多個平衡。 AVL樹,紅黑樹,B樹,2-3指樹等都是如此。

  2. 圍繞“重建”設計的數據結構,例如Hood-Melville隊列,允許在代表大多數值的結構內進行可變數量的重復。

  3. 實現有效優先級隊列的數據結構允許多個元素排列。

  4. 散列表將根據發生沖突的時間以不同方式排列元素。

如果沒有這種靈活性,這些結構都不能漸近有效。 然而,靈活性總是在最嚴格的解釋下打破法律。 在Haskell中,處理此問題的唯一好方法是使用模塊系統確保沒有人能夠檢測到問題。 在實驗依賴型語言中,研究人員一直致力於觀察類型理論和同倫類型理論之類的事情,以尋找更好的方式來談論“平等”,但這項研究遠未變得實用。

是否可以使用這些類型的非合法實例來構建AST?

這是一個意見問題。 (我堅定地站在'永無止境'陣營。)

使用這些類型的非合法實例時是否需要注意哪些具體問題?

  • 對潛在用戶和未來維護者的認知負擔
  • 潛在的錯誤,因為我們在一個根據破壞的法律作出假設的地方使用該類型

編輯以回答評論中的問題:

您是否能夠提出具體的例子來說明它如何增加用戶的認知負擔?

想象一下,如果有人在C中這樣做,你會有多煩惱:

 // limit all while loops to 10 iterations
 #define while(exp) for(int i = 0; (exp) && i < 10; ++i)

現在我們必須跟蹤這個偽定義及其含義的范圍。 這是一個非Haskell的例子,但我認為原理是一樣的。 我們不應該期望while的語義在特定的源文件中是不同的,就像我們不應該期望Monoid的語義對於特定的數據類型不同。

當我們說某事是X時,它應該是X,因為人們理解X的語義。這里的原則是不要為熟悉的概念創建例外

我認為首先使用合法抽象(如monoid)的重點是減輕程序員學習和記憶無數不同語義的需要。 因此,我們創建的每個例外都會破壞這一目標。 事實上,它使情況變得更糟; 我們必須記住抽象,並記住所有異常。 (順便說一句,我很欽佩,但同情那些學習英語作為第二語言的人。)

或者它如何導致潛在的錯誤?

一些圖書館:

-- instances of this class must have property P
class AbidesByP where
   ...

-- foo relies on the property P
foo :: AbidesByP a => a -> Result
foo a = ... 

我的代碼:

data MyData = ...

-- note: AbidesByP's are suppose to have property P, but this one doesn't
instance AbidesByP MyData where
   ...

其他一些程序員(或幾個月后我):

doSomethingWithMyData :: MyData -> SomeResult
doSomethingWithMyData x = let ...
                              ...
                              ...
                              r = foo x      -- potential bug
                              ...
                              ...
                           in ...

是否有另一種方法來編寫使用類似Foo的代碼?

我可能只是使用構造函數來構造:

(foo "hello" `FooAppend` foo " reallylongstringthatislong") `FooAppend` (foo " world" `FooAppend` foo "")

或者做一個運營商:

(<++>) = FooAppend

(foo "hello" <++> foo " reallylongstringthatislong") <++> (foo " world" <++> foo "")

暫無
暫無

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

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