您的位置:首页 > 其它

Clojure - 基本语法

2013-01-22 18:03 507 查看
http://clojuredocs.org/,在线Clojure语法例子

InstallingClojure

Clojureisanopen-sourceprojecthostedatgithub.com.gitclonehttps://github.com/clojure/clojure.gitThiswilldownloadthecodefromthemasterbranchintotheclojuredirectoryinyourworkspace.ClojureisaJavaproject,anditusestheAntbuildsystem.antRunningthiscommandwillleaveyouwithanappropriateClojureJARfile.OpenREPL,java–jar/path/to/clojure.jar

TheClojureREPL

REPL,命令行工具

user=>(defnhello[name](str"Hello,"name)) user=>(hello"Stu") Hello,Stu user=>(hello"Clojure") Hello,Clojure (str*1"and"*2) "Hello,ClojureandHello,Stu"[/code]

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这样最大的好处,就是非常便于generateandmanipulatecode

CaseSensitive

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(Programmingclojure–Concurrency)

Rootbinding

Whenyoudefineanobjectwithdefordefn,thatobjectisstoredinaClojurevar.Forexample,thefollowingdefcreatesavarnameduser/foo:
(deffoo10)
#'user/foo

Youcanrefertoavardirectly.Thevar(#')specialformreturnsavaritself,notthevar’svalue:
(vara-symbol)

Youcanusevartoreturnthevarboundtouser/foo:
(varfoo)
#'user/foo
#'foo
#'user/foo
比如可以用于取var自身的metadata,(meta#'str)通过def定义的var,是一种rootbinding,就是globle的,各个线程都能看到Rootbindingsalsobindnamestofunctions.Whenyoucalldefn(封装的def),itusesdefinternally.Sofunctionnamesliketriplebelowarerootbindings.
(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,alistofdirectoriesfromwhichto
loadexecutablecode.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-macro
add: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.

str

(str12nil3)
->"123"

Theexampleabovedemonstratesstr’sadvantagesovertoString().Itsmashestogethermultiplearguments,anditskipsnilwithouterror.Clojure的字符和Java字符一样,String就是字符序列,所以clojure的序列function可以直接用于stringClojurecharactersareJavacharacters.
Theirliteralsyntaxis\{letter},wherelettercanbealetter,ornewline,space,ortab.
Stringsaresequencesofcharacters.WhenyoucallClojuresequencefunctionsonaString,yougetasequenceofcharactersback.
(interleave"Attackatmidnight""Thepurpleelephantchortled");得到的是characterlist
->(\A\T\t\h\t\e\a\space\c\p\k\u\space\r
\a\p\t\l\space\e\m\space\i\e\d\l\n\e
\i\p\g\h\h\a\t\n)

(applystr(interleave"Attackatmidnight""Thepurpleelephantchortled"));通过str转化
->"ATthteacpkuraptlemiedlneipghhatn"

BooleansandNil,比较严格,没有python方便

Clojure’srulesforbooleansareeasytounderstand:
•trueistrueandfalseisfalse.
•Onlyfalse,nilevaluatestofalsewhenusedinabooleancontext.
•Otherthanfalseand,nil,everythingelseevaluatestotrueinabooleancontext.注意在booleancontext下,除了false和nil以外,全是true.仅仅在booleancontext下适用,特别注意!!!
user=>(if'()"T""F")
"T"
user=>(if0"T""F")
"T"
user=>(if1"T""F")
"T"
user=>(ifnil"T""F")
"F"
user=>(iffalse"T""F")
"F"
对于commonLisp,空list为false,但是在clojure中都是true,特别注意!!!
(if'()"WeareinClojure!""WeareinCommonLisp!")
->"WeareinClojure!"

true?,false?,andnil?

Clojureincludesasetofpredicatesfortestingtrue?,false?,andnil?这儿要小心的是,true?,这个断言,只有在真正是true的时候才会返回true(因为不在booleancontext)
(true?true)
->true
(true?"foo")
->false

所以对于下面的filter,你如果想当然会返回[11235],错,只会范围nil,因为里面确实没有true
(filtertrue?[112false3nil5])
->nil
(filteridentity[112false3nil5]);这样才work
->(11235)

Maps,python中的字典

AClojuremapisacollectionofkey/valuepairs.Mapshavealiteralformsurroundedbycurlybraces.
Youcanuseamapliteraltocreatealookuptablefortheinventorsofprogramminglanguages:
(definventors{"Lisp""McCarthy""Clojure""Hickey"})

Mapsarefunction

Ifyoupassakeytoamap,itwillreturnthatkey’svalue,oritwillreturnnilifthekeyisnotfound:
(inventors"Lisp")
"McCarthy"
(inventors"Foo")
nil

Get,handlemissing

(geta-mapkeynot-found-val?)
getallowsyoutospecifyadifferentreturnvalueformissingkeys:
(getinventors"Lisp""Idunno!")
"McCarthy"
(getinventors"Foo""Idunno!")
"Idunno!"

keyword,常用于map的key,是function

BecauseClojuredatastructuresareimmutableandimplementhash-Codecorrectly,anyClojuredatastructurecanbeakeyinamap.Thatsaid,averycommonkeytypeistheClojurekeyword.Akeywordislikeasymbol,exceptthatkeywordsbeginwithacolon(:)
Keywordsresolvetothemselves:
:foo
:foo
其实对于clojure,什么类型都可以作为keys,但是最常用的是keyword,它和一般symbol的区别就是以:开头,并且resolve的结果就是本身(一般var,resolve得到value),所以Keywords比较适合用作keys

Keywordsarealsofunctions.Theytakeamapargumentandlookthemselvesupinthemap.
(inventors:Clojure)
"Hickey"
(:Clojureinventors)
"Hickey"
比较有意思,map和keyword本身都可以作为函数,并且可以互相作为参数
这也是为什么使用keyword作为key的重要原因,因为keyword本身是function,所以取值非常方便

struct,预定义map的keys

Ifseveralmapshavekeysincommon,youcandocument(andenforce)thisfactbycreatingastructwithdefstruct:
(defstructname&keys)
defstruct其实就是可以定义map的keys,这样在创建map的时候,不需要重复写key,见下面例子
但是这个名字真的起的不好,讨好c程序员?定义struct,很confuse

(defstructbook:title:author)
(defb(structbook"Anathem""NealStephenson"))
b
{:title"Anathem",:author"NealStephenson"}
Figure2.1:ClojureForms


ReaderMacros,语法糖

这是一些特殊的语法宏(macros),为了便于coding而创建的DSL,而且大部分readermacros,是有标准的函数形式的,比如,;和comment,‘和quote同时,糖好吃也是要付代价的,增加入门难度,对于初学者大量的macros大大降低可读性.而且增加语法复杂度,对于号称nosyntax的Lisp而言...所以需要balance,工具怎么样用关键在人Clojureformsarereadbythereader,whichconvertstextintoClojuredatastructures.
Inadditiontothebasicforms,theClojurereaderalsorecognizesasetofreadermacros.Readermacrosarespecialreaderbehaviorstriggeredbyprefixmacrocharacters.
Manyreadermacrosareabbreviationsoflongerlistforms,andareusedtoreduceclutter.Youhavealreadyseenoneofthese.'(12)isequivalenttothelonger(quote(12)):Figure2.2:ReaderMacros

Functions,Clojure的核心概念

InClojure,afunctioncallissimplyalistwhosefirstelementresolvestoafunction.

命名规范

Functionnamesaretypicallyhyphenated(-),asinclear-agent-errors.Ifafunctionisapredicate,thenbyconventionitsnameshouldendwithaquestionmark.约定俗成,便于代码理解所以加上?
user=>(string?"hello")
true
user=>(keyword?:hello)
true
user=>(symbol?:hello)
true

函数定义

Todefineyourownfunctions,usedefn:
(defnnamedoc-string?attr-map?[params*]body);attr-map用于增加metadata
例子,
(defngreeting
"Returnsagreetingoftheform'Hello,name.'"
[name]
(str"Hello,"name))
(greeting"world")
->"Hello,world"
(docgreeting);查看docstring
-------------------------
exploring/greeting
([name])
Returnsagreetingoftheform'Hello,name.'

严格参数个数

Clojurefunctionsenforcetheirarity,thatis,theirexpectednumberofarguments.
Ifyoucallafunctionwithanincorrectnumberofarguments,ClojurewillthrowanIllegalArgumentException.
(greeting)
->java.lang.IllegalArgumentException:\
Wrongnumberofargspassedto:greeting(NO_SOURCE_FILE:0)

定义多组参数,类似[b]函数重载[/b]

(defnnamedoc-string?attr-map?([params*]body)+);最后的+表明可以定义多组([params*]body)

(defngreeting
"Returnsagreetingoftheform'Hello,name.'
Defaultnameis'world'."
([](greeting"world"))
([name](str"Hello,"name))
([greeting-prefixname](strgreeting-prefix""name)))这样可以解决上面不给参数的问题,也可以多参数...简单的实现重载的概念

user=>(greeting)
"Hello,world"
user=>(greeting"hi""df")
"hidf"

可变参数,variablearity

Youcancreateafunctionwithvariablearitybyincludinganampersand(&)intheparameterlist.Clojurewillbindthenameaftertheampersandtoalistofalltheremainingparameters.
(defndate[person-1person-2&chaperones]
(printlnperson-1"and"person-2
"wentoutwith"(countchaperones)"chaperones."))
(date"Romeo""Juliet""FriarLawrence""Nurse")
RomeoandJulietwentoutwith2chaperones.

AnonymousFunctions

Inadditiontonamedfunctionswithdefn,youcanalsocreateanonymousfunctionswithfn.
(fn[params*]body)

为什么需要匿名函数?

Thereareatleastthreereasonstocreateananonymousfunction:
•Thefunctionissobriefandself-explanatorythatgivingitanamemakesthecodehardertoread,noteasier.
•Thefunctionisonlybeingusedfrominsideanotherfunction,andneedsalocalname,notatop-levelbinding.
•Thefunctioniscreatedinsideanotherfunction,forthepurposeofclosingoversomedata.

例子

我们用下面的代码滤出长度大于2的word,可以如下实现
(defnindexable-word?[word]
(>(countword)2))
(use'clojure.contrib.str-utils);forre-split,breaksthesentenceintowords
(filterindexable-word?(re-split#"\\W+""Afinedayitis"))
->("fine""day")

第一种用法,fn使表达更简单
(filter(fn[w](>(countw)2))(re-split#"\\W+""Afineday"))

更简洁的匿名函数表示方法,Thereisanevershortersyntaxforanonymousfunctions,usingimplicitparameternames.Theparametersarenamed%1,%2,etc.,orjust%ifthereisonlyone.Thissyntaxlookslike:#body
(filter#(>(count%)2)(re-split#"\\W+""Afinedayitis"))
第二种用法,仅被用于某函数内部的函数,并且需要Localname(理由是可能被调多次,或使代码简化)
(defnindexable-words[text]
(let[indexable-word?(fn[w](>(countw)2))];定义匿名函数,并绑定给indexable-word?
(filterindexable-word?(re-split#"\\W+"text))))

Thecombinationofletandananonymousfunctionsaystoreadersofyourcode:"Thefunctionindexable-word?isinterestingenoughtohaveaname,butisrelevantonlyinsideindexable-words."第三种用法,用于动态的创建function,闭包
Athirdreasontouseanonymousfunctionsiswhenyoudynamicallycreatingafunctionatruntime.
(defnmake-greeter[greeting-prefix]
(fn[name](strgreeting-prefix","name)))(defhello-greeting(make-greeter"Hello"))
->#=(varuser/hello-greeting)
(defaloha-greeting(make-greeter"Aloha"))
->#=(varuser/aloha-greeting)(hello-greeting"world")
->"Hello,world"
(aloha-greeting"world")
->"Aloha,world"

FlowControl

Clojurehasveryfewflowcontrolforms.Inthissectionyouwillmeetif,do,andloop/recur.Asitturnsout,thisisalmostallyouwilleverneed.

Branchwithif,if-not

Clojure’sifevaluatesitsfirstargument.Iftheargumentislogicallytrue,itreturnstheresultofevaluatingitssecondargument:
(defnis-small?[number]
(if(<number100)"yes""no"))(is-small?50)
->"yes"
(is-small?50000)
->"no"
对于if很容易理解,唯一要注意的是,if不是一个标准的funciton,而是一种specialformTheruleforfunctionsis“evaluatealltheargs,thenapplythefunctiontothem.”
ifdoesnotfollowthisrule.Ifitdid,itwouldalwaysevaluateboththe“in”and“else”forms,regardlessofinput.InLispterminologyifiscalledaspecialformbecauseithasitsownspecial-caserulesforwhenitsargumentsgetevaluated.重要的是,除了Clojure本身内嵌的specialforms外,我们可以用macros创造自己的specialform.
这点极其总要,他使得clojure的本身语言的schema是可以扩展的(java,c都不行,你无法随便加个关键字)
所以对于clojure,它本身可以实现尽量少的语法和特殊form,然后把后面的事交给程序员去做,通过macro生成各种DSLInadditiontothespecialformsbuiltintoClojure,youcanwriteyourownspecialcaseevaluationrulesusingmacros.Macrosareextremelypowerful,becausetheymaketheentirelanguageprogrammable.Clojurecanaffordtohaveasmallsetofflowcontrolforms,becauseyoucanuseusemacrostoaddyourown.

Cond(类似switchcase)

condislikethecasestatementofClojure.Thegeneralformlookslikethefollowing:
(cond&clauses)
(defnrange-info[x]
(cond
(<x0)(println"Negative!")
(=x0)(println"Zero!")
:default(println"Positive!")))

IntroduceSideEffectswithdo

Clojure’sifallowsonlyoneformforeachbranch.Whatifyouwanttodomorethanonethingonabranch?
Forexample,youmightwanttologthatacertainbranchwaschosen.dotakesanynumberofforms,evaluatesthemall,andreturnsthelast.在if语法中,每个条件分支中都只能执行一个form,如果想执行多个form,怎么办?用do
(defnis-small?[number]
(if(<number100)
"yes"
(do
(println"Sawabignumber"number)
"no")))(is-small?200)
Sawabignumber200
->"no"
首先,在purefunction中,do其实是没用的,因为他只会返回最后一个form的结果,所以前面form的结果会直接被ignore,所以写了也白写.但在Clojure不是purefunction,引入了state和sideeffect
Printingaloggingstatementisanexampleofasideeffect.Theprintlndoesnotcontributetothereturnvalueofis-small?atall.
Instead,itreachesoutintotheworldoutsidethefunctionandactuallydoessomething.
大部分语言不会强调sideeffect,写log,写DB,IO,和一般的代码混合在一起没有区别,但这样造成了相当的复杂性.
而Clojure强调sideeffect的管理,虽然不能避免,do就是一种显示表示sideeffects的方法,因为如果这些forms没有sideeffect,那么他们不会起任何作用,所以使用do,一定表明上面的form是有sideeffects的.
当然Clojure引入sideeffect,只是不得已而为,所以应该尽量少用do.
Manyprogramminglanguagesmixpurefunctionsandsideeffectsincompletelyadhocfashion.
NotClojure.InClojure,sideeffectsareexplicitandunusual.doisonewaytosay“sideeffectstofollow.”
Sincedoignoresthereturnvaluesofallitsformssavethelast,thoseformsmusthavesideeffectstobeofanyuseatall.
Plantousedorarely,andforsideeffects,notforflowcontrol.Forthoseoccasionswhereyouneedmorecomplexcontrolflowthanasimpleif,youshoulddefinearecurrencewithloop/recur.

When,When-not

和if的不同是,没有else子句,执行条件后的所有语句例子:区别if和when,打印小于5的正整数
;仅打印1
(loop[i1]
(if(<i5)
(printlni)
(recur(inci))));else
;正确,打印1~5
(loop[i1]
(if(<i5)
(do(printlni)(recur(inci)))))
;正确when把条件判断后的所有都执行
(loop[i1]
(when(<i5)
(printlni)
(recur(inci))))

When-Not
when-notistheoppositeofwhen,inthatitevaluatesitsbodyifthetestreturnsfalse(ornil).Thegeneralformlookssimilartothatofwhen:
(when-nottest&body)

While

Clojure’swhilemacroworksinasimilarfashiontothoseseeninimperativelanguagessuchasRubyandJava.Thegeneralformisasfollows:
(whiletest&body)
Anexampleis

(while(request-on-queue?)
(handle-request(pop-request-queue)))

Recurwithloop/recur,类似for

关于loop/recur参考这个blog,/article/4893351.html
(defncountdown[resultx]
(if(zero?x)
result
(recur(conjresultx)(decx))));(countdown(conjresultx)(decx))))(defndemo-loop[]
(loop[result[]x5]
(if(zero?x)
result
(recur(conjresultx)(decx))))
)
对于这种简单的操作,Clojure’ssequencelibrary,直接可以实现,无需直接使用recur….
(into[](take5(iteratedec5)))
[54321]
(into[](drop-last(reverse(range6))))
[54321]
(vec(reverse(rest(range6))))
[54321]

Metadata

TheWikipediaentryonmetadatabeginsbysayingthatmetadatais“dataaboutdata.”
Averyspecificdefinition:metadataisamapofdataattachedtoanobjectthatdoesnotaffectthevalueoftheobjectTwoobjectswiththesamevalueanddifferentmetadataareconsideredequal(andhavethesamehashcode).
However,metadatahasthesameimmutablesemanticsasClojure'sotherdatastructures;
modifyinganobject'smetadatayieldsanewobject,withthesamevalue(andthesamehashcode)astheoriginalobject.metadata本身很容易理解,只要两个object的value相同,就算metadata不同,仍然是equal的.
对于Clojure需要注意的是,metadata仍然有不变特性,改变一个对象的metadata一样会导致创建新的对象.

with-meta

Youcanaddmetadatatoacollectionorasymbolusingthewith-metafunction:
(with-metaobjectmetadata)
Createasimpledatastructure,thenusewith-metatocreateanotherobjectwiththesamedatabutitsownmetadata:
(defstu{:name"Stu":email"stu@thinkrelevance.com"})
(defserializable-stu(with-metastu{:serializabletrue}));给map加上metadata,serializable:true,注意元数据的改变需要定义新的变量serializable-stu
Metadatamakesnodifferenceforoperationsthatdependonanobject’svalue,sostuandserializable-stuareequal:
(=stuserializable-stu)
true;因为value一样,所以是equal的
Youcanprovethatstuandserializable-stuaredifferentobjectsbycallingidentical?:
(identical?stuserializable-stu)
false;但是显然他们不是同一个对象,而是不同的对象
Youcanaccessmetadatawiththemetamacro(readermacro^),verifyingthatserializablestuhasmetadataandstudoesnot:
(metastu)
nil
(metaserializable-stu)
{:serializabletrue}
^stu
nil
^serializable-stu
{:serializabletrue}

ReaderMetadata

TheClojurelanguageitselfusesmetadatainseveralplaces.在clojure中只要加上Reader,意味着这是语言自带的(reader或编译器可识别),对于metadata,你可以任意给对象加上你自己的metadata,但是其实clojure本身也预定义了一组metadata,并且这些metadata是编译器可识别的如底下的例子,对于varstr(specialform也属于var),clojure会自动给它加上一组metadataForexample,varshaveametadatamapcontainingdocumentation,typeinformation,andsourceinformation.Hereisthemetadataforthestrvar:
(meta#'str);#'meansvar
{:ns#<Namespaceclojure.core>,
:namestr,
:file"core.clj",
:line313,
:arglists([][x][x&ys]),
:tagjava.lang.String,
:doc"Withnoargs,...etc."}
同时,你可以usethemetadatareadermacro来修改这些metadata
#^metadataform

下面再给个例子,定义函数的时候,指定参数var的metadata,tag:string
编译器就会知道并检查参数,当你传入int,会报错
user=>(defn#^{:tagString}shout[#^{:tagString}s](.toUpperCases))user=>(metashout)
{:ns#<Namespaceuser>,:nameshout}
user=>(meta#'shout)
{:ns#<Namespaceuser>,:nameshout,:file"NO_SOURCE_PATH",:line38,:arglists([s]),:tagjava.lang.String}(shout1)
java.lang.ClassCastException:\
java.lang.Integercannotbecasttojava.lang.String
下面列出clojure支持的readermetadata

with-meta和readermetadata差别

这个#^和with-meta有什么区别?看下面的例子metadata分两种,用户自定义的metadata,可以说是给value加上的metadata,这个只有用户理解,用户可以任意增加和修改.需要使用with-meta.clojure系统定义的metadata,可以说是给var本身加上的metadata,这个metadata是给编译器看的,用户可以修改,使用#^.可以增加吗?应该不行,加也没用,系统不认user=>(metaserializable-stu);value的metadata
{:serializabletrue}
user=>(meta#'serializable-stu);var的metadata
{:ns#<Namespaceuser>,:nameserializable-stu,:file"NO_SOURCE_PATH",:line2}

TypeHinting

Clojure是动态语言,但是为了提高效率,可以显式定义typehint
如下面的例子,定义参数text的typehint为^string
但是定义typehint本身不会限制类型,可见下面例子,虽然定义typehint为List,但是真正的参数可以是anything类型
The^ClassNamesyntaxdefinesatypehint,anexplicitindicationtotheClojurecompileroftheobjecttypeofanexpression,varvalue,oranamedbinding.
(defnlength-of
[^Stringtext]
(.lengthtext))

ThesehintsareusedbythecompileronlytoavoidemittingreflectiveinteropcallsTypehintsonfunctionargumentsorreturnsarenotsignaturedeclarations:theydonotaffectthetypesthatafunctioncanacceptorreturn.
(defnaccepts-anything
[^java.util.Listx]
x)
;=#'user/accepts-anything
(accepts-anything(java.util.ArrayList.))
;=#<ArrayList[]>
(accepts-anything5)
;=5
(accepts-anythingfalse)
;=false
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: