C#开发人员-我尝试过Lisps,但我不明白。


37

经过几个月的关于Lisp,CL和Clojure的学习和使用,我仍然看不到有什么理由代替C#编写任何令人信服的理由。

我真的很想提出一些令人信服的理由,或者有人要指出我缺少的东西确实很大

Lisp的优势(根据我的研究):

  • 紧凑,富有表现力的符号-不仅是C#,是的...但是我似乎也可以用C#来表达那些想法。
  • 对函数式编程的隐式支持-具有LINQ扩展方法的C#:
    • mapcar = .Select(lambda)
    • mapcan = .Select(lambda).Aggregate((a,b)=> a.Union(b))
    • 汽车/第一= .First()
    • cdr / rest = .Skip(1)....等
  • Lambda和高阶函数支持-C#具有此功能,并且语法可以说更简单:
    • “(lambda(x)(body))”与“ x =>(body)”
    • Clojure中的“#(”与“%”,“%1”,“%2”很好
  • 与对象分离的方法分派-C#通过扩展方法具有此功能
  • 多方法分派-C#本身没有这种方法,但是我可以在几个小时内将其实现为函数调用
  • 代码就是数据(和宏)-也许我没有“获得”宏,但是我没有看到一个不能将宏的概念实现为函数的示例。它不会改变“语言”,但我不确定这是一种优势
  • DSL-只能通过功能组合来实现...但是可以工作
  • 无类型的“探索性”编程-对于结构/类,C#的自动属性和“对象”都可以很好地工作,并且您可以轻松地升级为更强大的类型
  • 在非Windows硬件上运行-是吗?在大学以外,我只认识一个不在家中运行Windows或至少在* nix / Mac上运行Windows VM的人。(再说一次,也许这比我想像的要重要,我刚刚被洗脑了……)
  • 自底向上设计的REPL-好的,我承认这确实非常好,我在C#中错过了它。

