【问题标题】:Are there any static duck-typed languages?有没有静态鸭子类型的语言?
【发布时间】:2010-09-22 07:00:48
【问题描述】:

Can I specify interfaces when I declare a member?

在考虑了这个问题一段时间后,我突然想到静态鸭子类型的语言可能真的有效。为什么预定义的类不能在编译时绑定到接口?示例:

public interface IMyInterface
{
  public void MyMethod();
}

public class MyClass  //Does not explicitly implement IMyInterface
{
  public void MyMethod()  //But contains a compatible method definition
  {
    Console.WriteLine("Hello, world!");
  }
}

...

public void CallMyMethod(IMyInterface m)
{
  m.MyMethod();
}

...

MyClass obj = new MyClass();
CallMyMethod(obj);     // Automatically recognize that MyClass "fits" 
                       // MyInterface, and force a type-cast.

您知道支持这种功能的任何语言吗?它对 Java 或 C# 有帮助吗?它在某些方面存在根本缺陷吗?我知道你可以继承 MyClass 并实现接口或使用适配器设计模式来完成同样的事情,但这些方法看起来像是不必要的样板代码。

【问题讨论】:

    标签: class interface language-design duck-typing static-typing


    【解决方案1】:

    这个问题的全新答案Go has exactly this feature。我认为它真的很酷也很聪明(尽管我很想看看它在现实生活中的表现如何),并且对它的思考表示赞赏。

    据记录 in the official documentation (as part of the Tour of Go, with example code):

    接口是隐式实现的

    一个类型通过实现它的方法来实现一个接口。有 没有明确的意图声明,没有“implements”关键字。

    隐式接口将接口的定义与其 实现,然后可以出现在任何包中 预先安排。

    【讨论】:

    • 您能否链接到解释其工作原理的 Go 文档的实际部分?而且,更好的是,在这里包括一个例子?
    • 如你所愿(但只是链接到示例)
    【解决方案2】:

    在 C++ 中使用模板怎么样?

    class IMyInterface  // Inheritance from this is optional
    {
    public:
      virtual void MyMethod() = 0;
    }
    
    class MyClass  // Does not explicitly implement IMyInterface
    {
    public:
      void MyMethod()  // But contains a compatible method definition
      {
        std::cout << "Hello, world!" "\n";
      }
    }
    
    template<typename MyInterface>
    void CallMyMethod(MyInterface& m)
    {
      m.MyMethod();  // instantiation succeeds iff MyInterface has MyMethod
    }
    
    MyClass obj;
    CallMyMethod(obj);     // Automatically generate code with MyClass as 
                           // MyInterface
    

    我还没有真正编译过这段代码,但我相信它是可行的,并且是对原始提议(但不工作)代码的一个相当简单的 C++ 化。

    【讨论】:

    • 有人确实提到了 C++ 模板,但我认为该答案已被删除。无论如何,模板基本上只是高级宏。为您传递给它的每种不同类型创建一个新的“CallMyMethod”,因此它不是真正的类型推断。
    • 公平地说,原发帖人根本没有提到类型推断本身。我只是稍微调整了他的代码,并证明它可以在 C++ 中按原样工作。
    • 我无法理解为什么人们还认为C++模板只是高级宏,它是一个图灵完备的编程机制,而不是一个文本替换工具。
    • @swegi real(句法)宏也是图灵完备的。甚至更多。例如,在 Clojure 中,您几乎可以在编译时执行所有操作。
    • 除其他外,C++ 模板甚至不是“高级”宏。它们实际上充其量只是非常基本的宏。任何一种语言(或元语言)都不能被视为高级编程环境。
    【解决方案3】:

    根据定义,静态类型语言在编译时检查类型,而不是运行时。上述系统的一个明显问题是编译器会在程序编译时检查类型,而不是在运行时。

    现在,您可以在编译器中构建更多智能,以便它可以派生类型,而不是让程序员显式声明类型;编译器可能会看到 MyClass 实现了 MyMethod() 方法,并相应地处理这种情况,无需显式声明接口(如您所建议的那样)。这样的编译器可以利用类型推断,例如Hindley-Milner

    当然,像 Haskell 之类的一些静态类型语言已经做了一些与您的建议类似的事情类似; Haskell 编译器能够推断类型(大部分时间),而无需显式声明它们。但是很明显,Java/C# 没有这个能力。

    【讨论】:

    • 谢谢。我大约一周前才开始学习 Haskell,到目前为止它看起来很酷。不过,这有点学习曲线——以前从未真正接触过函数式语言。无论如何,它的类型推断系统很像我所指的。
    • C# 似乎有类型推断。例如,这篇文章谈到了它。 developer.com/net/csharp/article.php/3601646/…
    【解决方案4】:

    我不明白这一点。为什么不明确说明该类实​​现了接口并完成了它?实现接口是告诉其他程序员这个类应该按照接口定义的方式运行。仅在方法上具有相同的名称和签名并不能保证设计者的意图是使用该方法执行类似的操作。可能是这样,但为什么要把它留给解释(和滥用)呢?

    您可以在动态语言中成功“摆脱”这一点的原因更多地与 TDD 有关,而不是与语言本身有关。在我看来,如果该语言能够为使用/查看代码的其他人提供此类指导,那么您应该使用它。它实际上提高了清晰度,值得添加几个额外的字符。在您无权执行此操作的情况下,适配器的作用与显式声明接口与其他类的关联方式相同。

    【讨论】:

    • 我同意,这就是界面的意义,用来描述事物是什么。尽可能使用它,它不是样板代码,而是一种很好的做法。
    • 为什么这种模式在动态语言中很成功,但在静态语言中却被认为是不好的做法? TDD 与动态类型所独有的有什么关系?正如@mipadi 提到的,为什么 OO 语言不能支持一种 Haskell 风格的类型推断系统?
    • 从我有限的理解来看,Java 泛型只是一种类似于鸭子类型的方法。
    • @James:抱歉,没有。 Java 泛型相当于 Object 类型的自动转换。您不能编写如上所示的代码。
    • 如果代码已经编译,而你没有源代码,因为它不是你的怎么办?你将如何让它继承一个接口?你不能指望别人总是做出正确的决定
    【解决方案5】:

    F# 支持静态鸭子类型,但有一个问题:您必须使用成员约束。详细信息可在此blog entry 中找到。

    引用博客中的示例:

    let inline speak (a: ^a) =
        let x = (^a : (member speak: unit -> string) (a))
        printfn "It said: %s" x
        let y = (^a : (member talk: unit -> string) (a))
        printfn "Then it said %s" y
    
    type duck() =
        member x.speak() = "quack"
        member x.talk() = "quackity quack"
    type dog() =
        member x.speak() = "woof"
        member x.talk() = "arrrr"
    
    let x = new duck()
    let y = new dog()
    speak x
    speak y
    

    【讨论】:

      【解决方案6】:

      ML 系列中的大多数语言都支持具有推理和约束类型方案的结构类型,这是令人讨厌的语言设计器术语,看起来很可能就是您所说的“静态鸭子-在原始问题中输入”。

      这个家族中比较流行的语言包括:Haskell、Objective Caml、F# 和 Scala。当然,与您的示例最匹配的是Objective Caml。这是您的示例的翻译:

      open Printf
      
      class type iMyInterface = object
        method myMethod: unit
      end
      
      class myClass = object
        method myMethod = printf "Hello, world!"
      end
      
      let callMyMethod: #iMyInterface -> unit = fun m -> m#myMethod
      
      let myClass = new myClass
      
      callMyMethod myClass
      

      注意:您使用的某些名称必须更改以符合 OCaml 的标识符大小写语义概念,否则,这是一个非常简单的翻译。

      另外,值得注意的是,callMyMethod 函数中的类型注释和iMyInterface 类类型的定义都不是绝对必要的。 Objective Caml 可以推断您示例中的所有内容,而无需任何类型声明。

      【讨论】:

        【解决方案7】:

        TypeScript!

        好吧,好吧...所以它是一个 javascript 超集,可能不构成“语言”,但这种静态鸭子类型在 TypeScript 中至关重要。

        【讨论】:

        • 我认为 Typescript 使用了结构类型(公平地说,它与鸭子类型非常相似,只是更严格)
        【解决方案8】:

        Boo 绝对是一种静态鸭式语言:http://boo.codehaus.org/Duck+Typing

        摘录:

        Boo 是一种静态类型语言, 像 Java 或 C#。这意味着你的嘘声 应用程序的运行速度将与 那些用其他静态类型编码的 .NET 或 Mono 的语言。但是使用 有时是静态类型的语言 将你限制在一个僵化和 冗长的编码风格,带有 有时必要的类型声明 (如“x as int”,但这不是 由于 boo 的类型,经常需要 推理),有时是必要的 类型转换(请参阅转换类型)。嘘 支持类型推断和 最终泛型在这里有所帮助,但是...

        有时候放弃是合适的 静电提供的安全网 打字。也许你只是想探索 无需过多担心的 API 方法签名或者你可能是 创建与外部对话的代码 COM 对象等组件。任何一个 选择不应该是你的方式 我的。

        除了普通类型,如 object, int, string...boo 有一个 称为“鸭子”的特殊类型。术语 受到红宝石编程的启发 语言的鸭子打字功能(“如果 像鸭子一样走路,像鸭子一样嘎嘎叫 鸭子,一定是鸭子”)。

        【讨论】:

        • 带有鸭子类型的静态语言与静态鸭子类型不同。现有的 VB 和即将推出的 C# 4.0 都具有静态类型和动态/后期绑定/鸭子类型
        • 我并不是指静态和动态类型的混合(这似乎是您所指的,教程)。我指的是类型推断——编译器能够立即看到 MyClass 和 IMyInterface 是兼容的,而不需要程序员明确说明关系。
        • 我的错。 Boo 不会为你这样做。
        【解决方案9】:

        新版本的 C++ 朝着静态鸭子类型的方向发展。你可以有一天(今天?)写这样的东西:

        auto plus(auto x, auto y){
            return x+y;
        }
        

        如果x+y没有匹配的函数调用,它将无法编译。

        至于你的批评:

        为您传递给它的每种不同类型创建一个新的“CallMyMethod”,因此它并不是真正的类型推断。

        但它是类型推断(你可以说foo(bar),其中foo 是一个模板函数),并且具有相同的效果,除了它更省时并且在编译代码中占用更多空间。

        否则,您将不得不在运行时查找该方法。您必须找到一个名称,然后检查该名称是否包含具有正确参数的方法。

        或者,您必须存储所有关于匹配接口的信息,并查看与接口匹配的每个类,然后自动添加该接口。

        在任何一种情况下,这都允许您隐式地意外破坏类层次结构,这对新功能不利,因为它违背了 C#/Java 程序员的习惯。使用 C++ 模板,您已经知道自己处于雷区(而且他们还在添加功能(“概念”)以允许限制模板参数)。

        【讨论】:

          【解决方案10】:

          Scala 中的结构类型会做这样的事情。

          Statically Checked “Duck Typing” in Scala

          【讨论】:

            【解决方案11】:

            Crystal 是一种静态鸭类型语言。 Example:

            def add(x, y)
              x + y
            end
            
            add(true, false)
            

            add 的调用导致此编译错误:

            Error in foo.cr:6: instantiating 'add(Bool, Bool)'
            
            add(true, false)
            ^~~
            
            in foo.cr:2: undefined method '+' for Bool
            
              x + y
                ^
            

            【讨论】:

              【解决方案12】:

              Visual Basic 9 的预发布设计支持使用动态接口的静态鸭子类型,但为了按时发布,他们删除了功能*

              【讨论】:

                【解决方案13】:

                D (http://dlang.org) 是一种静态编译语言,通过 wrap() 和 unwrap() (http://dlang.org/phobos-prerelease/std_typecons.html#.unwrap) 提供鸭子类型。

                【讨论】:

                  【解决方案14】:
                  【解决方案15】:

                  在我的编程语言Heron 的最新版本中,它通过称为as 的结构子类型强制运算符支持类似的东西。所以而不是:

                  MyClass obj = new MyClass();
                  CallMyMethod(obj);
                  

                  你会写:

                  MyClass obj = new MyClass();
                  CallMyMethod(obj as IMyInterface);
                  

                  就像在您的示例中一样,在这种情况下,MyClass 不必显式实现 IMyInterface,但如果确实如此,则转换可能会隐式发生,并且可以省略 as 运算符。

                  我在this article 中写了更多关于我称之为显式结构子类型的技术。

                  【讨论】:

                    猜你喜欢
                    • 2011-03-23
                    • 1970-01-01
                    • 2011-02-25
                    • 2011-12-30
                    • 1970-01-01
                    • 1970-01-01
                    • 2016-09-26
                    相关资源
                    最近更新 更多