haskell - 建立 AST 的非法 Monoid 实例不被认为是有害的?

标签 haskell abstract-syntax-tree interpreter typeclass monoids

我见过如下定义的数据类型,对应的 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

您可以在 gist 中找到完整代码在 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 s。

然而,让我担心的一件事是 Monoid Foo 的实例不合法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 的情况下,我们可以确保 Monoid法律适用于 StringfooInterp功能。
  • 使用这些类型的非法实例来构建 AST 是否可行?
  • 在使用这些类型的非法实例时,是否有任何具体问题需要注意?
  • 是否有另一种方法来编写使用类似 Foo 的代码? ?某种方式可以解释单曲面结构,而不是使用 mappend直接在类型上?
  • 最佳答案

    引用 this answer on a similar question :

    You can think of it from this alternative point of view: the law (a <> b) <> c = a <> (b <> c) doesn't specify which equality should be used, i.e. what specific relation the = denotes. It is natural to think of it in terms of structural equality, but note that very few typeclass laws actually hold up to structural equality (e.g. try proving fmap id = id for [] as opposed to 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 )

    Here is another SO question where a non-structural structure ( Alternative ) is considered at first.

    关于haskell - 建立 AST 的非法 Monoid 实例不被认为是有害的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45884762/

    相关文章:

    c++ - 在 C++ 中复制 Haskell 的返回类型重载(通过类型类)

    haskell - Haskell 中的二叉搜索树实现

    ruby - 如何查看 Ruby 中类层次结构中方法的定义和重写位置?

    c# - 尝试将 LOX 语言实现从 Crafting Interpreter 的书中移植到 C# 时遇到泛型问题

    compiler-construction - 写入 REPL : where to start?

    haskell - 如何使用函数的幺半群实例?

    scala - 如何表达函数类型?

    c - 设计一个类似 C 的编译器

    parsing - 尝试理解词法分析器、解析树和语法树

    windows - 为什么对象的 id 会根据 python shell 中的行而改变