我在Lisp中缺少的东西(由于C#、. NET,Visual Studio,Resharper的混合使用):

  • 命名空间。即使使用静态方法,我也喜欢将它们绑定到“类”以对它们的上下文进行分类(Clojure似乎有这个,CL似乎没有。)
  • 强大的编译和设计时支持
    • 类型系统允许我确定我传递的数据结构的“正确性”
    • 拼写错误的内容会实时下划线;我不必等到运行时就知道
    • 建议自动进行代码改进(例如使用FP方法代替命令式方法)
  • GUI开发工具:WinForms和WPF(我知道Clojure可以访问Java GUI库,但是对我来说它们是完全陌生的。)
  • GUI调试工具:断点,插入,逐步,值检查器(文本,xml,自定义),监视,按线程调试,条件断点,调用堆栈窗口,可以跳转到任意级别的代码在堆栈中
    • (为公平起见,我在Emacs + Slime方面的工作似乎提供了其中的一部分,但我偏爱VS GUI驱动的方法)

我非常喜欢Lisp周围的炒作,因此给了我一个机会。

但是,在Lisp中我能做的还是在C#中做不到的?在C#中可能更详细,但是我也有自动完成功能。

我想念什么?为什么要使用Clojure / CL?


4
由于您已经以一种功能风格进行编程,并且听起来您非常依赖Windows基础结构,所以我看不到任何有说服力的理由让您从C#更改。我认识的大多数人都使用Mac或Linux,因此,很明显,这很大程度上取决于您所使用的人群(自3.1起,我已经使用过Windows的每个版本,但我仍然通常更喜欢使用跨平台软件和* nix系统...但是我不做任何本机GUI工作,也不做任何服务器和Web UI东西)
Sean Corfield

2
您可以看一下《 Perl之前的猪》。听这是一个有趣的演讲,并给出了一个宏适用的简单示例。
Matthias Benkard,2011年

4
“我还没有看到一个无法将宏的概念实现为函数的示例”-我想看看您将如何编写ANDOR作为函数。可以做到这一点(给定的LAMBDA,它也是一个宏),但是我看不出有一种明显的方法不会完全糟透了。

1
“命名空间。...Clojure似乎有这个,CL似乎没有” –您能更详细地说明您要寻找的内容吗?我没有真正使用过Clojure,但是它的命名空间看起来很像CL的软件包。

4
Jonathan:CL使用:(或::用于未导出的符号)在包中命名符号,例如,此处的示例将使用http:sessionchat:session

Answers:


23

宏使您可以编写编译器而无需使用语言。像C#这样的语言中的DSL通常相当于一个运行时解释器。根据您的域,由于性能原因,这可能会出现问题。速度确实很重要,否则您将使用解释性语言而非C#进行编码。

此外,宏还可以让您考虑人为因素-特定DSL最用户友好的语法是什么?使用C#,您在语法上无能为力。

因此,Lisp允许您参与语言设计而无需牺牲效率。尽管这些问题对可能并不重要,但它们却是所有有用的面向生产的编程语言的基础,因此非常重要。在C#中,此基础元素充其量只能以非常有限的形式展示给您。

宏的重要性在其他语言上并未丢失-模板Haskell,camlp4浮现在脑海。但这又是可用性的问题-迄今为止,Lisp宏可能是最易于使用但功能最强大的编译时转换实现。

因此,总的来说,人们通常会使用C#等语言来构建DSIL(特定于域的解释语言)。Lisp为您提供了构建更根本的东西DSP(特定于域的范式)的机会。Racket(以前是PLT-Scheme)在这方面特别令人鼓舞-它们具有惰性语言(Lazy Racket),类型化语言(Typed Racket),Prolog和Datalog,它们都是通过宏惯用地嵌入的。所有这些范例为大类问题提供了强大的解决方案,这些问题不能通过命令式编程甚至FP范例来最好地解决。


3
我对该论点感到有趣的是,我从未遇到过实施DSL / DSIL的机会(或者在我的无知中完全忽略了它)。您能否详细说明DSL的功能?(对于开发人员和用户。)听起来这可能是我真正想念的大事

15
@Jonathan Mitchem,您已经在使用很多外部DSL-配置文件,构建脚本,ORM配置,可视代码生成器(例如,winforms编辑器),XAML,解析器生成器等。嵌入式DSL甚至比您做的更好他们不需要单独的构建工具,并且可以使用单一语言来提供它们。而且您必须至少熟悉两种嵌入式DSL-正则表达式和SQL。
SK-logic

哇好 我只是从未将它们视为DSL。现在,基于“我可以使用c#进行的所有操作,我将坚持使用c#”的基础,我故意偏离了许多内容。我个人喜欢坚持使用行为一致的工具。但是我确实理解这些示例中DSL的价值。

2
@Jonathan Mitchem,任何给定工具的问题是,不一定适合给定目的。您必须为不同的任务选择不同的工具。而且任何元语言(包括Lisp)的功能就是您不必完全切换到其他工具,因为您可以在主要语言的基础上发展自己的领域特定工具。我建议您看一下Nemerle,对于C#开发人员来说,理解它会更容易,并且它的宏几乎与Lisp一样强大。
SK-logic

3
“我喜欢坚持使用一种工具...”用Lisp精心制作的DSL永远不会掩盖Lisp的全部功能,因此您仍然拥有一种工具。它们只是以一种使“特定领域”的编码更“自然”的方式(即具有更好的抽象性和整体组织性)添加到词汇表中。

15

幸运的是,几乎所有最初仅在Lisps中可用的内容都已迁移到许多其他现代语言中。最大的例外是“代码就是数据”的哲学和宏。

代码就是数据(和宏)-也许我没有“得到”宏,但是我没有看到一个不能将宏的概念实现为函数的示例

是的,很难理解宏。一般而言,元编程很难理解。如果您看到任何可以作为函数实现的宏,则说明程序员做错了。仅在功能无法使用时才应使用宏。

宏的“ hello world”示例是用cond编写“ if”。如果您真的有兴趣学习宏的功能,请尝试使用函数在Clojure中定义“ my-if”,并观察其如何中断。我并不是说要神秘,我只是从未见过有人通过解释来开始理解宏,但是此幻灯片演示非常不错:http : //www.slideshare.net/pcalcado/lisp-macros-in -20分钟的Clojure演示

我有一些小型项目,其中使用宏保存了成百上千的代码行(与Java中的代码相比)。Clojure的大部分实现都是在Clojure中实现的,这仅是因为宏系统才有可能实现。


3
在不接触任何代码的情况下,我经历了在Clojure和C#中实现“ if”而没有宏的情况。有效地(或字面上说)是不可能做到的。我承认那很干净。但是我想不通这对我有用的联系。我如何处理宏(我不能写成“ if”),而我却不能使用聪明的功能或面向对象设计?“这对我有什么帮助”不是评估语言的有效标准,而是用于评估我是否会使用它。(我真的希望 Lisp值得一改,但是还没有成为可行的替代方案。)

1
我花了一些时间来理解这个例子。对我来说,“保存样板代码”通常调用术语“重构”,这就是为什么解释没有说服我的一部分。我有一个可以在我应用它的每个实例中使用的解决方案。我认为问题是,如何确定可以使用宏的情况?

1
我同意宏是值得的,但是my-if需要它们并不是真的,可以将其编写为高阶函数(请参见下面的CL示例)。仅当您想要遍历代码或将其置于新的词法上下文中时,才真正需要宏。(defun my-if (test then &optional else) (cond (test (funcall then)) ('otherwise (funcall else))))

1
基本上,对于函数,您可以选择接受带引号的代码,可以执行这些代码,但是却缺少其词法上下文,因为您正在函数的词法上下文中执行该代码。或者,您可以接受保留其词法上下文的闭包,但是您只能调用,不能遍历它们或向其词法上下文中添加内容。为此,您需要一个宏,该宏可以遍历并包装代码,然后将其扩展放入宏调用的词法上下文中。

7
@Jonathan Mitchem,LINQ语法是宏可以完成的一个很好的例子,但是必须在语言核心中实现,因为该语言不支持任何体面的元编程。
SK-logic

14

我不是Common Lisp的专家,但是我对C#和Clojure都相当了解,因此希望这是一个有用的观点。

  • 简单语法=>强大 -Lisps是关于可能可行的最简单语法。S表达式是您所需要的。一旦您摸索了“((函数调用arg1 arg2 arg3)”),那么您基本上就掌握了它。其余的只是选择正确的函数调用和参数。看起来几乎是简单的,但是事实是,这种简单赋予了Lisps如此多的强度和灵活性,特别是启用了Lisp闻名的元编程功能。例如,如果我想让一个宏在相同的参数上调用几个不同的函数,我将执行以下操作(这不仅是运行时函数应用程序,它是一个生成编译代码的宏,就像您已写出所有单个函数调用一样)用手):
(defmacro print-many [functions args]  
  `(do   
     ~@(map   
        (fn [function] `(println (~function ~@args)))   
        functions)))

