等等,这是什么语言?


37

最近,我很高兴编写了一个Haskell程序,该程序可以检测到NegativeLiterals扩展是否被使用。我想出了以下几点:

data B=B{u::Integer}
instance Num B where{fromInteger=B;negate _=B 1}
main=print$1==u(-1)

在线尝试!

它将True正常打印,False否则打印。

现在,我的工作非常有趣,我将挑战扩展到了所有人。您还可以破解其他哪些Haskell语言扩展?

规则

要破解特定的语言扩展,您必须编写一个Haskell程序,该程序可以同时编译有无语言扩展(警告很好),并在使用该语言扩展运行并关闭时输出两个不同的非错误值(通过将No前缀添加到语言扩展)。这样,上面的代码可以简化为:

data B=B{u::Integer}
instance Num B where{fromInteger=B;negate _=B 1}
main=print$u(-1)

打印1和-1。

您用来破解扩展程序的任何方法都必须特定于该扩展程序。如果不允许的话,可能有一些方法可以任意检测启用了哪些编译器标志或LanguageExtensions。您可以启用其他语言扩展或更改编译器优化-O,而无需花费任何字节数。

语言扩展

你无法破解任何语言扩展,没有一个No对应物(例如Haskell98,Haskell2010,Unsafe,Trustworthy,Safe),因为这些不属于上文所列的条款。所有其他语言扩展都是公平的游戏。

计分

您是第一个破解的人,每种语言扩展都会获得一分,而您破解最短(以字节为单位)的每种语言扩展将获得一分。对于第二点,将打破联系以支持较早的提交。分数越高越好

您将无法为首次提交打分,NegativeLiterals或者QuasiQuotes因为我已经破解了它们并将其包含在帖子正文中。但是,您将能够在每一个裂缝中得分最短的地方得分。这是我的裂缝QuasiQuotes

import Text.Heredoc
main=print[here|here<-""] -- |]

在线尝试!


3
我认为这是所有有效选项的列表
— H.PWiz

1
请注意,NondecreasingIndentation由于明显的原因,我的上述评论未包括在内
— -H.PWiz

4
我认为该标题具有误导性,因为您可以使用的唯一语言是Haskell。怎么样,Wait, what language extension is this?或者完全不同的东西。
— MD XF

1
我很好奇是否有可能破解RelaxedPolyRec,因为一个足够古老的编译器实际上可以支持将其关闭。(该选项在停止执行任何操作后,随文档一起徘徊了数年。)
— dfeuer

1
@dfeuer看着这张票,似乎GHC 6.12.1支持关闭它。
— 与Orjan约翰森

Answers:


24

MagicHash,30个字节

x=1
y#a=2
x#a=1
main=print$x#x

-XMagicHash输出1 -XNoMagicHash输出2

MagicHash允许变量名以结尾#。因此,通过扩展,它定义了两个函数y#,x#并且每个函数都有一个值并返回一个常量2或1。x#x将返回1(因为它x#应用于1)

没有扩展名,它定义了一个函数,该函数#需要两个参数并返回2。这x#a=1是永远不会达到的模式。然后x#x是1#1,返回2。


2
我现在在唱歌X Magic Hash来舞动魔术之舞。希望您感到骄傲!
— TRiG

令我惊讶的是,MagicHash它不允许使用非拖尾式哈希。奇怪的!
— dfeuer

18

CPP,33 20字节

main=print$0-- \
 +1

0用-XCPP和1用打印-XNoCPP。

使用-XCPP,\在换行符之前加一个斜杠可删除换行符,因此该代码成为main=print$0-- +1且仅在注释的一部分时0被打印+1。

如果没有标志,则注释将被忽略,并且第二行被缩进,因此被解析为前一行的一部分。


以前的方法 #define

x=1{-
#define x 0
-}
main=print x

也打印0与-XCPP和1用-XNoCPP。


2
哦,天哪,直到现在,我还以为GHC会在转交给CPP之前先删除Haskell的评论。
— 立方

@Cubic它不是预处理器吗?
— Bergi

1
@Bergi当然可以,但是预处理程序并不一定意味着“是运行的第一件事”,特别是因为GHC必须首先传递文件才能找到杂项。我想评论会保留在CPP完成后的文档评论等工作中。
— 立方


