Clojure - 基本语法
2013-01-22 18:03
507 查看
InstallingClojure
Clojureisanopen-sourceprojecthostedatgithub.com.gitcloneTheClojureREPL
REPL,命令行工具
docandfind-doc,帮助文档
Thedoc,lookupthedocumentationassociatedwithanyotherfunctionormacro.user=>(doc+) ------------------------- clojure.core/+ ([][x][xy][xy&more]) Returnsthesumofnums.(+)returns0.
Thefind-docfunctionacceptsastring,whichcanbearegexpattern.Itthenfindsthedocumentationforallfunctionsormacroswhosenamesorassociateddocumentationmatchthesuppliedpattern.
user>(find-doc"lazy") ------------------------- clojure.core/concat ([][x][xy][xy&zs]) Returnsalazyseqrepresentingtheconcatenationof... ------------------------- clojure.core/cycle ([coll]) Returnsalazy(infinite!)sequenceofrepetitionsof... ...moreresults
基本语言特征
PrefixNotation
Clojurecodeusesprefixnotation(alsocalledpolishnotation)torepresentfunctioncalls.其实很多人会对于这个很不习惯,主要是因为数学计算操作符,比如(+12)其实对于函数,prefix是一种常态,换个写法add(1,2),是不是就比较容易接受了所以奇怪的不是prefix,而是其他的语言,为了迎合大家的使用习惯对数学操作符做了特殊的处理,这个导致了复杂的语法.而对于clojure,没有特例,一切都是function的语法,也可以说nosyntax这样最大的好处,就是非常便于generateandmanipulatecodeCaseSensitive
MostLispsarenotcasesensitive.Clojure,ontheotherhand,iscasesensitive.Comments,注释
单行:;;;;;;Lisper习惯于用越多;表示越重要或者越概要的注释;单行注释;;函数注释;;;macro或者defmulti的注释;;;;ns注释多行:(comment"...1......2...")Exception,异常
user=>(/10)
java.lang.ArithmeticException:Dividebyzero(NO_SOURCE_FILE:0)
查看detailedstacktraceThe*especialvariableholdsthelastexception.BecauseClojureexceptionsareJavaexceptions,youcancallJavamethodssuchasprintStackTrace():
user=>(.printStackTrace*e)
java.lang.ArithmeticException:Dividebyzero(NO_SOURCE_FILE:0)
atclojure.lang.Compiler.eval(Compiler.java:4094)
atclojure.lang.Repl.main(Repl.java:87)
Causedby:java.lang.ArithmeticException:Dividebyzero
atclojure.lang.Numbers.divide(Numbers.java:142)
atuser.eval__2677.invoke(UnknownSource)
atclojure.lang.Compiler.eval(Compiler.java:4083)
...1more
Symbols,Vars,Bindings
Symbols,名称,标识符
Broadlystated,asymbolisanidentifierthatresolvestoavalue.Symbols在clojure里面可以表示,Varname,functionname,operatorsname,macroname……命名规则
Symbolnamesarecasesensitive,anduser-definedsymbolshavethefollowingrestrictions:•Maycontainanyalphanumericcharacter,andthecharacters*,+,!,-,_,and?.
•Maynotstartwithanumber.
•Maycontainthecoloncharacter:,butnotatthebeginningorendofthesymbolname,andmaynotrepeat.
Accordingtotheserules,examplesoflegalsymbolnamesincludesymbol-name,symbol_name,symbol123,*symbol*,symbol!,symbol?,andname+symbol.Examplesofillegalsymbolnameswouldbe123symbol,:symbol:,symbol//name,etc.区分大小写,通常都是小写,并以-分隔.通常常量或全局,首尾加*
Byconvention,symbolnamesinClojureareusuallylower-case,withwordsseparatedbythedashcharacter(-).Ifasymbolisaconstantorglobalprogramsetting,itoftenbeginsandendswiththestarcharacter(*).Forexample,aprogrammightdefine(def*pi*3.14159).
SymbolResolution
resolve顺序如下specialform–>localbinding(let)-->threaddynamicbinding(binding)-->rootbinding(def)
Vars,变量
Varscanbedefinedandboundtosymbolsusingthedefspecialform.Clojure中变量和其他语言不同就是,不可变通常通过def定义,并bind到一个symbol(变量名称)
所以反之,从symbol可以reslove到var,并evaluate出var-value.
user=>(deffoo10);定义var
#'user/foo
user=>(resolve'foo);resolvesymbol(foo)tovar
#'user/foo
user=>user/foo;evaluatevartovalue
10user=>foo;等于上面两步,resolve和evaluate会自动完成
10
resolve(#’)只会取var本身,而不会evaluate,比如用于取var自身的metadata,(meta#'str)
Binding
Binding分为3种,rootbinding,localbinding(lexicalbinding)和thread-localdynamicbinding(Rootbinding
Whenyoudefineanobjectwithdefordefn,thatobjectisstoredinaClojurevar.Forexample,thefollowingdefcreatesavarnameduser/foo:(deffoo10)
#'user/foo
Youcanrefertoavardirectly.Thevar(#')specialformreturnsavaritself,notthevar’svalue:
(vara-symbol)
Youcanusevartoreturnthevarboundtouser/foo:
(varfoo)比如可以用于取var自身的metadata,(meta#'str)通过def定义的var,是一种rootbinding,就是globle的,各个线程都能看到Rootbindingsalsobindnamestofunctions.Whenyoucalldefn(封装的def),itusesdefinternally.Sofunctionnamesliketriplebelowarerootbindings.
#'user/foo
#'foo
#'user/foo
(defntriple[x](*3x))
Localbindings
除了rootbinding以外,还有一种binding叫localbinding,即lexicalbinding最常见的localbinding就是函数的参数Forexample,inafunctioncall,argumentvaluesbindtoparameternames.(defntriple[number](*3number))
(triple10)
->30
Afunction’sparameterbindingshavealexicalscope(只在词法范围内起作用所以叫lexicalbinding):theyarevisibleonlyinsidethetextofthefunctionbody.Functionsarenottheonlywaytohavecreatealexicalbinding.Thespecialformletdoesnothingotherthancreateasetoflexicalbindings:
(let[bindings*]exprs*)
let非常有用,底下给出了各种用法,包括各种destructioin操作(集合中只有部分有用)
;局部临时变量定义:
(let[x10](printlnx));定义多个变量,并进行destruction:
(let[[xy][34]](println(*xy)));12
(let[x3y4](println(*xy)))
(let[[xy][345]][xy]);[34]多余的5被忽略
(let[[__z][345]]z);5
(let[[ab&c][12345]][abc]);[12(345)]
(let[a10
[xy](split"2012-1""-")
b20]
(strx"."y));"2012.1"
(let[{x0y6}'[abcdefg]][xy]);[ag]0,6表示下标;多个变量之间可以依赖(后面的依赖前面的),这点*非常*非常*有用:
(let[x10y(*xx)z(*2y)](printlnz));200;let的执行体内可以调用多个函数:
(let[x10](printlnx)(println(*xx)))
NamespacesandLibraries
OrganizingClojureCode
所有语言的命名空间都是用于代码库的组织,否则放在一起太乱了clojure一般都会在文件开头加上一段命名空间和库的声明NamespacesarethemeansbywhichyoudivideyourClojurecodeintologicalgroups,similartopackagesinJavaormodulesinotherlanguages.
AlmosteveryClojuresourcefilebeginswithanamespacedeclarationusingthensmacro.
Thefollowingcodeisanexampleofanamespacedeclaration:
(nsclojure.contrib.gen-html-docs
(:require[clojure.contrib.duck-streams:asduck-streams])
(:use(clojure.contribseq-utilsstr-utilsrepl-utilsdefprxml))
(:import(java.langException)(java.util.regexPattern)))
切换namespace
Youcanswitchnamespaceswiththens,in-nsmacro.user=>(nsmyapp)
user=>(in-ns‘myapp)
Whenyoucreateanewnamespace,thejava.langpackageandtheClojurenamespaceareautomaticallyavailabletoyou:
myapp=>String
#=java.lang.String
myapp=>#'doc
#=(varclojure/doc)
其他的package,你需要自己import!!
(import'(java.ioFile))
->nil
myapp=>(File/separator)
->"/"
加载namespace
LoadingfromaFileorStream
(load-file"path/to/file.clj")(load-file"C:\\Documents\\file.clj")
对于stream,需要使用load-reader但其实这种方法使用的很少,一般都会基于classpath,否则会很麻烦
LoadingfromtheClasspath
TheJavaVirtualMachineusesaspecialvariablecalledtheclasspath,alistofdirectoriesfromwhichtoloadexecutablecode.Clojureprogramsalsousetheclasspathtosearchforsourcefiles.clojure也会使用classpath来searchsource,所以先要将工程所在目录放到classpath里面ClojurenamespacesfollowsimilarnamingconventionstoJavapackages:theyareorganizedhierarchicallywithpartsseparatedbyperiods.ApopularconventionistonameyourlibrariesusingthereversedformofanInternetdomainnamethatyoucontrol.
clojure采用和Java相同的ns命名规则,比如,com.example.my-cool-librarywouldbedefinedinthefilecom/example/my_cool_library.clj
Require,等同pythonimport
如题,所以require后,使用命名空间中的var,必须每次加上namespace(require'introduction)
(take10introduction.fibs)->(0112358132134)
(require'com.example.lib);;一般形式
(require'com.example.one'com.example.two'com.example.three);;可以添加多个
(require'[com.example.lib:aslib]);;别名
(require'(com.exampleonetwothree));;前缀形式,可以加入相同前缀的多个package
(require'(clojure.java[io:asio2]);;在前缀形式中,加别名;:reload,loadallnamespacesinthearguments
;:reload-all,beside:reload,needalldependentnamespacesrequiredbythosenamespaces.
(require'com.example.one'com.example.two:reload);:verbose,printsdebugginginformation
user=>(require'(clojurezip[set:ass]):verbose)
(clojure.core/load"/clojure/zip")
(clojure.core/load"/clojure/set")
(clojure.core/in-ns'user)
(clojure.core/alias's'clojure.set)
Use,等同Pythonfrom…import
其实clojure还有个命令叫refer,可以把namespace里面的var都load进来,避免每次都要加上namespace名,但很少用因为use=require+refer
用过python的都知道,尽量不要使用fromimport*
同样对于use,也进来使用条件,only
(use'clojure.core)(use'[clojure.core:exclude(mapset)])(use'[clojure.core:rename{mapcore-map,setcore-set}])(use'[com.example.library:only(abc)]:reload-all:verbose)
Import,importingJavaclasses
(import'java.util.Date)(import'(java.util.regexPatternMatcher))(import'(javax.swingBox$Filler));javax.swing.Box.Filler
其他关于namespace
NamespaceMetadata
Clojuredoesnotspecifyany“official”metadatakeysfornamespaces(ns#^{:doc"Thisismygreatlibrary.":author"Mr.Quux<quux@example.com>"}com.example.my-great-library)
ForwardDeclarations
Clojure也是var的定义必须放在var的使用之前,如果出于代码组织考虑一定要放后面,先使用declare声明(declareis-even?is-odd?)(defnis-even?(if(=n2)true
(is-odd?(decn))))
(defnis-odd?(if(=n3)true
(is-even?(decn))))
Namespace-QualifiedSymbolsandKeywords
Symbolsandkeywordscanbequalifiedwithanamespace.标识符和keywords都可以加上限定的命名空间,并通过name和namespace来分别取得,
user=>(name'com.example/thing)
"thing"
user=>(namespace'com.example/thing)
"com.example"user=>(name:com.example/mykey)
"mykey"
user=>(namespace:com.example/mykey)
"com.example"
为了语法方便,可以在keyword前面多加一个:来qualify到当前namespaceuser=>(namespace::keyword)
"user"对于symbol,使用backquote可以达到同样效果
Althoughnotexplicitlyforthispurpose,thebackquote`readermacrocanbeusedtocreatequalifiedsymbolsinthecurrentnamespace:
user=>`sym
user/sym
PublicandPrivateVars
Bydefault,alldefinitionsinanamespacearepublic,meaningtheycanbereferencedfromothernamespacesandcopiedwithreferoruse.Sometimesneed“internal”functionsthatshouldneverbecalledfromanyothernamespace.两种方法,defn-macroadd:privatemetadatatothesymbolyouaredefining
(def#^{:privatetrue}*my-private-value*123)
QueryingNamespaces
Thefunctionall-nstakesnoargumentsandreturnsasequenceofallnamespacescurrentlydefined.(keys(ns-publics'clojure.core))
Forms,clojure语言的basicelement,合法的s-expression
Clojureishomoiconic,whichistosaythatClojurecodeiscomposedofClojuredata.WhenyourunaClojureprogram,apartofClojurecalledthereaderreadsthetextoftheprograminchunkscalledforms,andtranslatesthemintoClojuredatastructures.
Clojurethentakesexecutestheforms.
UsingNumericTypes
Numericliteralsareforms.Numberssimplyevaluatetothemselves.Ifyouenteranumber,theREPLwillgiveitbacktoyou:42
->42
Alistofnumbersisanotherkindofform.Createalistofthenumbers1,2,and3:
'(123)
->(123)
单引号的作用
Noticethequoteinfrontofthelist.ThisquotetellsClojure“donotevaluatewhatcomesnext,justreturnit.”ThequoteisnecessarybecauselistsarespecialinClojure.WhenClojureevaluatesalist,ittriestointerpretthefirstelementofthislistasafunction(ormacro)andtheremainderofthelistasarguments.
加,减,乘,比较
Manymathematicalandcomparisonoperatorshavethenamesandsemanticsthatyouwouldexpectfromotherprogramminglanguages.Addition,subtraction,multiplication,comparison,andequalityallworkasyouwouldexpect:(-105)
->5
(*31010)
->300
(>52)
->true
(>=55)
->true
(<52)
->false
(=52)
->false
除法
Divisionmaysurpriseyou,Asyoucansee,Clojurehasabuilt-inRatiotype.Ifyouactuallywantdecimaldivision,useafloating-pointliteralforthedividend:(/227)
->22/7
(/22.07)
->3.142857142857143
Ifyouwanttosticktointegers,youcangettheintegerquotientandremainderwithquot()andrem():
(quot227);整除
->3
(rem227);余数
->1
StringsandCharacters
Stringsareanotherkindofreaderform.ClojurestringsareJavastrings.Theyaredelimitedby"(双引号),andtheycanspanmultiplelines:
"Thisisanmultilinestring"
->"Thisisanmultilinestring"
直接调用java接口
ClojuredoesnotwrapmostofJava’sstringfunctions.Instead,youcancallthemdirectlyusingClojure’sJavainteropforms:(.toUpperCase"hello")
->"HELLO"
Thedot(句号)beforetoUpperCasetellsClojuretotreatitasthenameofaJavamethodinsteadofaClojurefunction.