(print-many [+ - * /] [10 7 2])  
19  
1  
140  
5/7
  • 由于语法非常简单,因此Lisp的“代码就是数据”理念比功能组合/模板/泛型等所具有的功能强大得多。它不仅限于DSL,还包括编译时的代码生成和操作。 ,作为语言的基本特征。如果您尝试使用非谐音语言(例如C#或Java)来获得这些功能,那么最终您将不得不彻底改写Lisp。因此,著名的格林斯潘斯第十规则:“任何足够复杂的C或Fortran程序都包含一个临时的,非正式指定的,bug缠身的,缓慢执行Common Lisp的一半”。如果您发现自己在应用程序中发明了图灵完整的模板/流控制语言,那么您可能知道我的意思.....

  • 并发是Clojure的独特优势(甚至相对于其他Lisps),但是如果您认为编程的未来将意味着必须编写可扩展到高度多核计算机的应用程序,那么您真的应该对此进行研究-这很漂亮开创性的。Clojure STM(软件事务存储)之所以起作用,是因为Clojure包含基于身份和状态的新颖分离的不变性和无锁并发构造(请参阅Rich Hickey的有关身份和状态的精彩视频)。将这种并发功能移植到语言/运行时环境中非常痛苦,在该语言/运行时环境中,很大一部分API都在可变对象上工作。

  • 函数式编程 -Lisps强调使用高阶函数进行编程。没错,它可以在带有闭包/函数对象等的OO对象中进行仿真。但是不同之处在于,整个语言和标准库都旨在帮助您以这种方式编写代码。例如,Clojure 序列是lazy,因此可以无限长,这与C#/ Java中的ArrayLists或HashMaps不同。这使某些算法易于表达,例如,以下算法实现了斐波纳契数的无限列表:

    (def fibs (lazy-cat '(0 1) (map + fibs (drop 1 fibs))))

  • 动态类型化 -Lisps倾向于处于函数式编程语言范围的“动态”端(与像Haskell这样的语言一样,静态类型的概念非常强大和优美)相对。这既有优点也有缺点,但是我认为动态语言由于具有更大的灵活性而倾向于支持编程效率。这种动态类型具有较小的运行时性能成本,但是如果您为此感到烦恼,则可以随时向代码中添加类型提示以获取静态类型。基本上-默认情况下,您会获得便利,如果愿意做一些额外的工作,它将获得性能。

  • 交互式开发 -实际上,我认为您可以为C#4.0获得某种REPL,但是在Lisp中,这是标准做法,而不是新颖性。您根本不需要执行编译/构建/测试周期-您可以直接在运行环境中编写代码,然后再将其复制到源文件中。它教给您一种非常不同的编码方式,您可以立即看到结果并迭代地优化解决方案。

关于您认为“缺失”的事物的一些简短评论:

  • 如您所说,Clojure具有名称空间。实际上,它们是动态的名称空间:您可以在运行时更新它们-这为交互式开发提供了一些令人惊讶的好处。我在运行线程的鼻子下,或者在入侵实时数据结构的过程中,重新定义了很多有趣的功能。
  • 编译/设计时间支持-如果您在REPL上进行编码,这并没有什么关系-您只需执行一下即可直接查看结果。话虽如此,Clojure开始获得一些改进的IDE支持,例如EclipseCounterClockwise插件
  • GUI开发-是的,您必须学习一个新的API,但是在切换语言时总是如此。...好消息是Java API并不那么困难,并且现在有一些有趣的Clojure-看起来很容易使用的特定包装器/示例。有关一些不错的示例,请参见有关Clojure GUI开发的问题
  • GUI调试:假设您使用CounterClockwise,则可与Eclipse中的GUI调试器一起使用;如果使用Netbeans,则可使用Enclojure。话虽如此,我没有使用此功能-我发现REPL上的交互式调试更有效率。

我个人发现Clojure / Lisp的优势非常引人注目,而且我不打算再使用C#/ Java(除了需要借用所有有用的库之外!)。

但是,我花了几个月的时间才真正掌握了这一切。如果您真正有兴趣学习Lisp / Clojure作为一种启发性的学习经历,那么没有什么可以替代并强迫自己实施一些事情了。


1
感谢您对想法的大量列举。实际上,我确实通过C#中的IEnumerable <>构造大量使用了惰性序列(并且自2005左右以来就没有接触过ArrayList),但是我理解这个想法。Clojure的并发原语非常令人印象深刻。几年前,我在C#中进行网格计算-许多多核机器之间并发运行-我仍然相信那是未来。我从每个人那里得到的最大想法可能是“您真的必须深入研究并体验它,这真的无法解释”。因此,我将继续挖掘并不断体验。

1
很酷,很高兴为您提供帮助-这绝对是正确的精神!!
mikera

10

C#具有许多功能,但是感觉不同。某种语言具有某种功能仍然并不意味着它易于使用,范式化并且可以与其他语言很好地集成在一起。

Common Lisp具有以下组合:

  • s-表达式作为数据语法
  • 作为数据的代码导致产生宏和其他符号计算技术
  • 动态语言允许运行时修改(后期绑定,MOP等)
  • 强类型,动态类型
  • 与增量编译器或解释器交互使用
  • 评估者作为核心执行引擎
  • 垃圾回收管理内存
  • 混合语言大量使用命令式,功能性和面向对象的编程。可以添加其他范例(规则,逻辑,约束等)。

现在,其他语言(例如C#)也具有这种功能。但往往没有同样的重视。

C#有

  • 类似于C的语法
  • 大多数命令式面向对象,具有一些FP功能
  • 静态类型
  • 主要与批处理编译器一起批处理使用
  • 垃圾回收管理内存

如果没有像Lisp那样广泛使用C#,则C#具有例如代码操作功能将无济于事。在Lisp中,您不会找到许多没有以各种简单和复杂的方式大量使用宏的软件。在Lisp中,使用代码操纵确实是一个很大的功能。在许多语言中都可以模拟某些用途,但是并不能像Lisp一样使它成为主要功能。有关示例,请参见“ On Lisp”或“ Practical Common Lisp”之类的书。

对于交互式开发也是如此。如果您开发大型CAD系统,则大部分开发工作都将通过编辑器和REPL进行交互-直接进入正在运行的CAD应用程序中。您可以使用C#或其他语言来模拟很多-通常通过实现扩展语言。对于Lisp来说,重新启动正在开发的CAD应用程序以添加更改是愚蠢的。然后,CAD系统还将包含一个增强的Lisp,它将添加语言功能(以宏和符号描述的形式)以描述CAD对象及其关系(部分,包含,...)。一个人可以用C#做​​类似的事情,但是在Lisp中,这是默认的开发风格。

如果您使用交互式开发过程来开发复杂的软件,或者您的软件具有实质性的符号部分(元编程,其他范例,计算机代数,音乐创作,计划等),那么Lisp可能适合您。

如果您在Windows环境中工作(我不这样做),则C#具有优势,因为它是Microsoft的主要开发语言。Microsoft不支持任何形式的Lisp。

你写:

  • 命名空间。即使使用静态方法,我也喜欢将它们绑定到“类”以对它们的上下文进行分类(Clojure似乎有这个,CL似乎没有。)

Common Lisp不会将名称空间附加到类。命名空间由符号包提供,并且独立于类。如果使用CLOS,则需要重新定向面向对象编程的方法。

  • 类型系统提供了强大的编译和设计时支持,使我能够确定我传递的数据结构的“正确性”,这些错误拼写是实时的。我不必等到运行时就自动建议代码改进(例如使用FP方法而不是命令式方法)

使用Lisp编译器。它会通知您有关未知变量的信息,可能会有样式建议(取决于所使用的编译器),给出了效率提示,...编译器是一个按键操作。

  • GUI开发工具:WinForms和WPF(我知道Clojure可以访问Java GUI库,但是对我来说它们是完全陌生的。)

诸如LispWorks和Allegro CL之类的商业Lisps在Windows下提供类似的功能。

  • GUI调试工具:断点,插入,逐步,值检查器(文本,xml,自定义),监视,按线程调试,条件断点,调用堆栈窗口,可以跳转到任意级别的代码在堆栈中(为公平起见,我在Emacs + Slime方面的工作似乎提供了其中的一些,但是我偏爱VS GUI驱动的方法)

诸如LispWorks和Allegro CL之类的商业Lisps在Windows下提供了所有这些功能。


1
我的批评不是真的对Lisps的批评。我发现他们很有能力。我正在寻找Lisp可以做的/让我做的我还没有做的事情。我完全理解,大多数C#开发人员不会使用许多“新”语言功能。我认为更重要的一点是,我确实做到了,老实说,在GUI之外,我几乎以100%FP风格编写。FP是一个概念上的飞跃,但值得。但是,从我所在的地方切换到Lisp似乎没有什么好处。我希望发现我有理由继续给它一个机会。

@Jonathan Mitchem:C#看起来实际上并不像FP语言,即使该语言具有支持它的某些功能,也有少数库可以使用它们。如果要在Microsoft生态系统中进行FP编程,则F#可能适合您。

@Rainer不,它看起来不像FP语言,但是可以做到,而且非常紧凑。当我想要/需要命令性代码或OO时,我也可以这样做。(有点偏离主题:我并不是真的认为F#是值得学习的一种严肃的语言,我宁愿让我绕过Haskell中的monad。但是当我是C ++开发人员并且C#出来时,我并不认为它一种“真实的”语言[不是那时,不是,恕我直言])


@Jonathan Mitchem:我为什么要看?我写道C#支持某些FP功能。它只是添加到命令式的面向对象语言中,而不是像Lisp,Scheme这样的惯用语言,尤其是与SML,F#,Haskell或其他主要功能性语言相比。我的观点是:在C#中可以使用某些FP,但这不是核心功能。

5

雷尼尔·约瑟夫(Rainier Joswig)表示最好。

对我来说,仅查看Lisp功能列表就无法理解Lisp的功能。对于Lisp,尤其是Common Lisp,整体大于部分之和。不仅是REPL,s表达式,宏,多方法分派,闭包,包,CLOS,重新启动,X,Y和Z。这是整个shebang的精巧集成。这是日常经验,与其他编程语言的日常经验非常不同。

Lisp看起来不同,使用方式也不同,一种使用方式会以不同的方式进行编程。它优雅,完整,大而无缝。它并非没有缺陷和不足。当然,可能无法与其他语言进行比较。但是Lisp 绝不只是语言X加REPL和宏,或者语言Y加多方法分发和重新启动。


3
代码就是数据(和宏)-也许我没有“获得”宏,但是我没有看到一个不能将宏的概念实现为函数的示例。它不会改变“语言”,但我不确定这是一种优势

通常,应将宏用于无法表达为函数调用的对象。我不知道C#中是否有类似于switch的条件(类似于C中存在的条件,如switch(形式){case ...}),但是让我们假设它确实存在并且具有相同的限制(需要整数类型)。

现在,让我们进一步假设您想要的东西看起来像那样,但是是在字符串上分派的。在Lisp中(好吧,特别是在Common Lisp中,也许在其他情况下),这只是“一个宏”而已,如果您限制用户使用常量字符串(可能不是很费劲)来进行匹配,则可以(编译时)计算最少的测试序列,以确定哪种情况匹配(或使用哈希表,可能存储闭包)。

尽管有可能将其表示为一个函数(比较字符串,要执行的案例和闭包的列表),但它看起来可能不像“普通代码”,这就是Lisp宏有一定优势的地方。你可能已经用了不少宏或特殊形式taht是宏观像,像defundefmacrosetfcondloop等等。


2

这是我发现的最好的说明性文章,它使读者从Lisp倡导的表面上展示了所使用概念的更深层含义:

http://www.defmacro.org/ramblings/lisp.html

我记得在其他地方读过这样的想法:其他语言采用了Lisp的各种功能;垃圾回收,动态类型,匿名函数,闭包以生成更好的C ++ / C / Algol。但是,要获得Lisp的全部功能,您需要具备Lisp的所有基本功能,这时您需要Lisp的另一种方言,这是一种独特的语言家族,以一种或另一种形式存在了50多年。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.