功能新思想

介绍

伙计,我已经有一个多月了(很幸运我也写下了这个想法),直到我写这篇文章的那天。 我之所以写这本书不是因为我忙于搬家和新工作,但是现在一切都终于平静下来了!

事情是这样的:当您真正深入研究它时,正确的面向对象编程和函数式编程非常相似。 最大的区别是,面向对象的编程喜欢使用封装将真实的数据隐藏在对象的幕后-要求您定义附加到知道私有细节的类型的方法-而函数式编程则非常重要-这样一来,您通常可以得到类似开关的结构(但效果更好),该结构允许您编写一个函数来处理所有不同的类型。

副作用是调用函数的两种不同方式:作为方法和独立函数。

object.methodCall()
// Vs
functionCall(object)

实际上,第二种可能是程序上的或功能上的。 没有更多的争论就不可能说出来。

由于函数式编程喜欢将函数组合在一起,因此它喜欢将函数的“主题”作为最后一个参数,以便可以对该函数进行咖喱处理或将其部分应用到可以与在该函数上工作的其他函数组成的基本函数中。相同类型或输出类型。 真的很酷,很方便。 但是在程序风格上,趋势是将“主题”作为第一个论点,以显示其突出性。

object.methodCall(argument)  // OO style
functionCall(object, argument)  // procedural style
functionCall(argument, object)  // functional style

有趣的是,Python 方法实际上是在过程函数样式中显式定义的,但通常是在OO样式中调用的。 这激发了我的一个想法,在知道有多少其他语言起作用的情况下,我将其引向了今天的想法。 如果可以用一种方法定义一个函数,但可以用上面列出的三种方法中的任何一种来使用呢?

基本规则

首先,我想说的是,我希望使用该系统而不需要括号或逗号,但是为了易于阅读,我将使用它们。 就是说,我将继续讲到当我们摆脱这些新可能性时所打开的新可能性。

因此,为了使该系统能够受到人们的任何吸引,它必须简单,容易(这两者是不同的),并且不能使人分心。

定义功能

定义函数几乎与否则完全相同,只有一两个小区别:您必须以某种方式标记函数的主题(我的建议是在参数名称的末尾加句号)。 并且该主题必须是列表中的第一个参数。 像在Python中一样,即使它是方法,也必须将主题显式添加到参数列表中。 因此,对于Pythonistas来说,这不是改变。 对于其他OO语言的用户而言,包括通常隐含的内容可能会有些刺耳。

之所以必须明确标记主题,是因为某些功能无法使其与主题一起使用。 以maximum()minimum()为例。 它们的所有参数均具有相等的权重。 因此,必须做一些明确的事情。

基于此的一个有趣的想法是想知道如何使用OO语言中常见的隐式this变量。 好了,而不是使用一个隐含的this (或self Python中,其中,可悲的是,不能隐式使用),我们与周期更换整个参数名称, this简直是缩短到一个时期整个呼叫,使得它几乎看不见,但仍然清楚地表明它正在被使用(如果您了解Python的Zen,则知道显式比隐式更好)。

这与数学中的想法相同,在数学中,您仅使用点(甚至只是将两个操作数混在一起)来显示乘法。 使用乘法是如此普遍,以至于缩短其编写方式使其变得更容易使用。 以同样的方式,该主题被如此普遍地使用,以至于使用它的缩短方式使得编写代码变得更加容易。

在编写lambda时,这也会产生一些非常酷的效果,我们将在后面介绍。

调用函数

现在我们进入最酷的部分。 通过对函数的定义进行如此简单的更改,就必须采用某种复杂的方式来调用函数,对吗? 错误。 让我们来看看。

面向对象的风格

实际上,以OO方式调用代码绝对没有任何变化。 仍然只是object.methodCall(arguments) 。 看到? 这很简单。 但是我要超越自己。 我们还有两个要走。

程序风格

在这里,我们实际上做了一些新的事情,但是又非常简单。 我们要做的就是在传递的内容的末尾加上一个句点: functionCall(object., arguments)