14

BinaryLiterals,57个字节

b1=1
instance Show(a->b)where;show _=""
main=print$(+)0b1

-XBinaryLiterals打印单个换行符。-XNoBinaryLiterals打印一个1。

我相信有更好的方法可以做到这一点。如果找到一个,请发布。


您不能仅将其定义b为一个函数(这样b(0, 1),二进制不会变为,而二进制变为0b1)?
— NoOneIsHere18年

12

MonomorphismRestriction + 7个其他,107字节

这使用TH,它-XTemplateHaskell始终需要标志。

文件T.hs,81 + 4字节

module T where
import Language.Haskell.TH
p=(+)
t=reify(mkName"p")>>=stringE.show

主22字节

import T
main=print $t

使用标志MonomorphismRestriction进行编译会强制使用pto 的类型,Integer -> Integer -> Integer从而产生以下输出:

"VarI T.p (AppT (AppT ArrowT (ConT GHC.Integer.Type.Integer)) (AppT (AppT ArrowT (ConT GHC.Integer.Type.Integer)) (ConT GHC.Integer.Type.Integer))) Nothing"

使用标志NoMonomorphismRestriction进行编译时,将保留p最通用的类型,即。Num a => a->a->a-产生类似(将VarT名称简称为a)的信息:

"VarI T.p (ForallT [KindedTV a StarT] [AppT (ConT GHC.Num.Num) (VarT a)] (AppT (AppT ArrowT (VarT a)) (AppT (AppT ArrowT (VarT a)) (VarT a)))) Nothing"

在线尝试!


备择方案

由于上述代码只是打印出的类型p,因此可以使用所有以某种方式影响Haskell推断类型的标志来完成此操作。我将只指定该标志及其替换功能,p并在需要时指定其他标志(除外-XTemplateHaskell):

重载列表,106字节

其他需求-XNoMonomorphismRestriction:

p=[]

无论是p :: [a]或p :: IsList l => l,尝试到网上!

OverloadedStrings,106个字节

其他需求-XNoMonomorphismRestriction:

p=""

无论是p :: String或p :: IsString s => s,尝试到网上!

PolyKinds,112个字节

这完全是由于@CsongorKiss:

data P a=P 

无论是P :: P a或P :: forall k (a :: k). P a,尝试到网上!

MonadComprehensions,114个字节

p x=[i|i<-x]

无论是p :: [a] -> [a]或p :: Monad m => m a -> m a,尝试到网上!

NamedWildCards,114个字节

这是@Laikoni发现的,它还需要-XPartialTypeSignatures:

p=id::_a->_a

它们都具有保存类型(p :: a -> a),但是GHC会为变量生成不同的名称,请在线尝试!

适用,120字节

p x=do i<-x;pure i

无论是p :: Monad m => m a -> m a或p :: Functor f => f a -> f a,尝试到网上!

重载标签,120字节

这需要附加标志-XFlexibleContexts:

p x=(#id)x
(#)=seq

输入as p :: a -> b -> b或p :: IsLabel "id" (a->b) => a -> b,请在线尝试!


类似的事情对其他标志有用吗?
— H.PWiz '18年

是的,您可以这样做,OverloadedStrings或者OverloadedLists可以肯定也可以这样做
— 。-

2
它也适用于PolyKinds:在线尝试!
— Csongor Kiss

1
似乎也可以使用NamedWildCards:在线尝试!(要求-XPartialTypeSignatures)
— Laikoni

10

CPP,27 25

main=print({-/*-}1{-*/-})

在线尝试!

打印()的-XCPP和1用于-XNoCPP

先前版本:

main=print[1{-/*-},2{-*/-}]

在线尝试!

[1]使用-XCPP和进行打印[1,2]。

鸣谢:这是受Laikoni的答案启发的,但#define它不是A 而是仅使用C注释。


9

ScopedTypeVariables,162个 113字节

instance Show[()]where show _=""
p::forall a.(Show a,Show[a])=>a->IO()
p a=(print::Show a=>[a]->IO())[a]
main=p()

-XScopedTypeVariables打印""(空),- XNoScopedTypeVariables打印"[()]"。

编辑:由于评论中的有用建议,更新了解决方案


1
啊,我明白了。通常,将代码包含在主体中会更好,但是非高尔夫版本也很不错。我还注意到"T"可以将替换为""。
— 小麦巫师

2
您可以做的另一件事是将数据类型替换T为()。避免必须定义它。在线尝试!
— 小麦巫师

1
不错,我只是意识到可以将不连贯的实用程序作为标记包括在内:在线尝试!
— Csongor Kiss

2
此外,show 可以更改其打印
— 格式

的Unicode语法forall将为您节省一些字节。我怀疑,任何需要额外实例的解决方案都有很大的获胜希望。
— dfeuer

9

MonoLocalBinds,GADT或TypeFamilies,36 32字节

编辑:

  • -4个字节:stasoid 将其版本合并到了伟大的多语系链中,他通过将所有声明放在顶层使我感到惊讶。显然,触发此限制不需要实际的本地绑定。
a=0
f b=b^a
main=print(f pi,f 0)
  • 与没有扩展名,这个程序将打印(1.0,1)。
  • 使用标志-XMonoLocalBinds,-XGADTs或-XTypeFamilies中的任何一个,它都会打印(1.0,1.0)。

  • 该MonoLocalBinds扩展的存在是为了防止GADTs和类型的家庭引起了一些直观的类型推断。因此,此扩展名被其他两个自动打开。

  • 它是可以关掉它再次明确地-XNoMonoLocalBinds,这一招假定你不知道。
  • 像其更著名的表亲一样,单态性限制MonoLocalBinds通过防止某些值(在局部绑定中,例如let或where,因此名称显然也可能出现在顶层)来实现多态。尽管是为更合理的类型推断而创建的,但如果可能的话,触发规则比MR还要冗长。

  • 没有任何扩展名,上面的程序会推断类型f :: Num a => a -> a,允许f pi默认为a Double和f 0an Integer。

  • 使用扩展名,推断的类型变为f :: Double -> Double,并且f 0还必须返回a Double。
  • 独立变量a=0是需要触发技术规则:a通过单态限制命中,并且a是一个自由变量的f,这意味着f的结合基团不完全广义,该装置f未关闭,因此不会成为多晶型。

9

OverloadedStrings,65 48 32字节

利用RebindableSyntax,使用我们自己的fromString版本将任何字符串文字转换为"y"。

main=print""
fromString _=['y']

必须使用编译-XRebindableSyntax -XImplicitPrelude。

没有-XOverloadedStrings印刷品""; 与版画"y"。

同样,只是现在让我吃惊的是,相同的技术适用于(例如)OverloadedLists:

重载列表,27个字节

main=print[0]
fromListN=(:)

必须使用编译-XRebindableSyntax -XImplicitPrelude。

没有-XOverloadedLists印刷品[0]; 与版画[1,0]。


1
您可以将最后一行缩短为fromString a=['y']。
— 与Orjan约翰森

print "n"也可以删除其中的空间。
— Laikoni '18

@ØrjanJohansen谢谢!我的测试失败了="y",但=['y']工作正常!
— felixphew

1
您可以n从print"n"
— Wheat Wizard

1
您也可以使用-XImplicitPreludeafter RebindableSyntax来避免导入行。
— dfeuer

8

BangPatterns,32个字节

(!)=seq
main|let f!_=0=print$9!1

-XBangPatterns打印,1而-XNoBangPatterns打印0。

这利用了标志BangPatterns允许用来注释模式!以强制评估WHNF,在这种情况下9!1将使用顶级定义(!)=seq。如果未启用该标志,则f!_定义一个新的运算符,(!)并在顶层定义中显示阴影。


7

ApplicativeDo,104个字节

import Control.Applicative
z=ZipList
instance Monad ZipList where _>>=_=z[]
main=print$do a<-z[1];pure a

在线尝试!

使用ApplicativeDo,此打印

ZipList {getZipList = [1]}

没有它,它会打印

ZipList {getZipList = []}

ZipList与实例的基础库的几个类型之一Applicative,但不是Monad。可能会有更短的选择潜伏在某个地方。


7

严格,87 84 82字节

-5个字节,感谢dfeuer!

BlockArguments保存周围的括号可能会更少\_->print 1:

import Control.Exception
0!_=0
main=catch @ErrorCall(print$0!error"")(\_->print 1)

与运行此-XStrict打印1,而与运行它-XNoStrict将打印0。这使用Haskell默认情况下是惰性的,不需要评估,error""因为它已经知道结果将0与的第一个参数匹配时发生(!),可以使用该标志更改此行为-强制运行时评估两个参数。

如果在一种情况下不打印任何内容,我们可以将其缩减为75个字节,用main替​​换(也可以用dfeuer删除一些字节):

main=catch @ErrorCall(print$0!error"")mempty

StrictData,106 99 93字节

-15个字节,感谢dfeuer!

这基本上是相同的,但是可以使用数据字段:

import Control.Exception
data D=D()
main=catch @ErrorCall(p$seq(D$error"")0)(\_->p 1);p=print

打印1与-XStrictData标志和0与-XNoStrictData。

如果在一种情况下不打印任何内容,我们可以将其缩减为86个字节,用main替​​换(dfeuer减少 19个字节):

main=catch @ErrorCall(print$seq(D$error"")0)mempty

注意:所有解决方案都需要TypeApplications设置。


您可以轻松地将其缩减为98个字节,这恰好与我的(非常不同的)解决方案完全匹配。TIO。
— dfeuer

实际上,您甚至可以做得更好:只需使用,而不是在异常处理程序中进行打印pure()。
— dfeuer

1
@dfeuer:不错,D{}技巧很酷!通过使用剃光另一个一次性PartialTypeSignatures的,而不是ScopedTypeVariables:)
— ბიმო

1
@dfeuer:我看了一下,尝试了一些东西,但是我从未使用过Generics,所以我可能不是合适的人。
— ბიმო

1
您甚至可以利用边缘GHC和-XBlockArguments以下内容main=catch @ErrorCall(p$seq(D$error"")1)\_->p 3
— 做得更好

6

ApplicativeDo,146个字节

newtype C a=C{u::Int}
instance Functor C where fmap _ _=C 1
instance Applicative C
instance Monad C where _>>=_=C 0
main=print$u$do{_<-C 0;pure 1}

启用ApplicativeDo时显示1,否则显示0

在线尝试!


1
谢谢!嗯,我认为我使用的是GHC的旧版本(“无适用”是我的系统的警告)
— oisdk

3
使用-XDeriveAnyClass可以导出Applicative并Show使用记录语法进行保存,请参见this。
— ბიმო


6

ExtendedDefaultRules,54 53字节

instance Num()
main=print(toEnum 0::Num a=>Enum a=>a)

()用-XExtendedDefaultRules和0用打印-XNoExtendedDefaultRules。

默认情况下,GHCi中启用了此标志,而GHCi中没有启用该标志,尽管BMO很快就可以提供帮助,但最近使我感到困惑的是。

上面的代码是《 GHC用户指南》中示例的简化版本,其中解释了GHCi中的类型默认值。

-1个字节感谢ØrjanJohansen!


在查看借用到多语言的代码(括号给您带来麻烦)时,我记得GHC支持短一字节的语法toEnum 0::Num a=>Enum a=>a。
— 与Orjan约翰森

你可以把它降低到48个字节PartialTypeSignatures:main=print(toEnum 0::_=>Num a=>a)。另外,您的TIO链接已过期。
— dfeuer

6

RebindableSyntax,25个字节

我在阅读最近发布的《 GHC扩展指南》时,发现有一个我不记得的简单指南。

main|negate<-id=print$ -1

也需要-XImplicitPrelude,或者import Prelude在代码本身中也需要。

  • -XRebindableSyntax 更改Haskell某些语法糖的行为,以便可以重新定义它。
  • -1是的语法糖negate 1。
  • 通常negate是Prelude.negate,但扩展名为“ negate使用时范围内的任何一个”,定义为id。
  • 由于该扩展名是用于替换该Prelude模块的,因此它会自动禁用通常的隐式导入,但是此处需要其他Prelude功能(例如print),因此可以使用来重新启用-XImplicitPrelude。

6

严格,52个字节

import GHC.IO
f _=print()
main=f$unsafePerformIO$f()

-X严格

-XNoStrict

使用-XStrict,打印()更多时间。

感谢@Sriotchilism O'Zaic提供了两个字节。


6

StrictData,58个字节

import GHC.Exts
data D=D Int
main=print$unsafeCoerce#D 3+0

(链接有些过时;将修复。)

-XNoStrictData

-XStrictData

需要MagicHash(让我们GHC.Exts代替导入Unsafe.Coerce)和-O(绝对必要,以使小的严格字段解压缩)。

使用-XStrictData,将打印3。否则,将(可能是标记的)指针的整数值打印到的预分配副本3::Integer,该副本不可能是3。

说明

根据类型默认值,稍加扩展就会更容易理解。使用签名,我们可以删除添加项。

main=print
  (unsafeCoerce# D (3::Integer)
    :: Integer)

等效地,

main=print
  (unsafeCoerce# $
    D (unsafeCoerce# (3::Integer))
    :: Integer)

为什么会打印3?这似乎令人惊讶!好吧,小Integer值非常像Ints 表示,而(具有严格数据)像Ds 一样表示。我们最终忽略了指示整数是小还是大正/负的标记。

为什么没有扩展名就无法打印3?撇开任何内存布局原因,低位(32位最低为2,64位最低为3)的数据指针必须表示从第三个构造函数生成的值。在这种情况下,这将需要一个负整数。


5

UnboxedTuples,52个字节

import Language.Haskell.TH
main=runQ[|(##)|]>>=print

需要-XTemplateHaskell。打印ConE GHC.Prim.(##)与-XUnboxedTuples和UnboundVarE ##与-XNoUnboxedTuples。


所需选项的分数不应该再有+16 -XTemplateHaskell吗?
— celtschk

2
@celtschk我没有计算它,因为当前关于命令行标志的元共识说,它们不计算在内,而是构成一种新语言。尽管经过思考,我发现在这种挑战的背景下,这种挑战仅允许Haskell回答,但也可以使用其他标志,不清楚要做什么。我会问OP。
— Laikoni

我不知道对此达成的共识已经改变。谢谢你的指点。确定要问OP是个好主意。
— celtschk

5

重载列表,76个字节

import GHC.Exts
instance IsList[()]where fromList=(():)
main=print([]::[()])

使用-XOverloadedLists可以打印[()]。使用-XNoOverloadedLists可以打印[]

这需要附加标志:-XFlexibleInstances,-XIncoherentInstances


您可以避免重叠的实例。
— dfeuer

5

HexFloatLiterals,49 25字节

感谢ØrjanJohansen,使用-24个字节。

main|(.)<-seq=print$0x0.0

0.0用-XHexFloatLiterals和0用打印-XNoHexFloatLiterals。

没有TIO链接,因为在ghc 8.4.1中添加了HexFloatLiterals,但是TIO具有ghc 8.2.2。


main|(.)<-seq=print$0x0.0避免导入隐藏。
— 与Orjan约翰森

main|let _._=0=print$0x0.0对于多语言可能会更容易。
— 与Orjan约翰森

5

ScopedTypeVariables,37个字节

main=print(1::_=>a):: ∀a.a~Float=>_

这也要求UnicodeSyntax,PartialTypeSignatures,GADTs,和ExplicitForAll。

在线尝试(不带扩展名)

在线试用(带扩展名)

说明

部分类型签名仅用于保存字节。我们可以这样填写它们:

main=print(1::(Num a, Show a)=>a):: ∀a.a~Float=>IO ()

对于作用域类型变量,ain的in 1被约束为ain的in main,其本身被约束为in Float。如果没有作用域类型变量,则1默认为type Integer。由于Float和Integer值的显示方式不同,因此我们可以区分它们。

感谢@ØrjanJohansen提供多达19个字节!他意识到,利用Show不同数值类型的实例之间的差异要好于其算术差异。他还意识到,可以保留main“语法上模棱两可” 的类型,因为约束实际上可以消除歧义。摆脱本地功能还使我腾出了精力,可以删除类型签名main(将其移至RHS)以节省五个字节。



@ØrjanJohansen,很好。
— dfeuer

@ØrjanJohansen,我应该进行编辑,还是要添加自己的编辑?
— dfeuer

编辑,这是您的渐进式发展。
— 与Orjan约翰森

@ØrjanJohansen,谢谢,太好了。
— dfeuer

5

DeriveAnyClass,121个 113字节

感谢dfeuer提供了很多字节!

import Control.Exception
newtype M=M Int deriving(Show,Num)
main=handle h$print(0::M);h(_::SomeException)=print 1

-XDeriveAnyClass打印,1而-XNoDeriveAnyClass打印M 0。

这利用了以下事实:从警告中可以看到,当同时启用DeriveAnyClass和GeneralizedNewtypeDeriving时,DeriveAnyClass是默认策略。该标志将很乐意为所有方法都是空实现,但GeneralizedNewtypeDeriving实际上是足够聪明的使用基础类型的实现,因为Int是Num它不会在这种情况下失败。


如果在启用该标志的情况下不打印任何内容,则用main以下内容替换109字节:

main=print(0::M)`catch`(mempty::SomeException->_)

至少在runhaskell这实际打印M 1带-XDeriveAnyClass,由于懒惰......
— 停止转counterclockwis

@ceasedtoturncounterclockwis:是在GHCI为好,但对TIO(和我的机器)编译和运行,然后当它导致1:)
— ბიმო



1
我以完全不同的方式将其降至104,所以我添加了自己的答案。
— dfeuer

4

PostfixOperators,63个字节

import Text.Show.Functions
instance Num(a->b)
main=print(0`id`)

在线尝试(不带扩展名)

在线试用(带扩展名)

这是我写的Hugs / GHC多语言版本的简化版本。请参阅该帖子以获取解释。感谢@ØrjanJohansen的实现,我可以使用它id代替自定义运算符,从而节省了四个字节。


id可以代替!。
— 与Orjan约翰森

@ØrjanJohansen,的确是!这样可以节省四个字节。
— dfeuer




3

TemplateHaskell,140 91字节

只是从mauke复制而来,但做了一些小的修改。我不知道这是怎么回事。

-49个字节,感谢ØrjanJohansen。

import Language.Haskell.TH
instance Show(Q a)where show _=""
main=print$(pure$TupE[]::ExpQ)

在线尝试!


$(...)(没有空格)是启用TH时的模板评估语法,并且TupE[](“空元组”)给出()。使用Show可能会有不错的多语种,虽然这个特别的挑战,我觉得有关定义的值打印为空字符串有点不好...
— 与Orjan约翰森

2

MonomorphismRestriction,31个 29字节

编辑:

  • -2字节,由H.PWiz改进
f=(2^)
main=print$f$f(6::Int)

-XMonomorphismRestriction打印0。-XNoMonomorphismRestriction打印18446744073709551616。

  • 由于存在限制,必须将的两种用法f设为同一类型,因此程序2^2^6 = 2^64将以64位Int(在64位平台上)打印,并溢出到0。
  • 在没有限制的情况下,程序将打印2^64为bignum Integer。

1
我认为f=(2^);main=print$f$f(64::Int)可以节省一个字节。但是它实际上不会终止
— H.PWiz

@ H.PWiz幸运的是64=2^6,它保存了另一个字节。
— 与Orjan约翰森

1

ScopedTypeVariables,119 97字节

只是从mauke复制而来,但做了一些小的修改。

目前还有其他两个答案为ScopedTypeVariables:通过琼戈尔吻113个字节和 37个字节的dfeuer。提交内容的不同之处在于,它不需要其他Haskell扩展。

-22个字节,感谢ØrjanJohansen。

class(Show a,Num a)=>S a where s::a->IO();s _=print$(id::a->a)0
instance S Float
main=s(0::Float)

在线尝试!


97字节(尽管该IO()/print技巧在多语言版本中不起作用)。
— 与Orjan约翰森

@ØrjanJohansen我添加了ScopedTypeVariables,但破坏了ExtendedDefaultRules。如何解决?之前我已经有这样的错误,但是我无法在这里应用您的解释。我添加的ScopedTypeVariables代码是this。
— stasoid

我看到,这些代码使用了类似的默认技巧,并且它们相互干扰。一种解决方案是让新方案使用比更为严格的类Num。我认为class(Show a,Floating a)=>K a where{k::a->String;k=pure$ show(f pi)where f=id::a->a};应该可以正常使用它Float并以不同的精度进行Double显示pi。
— 与Orjan约翰森

@ØrjanJohansen哇,很合适。谢谢。
— stasoid
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.