但是,如果主题是某个计算值,该怎么办? 简单。 只需将计算结果括在括号中,然后将点放在: functionCall((a + b + c)., arguments)

功能风格

好的,这很奇怪。 它不符合您的想法。 我已经考虑了很多,当涉及到功能样式时,由于功能的可组合性,您只能这样做。 因此,对于这种样式,它实际上看起来像这样: functionCall(arguments)(object.)

如果主题没有在函数名称前添加前缀,并且不是列表中的第一个参数,则编译器会假设您正在执行此操作以编写函数,因此它将创建部分函数,​​从而将主题排除在外。 当您试图将函数组合在一起时,这可以消除对partial()类的混乱调用。

的确,在某些函数式语言中,函数是为咖喱设计的,因此无论如何这都是自动的,但是我们正在尝试使用一种不错的混合语言,使两种样式都同样可行,并且包括自动咖喱可能会干扰“氛围” 。

由于假设您要为合成设置函数(甚至可能只是使用预先设置的所有参数创建一个新函数以供以后使用),因此您不太可能实际执行上述函数调用。 假设>>是左右合成运算符,则它看起来应该像这样:

newFunc = functionCall(arguments) >> otherFunction(arguments) >> anotherFunction(arguments)
…
newFunc(object.)
// or
object.newFunc()

顺便说一下,如果您要立即使用组合函数,则可以更简单地以OO样式完成,如下所示:

object.functionCall(arguments).otherFunction(arguments).anotherFunction(arguments)

方法链接是如此美丽,不是吗?

有很多点

我喜欢整个想法的地方是,它使“点运算符”变成了更有用的东西。 它可以说出“嘿,我是这里真正重要的对象”。 它使我们能够采取什么样的有时是一个令人困惑的隐含this并使其明确,但微小的。 由于带点的参数/参数实际上在带点的参数列表中,因此它在方法定义及其使用之间也形成了一种对称性。

Lambdas

Kotlin的真正酷事之一就是您可以使用它们的lambda来做。 它具有各种快捷方式和特殊功能,例如:

  • 如果lambda接受0或1个参数,请省略参数列表。 另外,如果它取1,并且不包含参数列表,则可以将it用作隐式参数名称
  • 带接收器的Lambda ,或者像Iike所说的那样,称为扩展Lambda。 这就是“参数”中的一个是可与隐式或显式使用的接收机this在lambda体。 这在内部DSL中特别有用。
  • 以来:
    • lambda被大括号( {} )包围

然后,使用这些lambda可以创建看起来像语言功能的函数。

这里有一个问题:当你定义需要拉姆达的功能,你必须指定在点的拉姆达是否具有接收与否。 这导致一些函数似乎在其标准库中增加了两倍或三倍( also()apply()let()run()with() )。 它们执行的操作几乎相同,唯一的区别是一个参数中的lambda参数没有接收方,而另一个参数中的lambda参数却没有接收方。

我们可以使用我们的功能系统解决该问题。 而不是像这样定义let()run()with()

fun <T, R> T.let(block: (T) -> R): R { … }
fun <T, R> T.run(block: T.() -> R): R { … }
fun <T, R> with(receiver: T, block: T.() -> R): R { … }

您可以像这样with()函数:

fun <T, R> with(receiver.: T, block: (T.) -> R) : R { … }

现在,以receiver为主题参数,可以将其称为3种不同的方式(OO,过程或功能)。 但是,这三种不同方式中的每一种也可以决定是否编写lambda以“使用接收器”。 当您不指定lambda的参数时,它没有任何区别,因为单个参数隐含为一个点,它将替换itthis (无论是隐式还是显式使用)。

顺便说一句,如果您没有注意到,这也消除了对显式扩展方法的需要,因为任何标记主题的函数都可以像使用类上的方法一样使用。 我们已经消除了Kotlin令人敬畏的功能的一半,而仅包含一项(一半?)功能。

没有括号和逗号的函数

免责声明:在某些情况下,通过钻入这个兔子洞,您实际上可能需要使用比以前更多的括号(特别是当您开始对运算符发疯时),但是这些括号中的大多数应该一直都存在。 以后再说。 另外,我们不会讨论函数定义-我一直在努力尝试一种可能的语法,该语法至少没有parens而仍然具有我想要的所有功能-仅函数调用。

您是否知道有一些语言在调用函数时不使用括号或逗号? Lisps不使用逗号,而是将括号移到整个函数调用周围,而不仅仅是参数。 除去那些括号,您实际上就有了Haskell(这是不正确的,但是至少一个基本的函数调用看起来是一样的)。

在Haskell中,调用函数与function arg1 arg2 arg3函数一样简单。 为什么这是可取的? 因为那样的话,您可以删除许多讨厌的特殊字符,这些特殊字符会使您在输入时放慢速度。 而且,它可以使内部API看起来更简洁,读起来更像散文(如果可以的话,这始终是一个不错的目标)。 例如,Java的Lock具有一个名为tryLock()的方法,该方法采用数字和TimeUnit 。 通常,通过静态导入TimeUnit ,对此的调用将类似于以下内容:

lock.tryLock(500, MILLISECONDS)

但是,如果您要进行一些细微调整并去除括号和逗号,它可能看起来像这样:

lock.tryFor 500 MILLISECONDS

或者,如果将我们的新功能应用于该函数以及允许的命名参数,则该函数看起来可能像这样:

tryToLock lock. for=500 MILLISECONDS

男人,读起来像是散文。 摆脱=和点是很好的,但是我不确定这样做的好方法,或者从长远来看是否足够可取。 至于名字tryToLock ,我最初只是使用tryTo ,它可以工作,但不是一个好主意。 它假定传递的锁将被称为lock或足够接近的东西(在这种情况下,实际上是一个通常安全的假设)。 问题在于变量的名称是一个名词,但是我们像动词一样使用它。 名词即使有动词,也不会完美地匹配其动词形式。 如果名称更像myReadLock ,那将非常有效。

另外,请注意,数值参数的名称for 。 当在函数定义中查看时,效果不是很好。 但是,如果您使用的是允许命名参数的语言,请尝试考虑是否可以使用像这样的小词作为参数名称,以使带有命名参数的调用更好地读取。 不必使用没有括号和逗号的语言。 即使在Kotlin,通话看起来像

lock.tryToLock(for=500, MILLISECONDS)

它读得更多散文。 更好的是

lock.tryToLockFor(500, MILLISECONDS)

哪种与我的论点相反?

但是,我正在摆脱话题。

关键是它可以节省击键次数,并且可以像散文一样阅读更多内容。

运算符和中缀函数

跳过括号和逗号还有另一个好处:不需要特殊的新语法即可将函数定义为运算符和infix函数。

这要求我告诉您一些可能早些时候应该告诉您的内容,但本节让我感到惊讶。 您可以在主题和“方法”调用之间放置一个空格。 因此,我们较早尝试删除括号和逗号的尝试可能看起来像这样:

lock. tryFor 500 MILLISECONDS

您为什么不能在那里放置空间? lock. 只是另一个论点。 它只是一个特别的地方。 函数名和其他参数之间有一个空格,因此为什么在主题和函数名之间不应该有一个空格。 事实上,当对象是在函数名说, 得到的空间。 那么在这种情况下为什么不呢?

您是否看到这如何帮助我们获得运算符和中缀函数而无需任何其他语法? 假设该语言没有任何内置运算符,并且还允许名称中包含大多数特殊字符。 然后,您可以将整数加法运算符定义为如下函数:

fun +(operand1.: Int, operand2:Int): Int { … }

然后可以这样使用:

val x = 12. + 15

当然,那个点令人分心。 如果您觉得这很烦人,可以将一个operator关键字应用于一个函数,以表示subject参数将始终排在首位,并且不需要点。 但即使如此,这几乎涵盖了Kotlin的operator关键字以及Kotlin的infix关键字的所有情况。 如果您不介意点,并且也希望通过其他两种方式调用操作符函数,则甚至不需要该关键字。

如有疑问,请将其消除

使用函数而不是适当的运算符的语言对其具有有趣的特性。 他们几乎没有操作员优先考虑的问题。 像赋值运算符之类的东西有时还是会被烘焙(不是在Lisps中),并且需要具有比函数更低的优先级,但是这将覆盖90%的烘焙优先级。 这是做这样的事情:

a + b < c * d OR a / b > c - d

很难解释。 数学运算符,比较运算符和逻辑运算符都只是函数,这意味着它们都具有相同的优先级。 就是这样,从左到右移动将使我们在乘法运算符上遇到麻烦。 直到那时,我们只有a + b < c ,这是一个布尔值,然后与乘以c 。 用语言将数字与布尔值相乘的可能性很小。 因此,我们需要某种方式来分配优先级。 这种方式是使用括号。 在正常的语言中,您可能已经使用了一些括号来使内容更加清晰,例如:

(a + b < c * d) OR (a / b > c - d)

但也许不是。 您不需要。 优先级表将首先进行乘法和加法,然后进行加法和减法,然后进行比较,最后进行逻辑运算符(尽管短路将使之如此,以便直到(且除非)OR才计算右侧确定左侧为假。

没有任何优先权,您需要输入

((a + b) < (c * d)) OR ((a / b) > (c - d))

无论如何,这可以说是更好的选择。 实际上,这正是您要做的事情,即在lisps(尽管两个操作数都在运算符的右边)和Haskell(据我所知。我只是略过了Haskell的外观)。 当然,在函数名称中使用特殊字符可以用作运算符,只是打开了一大堆蠕虫,使人们提出了自己完全荒谬而深奥而令人困惑的运算符(可能是我最大的误解之一)讨厌Haskell的事情),这就是使许多语言发誓任何类型的运算符重载的原因。

我告诉你,有些情况下比正常情况需要更多的赔付。 就个人而言,我认为需要它们很麻烦。 特别是因为我讨厌在许多语言中AND和OR具有不同的优先级这一事实。 仅仅因为其中一个是布尔代数中的“加法”,另一个是“乘法”,这并不意味着那是应该按优先级排序的方式。 rah!

无论如何……我想我几乎想与您分享所有这一切。 除了…

旁注:名称

当一种语言允许在名称中使用更多特殊字符时,它将打开我最喜欢的命名约定。 通常,在严格限制的语言中,您几乎只能使用下划线或大写字母来分隔单词。 每个都有明显的缺点,即需要Shift键,而我有很高的倾向以某种方式搞砸,这确实使我感到沮丧。

在学习了一些有关Clojure的知识之后,看到它们使用连字符来打断名字(当转换为Java时,它变成了使用驼峰式的大写字母),我坠入了爱河。 它像单词下划线一样在单词之间放置空格,使其更易于阅读,但是不需要像下划线那样需要Shift键。 因此,对您来说,语言设计人员是:如果您可以找到一种允许名称中带有连字符的方法,那么您将不胜感激。 还有问号。 Clojure代码后缀以问号返回布尔值的函数的方式确实很有帮助且令人满足。

好的,现在我完成了。 向前,到了结局!

奥托罗

你觉得呢? 听起来有点傻吗? 您是否认为这确实很棒,并希望这些想法存在一种语言? 在下面的评论中让我知道。

大家知道,我计划有一天可能使用JetBrains的MPS来制作具有这种想法的语言,并将其翻译成Python AST和Kotlin / Java。

另外,下周,如果有时间,我将向您展示一些Python装饰器,这些装饰器将允许您以与此类似的方式使用函数。 现在这只是一个想法,我认为它会起作用。 我们将会看到。

再见!

翻译自: https://www.javacodegeeks.com/2017/10/new-idea-functions.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值