Io简介
大多数的Io社区都致力于将Io作为带有微型虚拟机和丰富并发特性的可嵌入语言来推广。
Io的核心优势是拥有大量可定制的语法和函数,以及强有力的并发模型。
在Io中,万事万物皆为消息,且每条消息都灰返回另一接受消息的对象。Io这门语言没有关键字,有的只是少量在行为上接近于关键字的字符。
对象、原型和继承
Io是一门原型语言,所有的对象都有原型,对象还带有槽(slot,相当于类的域和方法??)。
槽的相关操作
:=
当槽不存在,Io会创建一个槽,然后赋值=
给槽赋值,如果槽不存在,抛出异常::=
新建槽- type 任何对象都有type这个槽,返回对象的原型
- slotNames返回对象的槽名列表
- getSlot(name)获取槽的内容,如果槽不存在会获取父对象的槽
Io的类型是一个非常好的机制。从惯用的角度说,以大写开开头的对象时类型,因此Io会对它设置type槽。而类型的复制品若以小写开头则会调用它父对象的type槽。
方法
方法是槽的一种?
方法也是对象,和其它类型的对象一样,你可以获取它的类型。
Io>method() type
==>Block
Lobby是主命名空间(相当于浏览器中javascript的window?),包含所有已命名的对象。
Io> Lobby
Protos = Object_0x1dac868
_ = Object_0x1dac8c8
exit = method(...)
forward = method(...)
set_ = method(...)
- 所有事物都是对象;
- 所有与对象的交互都是消息
- 你要做的不是实例化类,而是复制那些叫做原型的对象
- 对象会记住它的原型
- 对象有槽
- 槽包含对象(包括方法对象)
- 消息返回槽中的值,或调用槽中的方法
- 如果对象无法响应某消息,它则会把消息发送给自己的原型
单例
true、false和nil都是单例(singleton),对它们进行复制,返回的只是单例对象的值。构建单例只需重定义clone方法,让它返回单例对象自身即可。
第一天自习
-
对1+1求值,然后对1+“one"求值。Io是强类型还是弱类型?用代码证实你的答案。
Io> 1+1 ==> 2 Io> 1+"one" Exception: argument 0 to method '+' must be a Number, not a 'Sequence' --------- message '+' in 'Command Line' on line 1 Io> "1"+"one" Exception: Io Assertion 'operation not valid on non-number encodings' --------- message '+' in 'Command Line' on line 1 Io> "1" .. "one" ==> 1one Io> 1 .. "one" ==> 1one
-
0是true还是false?空字符串是true还是false?nil是true还是false?用代码证实你的答案。
Io> if( 0 , true println,false println) true ==> true Io> if( "" , true println,false println) true ==> true Io> if( nil , true println,false println) false ==> false
-
如何知道某个原型具有哪些槽?
Io> Sequence slotNames foreach(println) log linePrint beforeSeq pathComponent urlDecoded removeSeq bitwiseAnd removeOddIndexes uppercase findSeq replaceSeq cPrint logicalOr ...
-
=、:=、::=
之间有什么区别?你会在什么时候使用它们?Io> OperatorTable ==> OperatorTable_0x951180: Operators 0 ? @ @@ ... Assign Operators ::= newSlot := setSlot = updateSlot
实际测试没有发现::=与:=的区别。
-
从文件中运行Io程序。
D:\IoCodes>cat sum.io sum := 0 for(i,1,100, sum = sum + i ) sum println D:\IoCodes>io sum.io 5050
运算符
Io> OperatorTable addOperator("xor",11)
==> OperatorTable_0x1d61180:
Operators
...
10 && and
11 or xor ||
12 ..
13 %= &= *= += -= /= <<= >>= ^= |=
14 return
Assign Operators
::= newSlot
:= setSlot
= updateSlot
To add a new operator: OperatorTable addOperator("+", 4) and implement the + message.
To add a new assign operator: OperatorTable addAssignOperator("=", "updateSlot") and implement the updateSlot message.
Io> true xor := method(bool,if(bool,false,true))
==> method(bool,
if(bool, false, true)
)
Io> false xor := method(bool,if(bool,true,false))
==> method(bool,
if(bool, true, false)
)
Io> true xor false
==> true
消息
在Io中,几乎一切都是消息。一个消息由三部分组成:发送者(sender)、目标(target)、参数(arguments)。你可以用call方法访问任何消息的元信息。
//msg.io
MsgReceiver := Object clone
MsgReceiver receive := method(
"message name is : " print
call message name println
"sender is :" println
call sender println
"target is :" println
call target println
"arguments is :" println
call message arguments println
"arguments[0] is : " print
call message argAt(0) println
"arguments[0] is : " print
call argAt(0) type println
"arguments[0] is : " print
call evalArgAt(0) type println
)
MsgSender := Object clone
MsgSender send := method(
"message name is : " print
call message name println
"sender is :" println
call sender println
"target is :" println
call target println
receiver := MsgReceiver clone
receiver receive("hollo world!")
)
sender := MsgSender clone
sender send
//result:
message name is : send
sender is :
Object_0x44c890:
Lobby = Object_0x44c890
MsgReceiver = MsgReceiver_0x2171178
MsgSender = MsgSender_0x2171238
Protos = Object_0x44c830
_ = nil
exit = method(...)
forward = method(...)
sender = MsgSender_0x21712c8
set_ = method(...)
target is :
MsgSender_0x21712c8:
message name is : receive
sender is :
MsgSender_0x21712c8:
target is :
MsgReceiver_0x217c368:
arguments is :
list("hollo world!")
arguments[0] is : "hollo world!"
arguments[0] is : Message
arguments[0] is : Sequence
[Finished in 0.2s]
可以看到,send方法的发送者是全局对象Lobby。 evalArgAt和argAt是两者间的区别:
evalArgAt(argNumber)
Evaluates the specified argument of the Call’s message in the context of it’s sender.argAt(argNumber)
Returns the message’s argNumber arg. Shorthand for same as call message argAt(argNumber).
大多数语言都将参数作为栈上的值传递,但是Io不是这样。Io传递的是消息本身和上下文,在由接受者对消息求值。实际上你可以用消息实现控制结构。
反射
在Io中,处理反射分为两个部分。在邮局那个例子中,是消息反射。对象反射是处理对象和对象的槽。
Object ancestors := method(
prototype := self proto
if(prototype !=Object,
writeln("Slots of ",prototype type,"\n---------------------")
prototype slotNames foreach(slotName, writeln(slotName))
writeln
prototype ancestors))
Animal := Object clone
Animal speak := method(
"ambigulous animal noise" println
)
Duck := Animal clone
Duck speak := method(
"quack" println
)
Duck walk := method(
"waddle" println
)
disco := Duck clone
disco ancestors
disco walk
disco speak
***************output:*******************
Slots of Duck
---------------------
walk
speak
type
Slots of Animal
---------------------
speak
type
waddle
quack
第二天自习
-
计算斐波那契数列的递归和循环两种方法:
fib := method(n, if(n ==1 or n == 2) then( return 1 ) else ( return (fib(n-1) + fib(n-2)) ) ) fib2 := method(n, if(n == 1) then ( return 1 ) else ( prv1 := 1 prv2 := 0 cur := 0 for(i,2,n, cur = prv1 + prv2 prv2 = prv1 prv1 = cur ) return cur ) )
-
在分母为0的情况下如何让运算符/返回0?
Number setSlot("coreDivision", Number getSlot("/")) Number / = method(n, if(n==0) then( return 0 )else( return (self coreDivision(n)) ) ) (4/0) println (8/4) println (16/8) println (16 / -8) println
-
写一个程序,把二维数组的所有数相加
Io中没有Array这个原型,所以用List代替数组sum := method(arr, if(arr type != "List") then( return 0 ) sum := 0 arr foreach(e, if(e type != "List") then( continue ) if(e size < 2) then( continue ) sum = sum + e at(0) sum = sum + e at(1) ) return sum ) sum(list(list(3,4),list(5))) println sum(list(7,8)) println ************output************* 7 0
-
对列表增加一个名为myAverage的槽,以计算所有数字的平均值。
List myAverage := method( if(self isEmpty) then( return 0 ) sum := 0 self foreach(e, if(e type != "Number") then( Exception raise("List member is not a number") ) sum = sum + e ) avg := sum / self size ) list() myAverage println list(3,4,5,1.2) myAverage println
-
对二维矩阵写一个原型。该原型dim的方法可为一个包含y个列表的列表分配内存,其中每个列表有x个元素,set(x,y) 方法可以设置类别中的值,get(x,y)方法可返回列表中的值。
TwoDArray := Object clone TwoDArray dim := method(x,y, self data := list() for(i,0,x-1, innerList := list() for(j,0,y-1, innerList append(nil) ) data append(innerList) ) ) TwoDArray set := method(x,y,e, outterList := self data if(x > outterList size, return) innerList := outterList at(x) if(y > innerList size, return) innerList atPut(y,e) ) TwoDArray get := method(x,y, outterList := self data if(x > outterList size, return nil) innerList := outterList at(x) if(y > innerList size, return nil) innerList at(y) ) arr2 := TwoDArray clone arr2 dim(3,4) arr2 set(1,2,3) arr2 set(1,10,3) arr2 get(1,2) println arr2 get(1,10) println
dim(x,y)分配了包含x个列表的列表,每个子列表有y个元素,与题目有点不同,但觉得这样更好理解。 代码没有对数组下标越界抛出异常,抛出异常应该更好。
-
写一个转置方法,是原列表上的matrix get(x,y)与转置后的列表(new_matrix get(y,x))相等。
TwoDArray transpose := method( outterList := self data y := outterList size innerList := outterList at(0) x := innerList size ret := TwoDArray clone ret dim(x, y) for(i,0,x-1, for(j,0,y-1, ret set(i, j, self get(j, i)) ) ) return ret ) arr2 := TwoDArray clone arr2 dim(3,4) arr2 set(1,2,3) arr2 set(1,10,3) arr2 get(1,2) println arr2 get(1,10) println "-----------" println arr3 := arr2 transpose arr3 get(2,1) println arr3 get(10,1) println
-
将文件写入矩阵,并从文件读取矩阵
TwoDArray writeMatrix := method(filePath, f := File with(filePath) f remove f openForUpdating outterList := self data x := outterList size y := outterList at(0) size f write((x .. "," .. y .. "\n")) outterList foreach(e, line := "" e foreach(e2, line = line .. e2 .. "," ) f write(line exSlice(0,-1),"\n") ) f close ) TwoDArray readMatrix := method(filePath, f := File with(filePath) f openForReading line := f readLine d := line split(",") x := d at(0) asNumber y := d at(1) asNumber ret := TwoDArray clone ret dim(x,y) writeln("x=",x,", y=",y) i := 0 j := 0 f readLines foreach(line, list := line split(",") j = 0 list foreach(e, writeln(i,"x",j,"=",e) if(e != "nil",ret set(i,j,e)) j = j + 1 ) i = i +1 ) f close return ret ) TwoDArray writeMatrix2 := method(filePath, f := File with(filePath) f remove f openForUpdating f write(self serialized()) f close ) TwoDArray readMatrix2 := method(filePath, ret := doFile(filePath) return ret ) arr2 := TwoDArray clone arr2 dim(3,4) arr2 set(1,2,3) arr2 set(1,10,3) arr2 get(1,2) println arr2 get(1,10) println "-----------" println arr3 := arr2 transpose arr3 get(2,1) println arr3 get(10,1) println arr3 writeMatrix("./test.dt") arr4 := TwoDArray readMatrix("./test.dt") arr4 println arr4 get(2,1) println arr4 get(10,1) println arr3 writeMatrix2("./test2.dt") arr5 := TwoDArray readMatrix2("./test2.dt") arr5 println arr5 get(2,1) println arr5 get(10,1) println
-
写一个程序,提供10次尝试机会,猜一个1~100之间的随机数。如果
GuessNumGame := Object clone GuessNumGame init := method( self secretNum := Random value(1,101) floor self guessTimes := 0 self lowBoundry := 0 self highBoundry := 100 secretNum println ) GuessNumGame guess := method(x, guessTimes println if(guessTimes > 10) then( "You had guess over 10 times! Game over!" println return ) if(x < secretNum) then( if(x > lowBoundry,lowBoundry = x) writeln("You guess lowwer. You can guess between ",lowBoundry," to ",highBoundry) ) elseif(x == self secretNum) then( writeln("Congratulations! You got the right number!") ) else( if(x < highBoundry, highBoundry = x) writeln("You guess higher. You can guess between ",lowBoundry," to ",highBoundry) ) guessTimes = guessTimes + 1 ) game := GuessNumGame clone game guess(10) game guess(50)
领域特定语言
几乎每一个研究过Io语言的人,都会对它在DSL方面的强大赞不绝口。下面实现一种有趣的电话号码语法的API。
比如:
{
"Bob smith":"5195551212",
"Mary Walsh":"4162223434"
}
解决这一个问题的办法:
OperatorTable addAssignOperator(":","atPutNumber")
curlyBrackets := method(
r := Map clone
call message arguments foreach(arg,
r doMessage(arg)
)
r
)
Map atPutNumber := method(
self atPut(
call evalArgAt(0) asMutable removePrefix("\"") removeSuffix("\""),
call evalArgAt(1)
)
)
s := File with("phonebook.txt") openForReading contents
phoneNumbers := doString(s) //doString把电话号码簿求值为Io代码
phoneNumbers keys println
phoneNumbers values println
Io的method_messing
就像Ruby的method_missing那样,你也可以用Io的forward消息做到同样的事,但是这样做的风险会高一些。Io没有类,所以改变forward也将改变从Object获得的基本行为方式。
XML是对数据进行结构化的绝妙方式,但是却有着令人作呕的语法。为了摆脱这语法,你可以写一个程序,用Io代码来表示XML数据。
假如你想把下面的数据:
<body>
<p>
This is a simple paragraph.
</p>
</body>
表示成:
body(
p("This is a simple paragraph.")
)
我们把这种新语言称作LispML。我们将用Io的forward处理这门语言,就像处理不存在的方法一样(missing_method)。
Builder := Object clone
Builder forward := method(
writeln("<",call message name,">")
call message arguments foreach(arg,
content := self doMessage(arg)
if(content type == "Sequence", writeln(content))
)
writeln("</",call message name,">")
)
Builder ul(
li("IO"),
li("Lua"),
li("Javascript")
)
并发
Io有非常出色的并发库,其主要组成部分包括协程、actor和future。
1.协程
协程是并发的基础。它提供了进程自动挂起和恢复执行的机制。你可以把协程想象成有多个入口和出口的函数。每次yield都会自动挂起当前进程,并把控制转到另一个进程中。 通过在消息前加上@或@@,你可以异步触发消息,前者将返回future,后者会返回nil,并在其自身线程中触发消息。
vizzini := Object clone
vizzini talk := method(
"Fezzik, are there rooks ahead?" println
yield
"No more rhymes now, I mean it." println
yield
)
fezzik := Object clone
fezzik rhyme := method(
yield
"If there are, we'll all be dead." println
yield
"Anybody want a peanut?" println
)
vizzini @@talk
fezzik @@rhyme
Coroutine currentCoroutine pause
协程是组成更高级抽象概念(如actor)的基本元素。你可以把actor想象成通用的并发原语,它可以发送消息、处理消息以及创建其它actor。actor接收到的消息是并发的。在Io中,actor把新到达的消息放到队列上,并用协程处理队列中的各个消息。
2.Actor
和线程相比,actor有巨大的理论优势。一个actor可以改变其自身状态,并且通过严格控制的队列接触其它actor。而多个线程可以不受限制地改变状态。线程容易接受到被称为竞争条件的并发影响。在这种问题中,如果两个线程同时存取资源,可能导致不可预测的后果。
Io的动人之处就在于此,发送异步消息给任何对象就是actor,就这么简单。举一个例子:
slower := Object clone
faster := Object clone
slower start := method(
wait(2)
writeln("slowly")
)
faster start := method(
wait(1)
writeln("quickly")
)
slower start
faster start
"======================" println
slower @@start
faster @@start
Coroutine currentCoroutine pause
3.future
在Io中,future并不是代理实现。future会阻塞到可获得结果为止。future的值一开始是个future对象,但等到结果产生之后,所有future值的实例都会指向结果对象。
futureResult := URL with("http://google.com/") @fetch
writeln("doing other thing ...")
wait(2)
writeln("fetched," futureResult size," bytes")
在windows下运行上面代码出现Exception: Object does not respond to ‘URL’异常,google了一下,未能解决,望高手指点。
第三天自习
-
改进本节生成的XML程序,增加空格以显示缩进结构。
Builder := Object clone Builder indentSize := 4 Builder i := 0 Builder forward := method( (i * indentSize) repeat(write(" ")) writeln("<",call message name,">") i = i + 1 call message arguments foreach(arg, content := self doMessage(arg) if(content type == "Sequence", (i * indentSize) repeat(write(" ")) writeln(content) ) ) i = i -1 (i * indentSize) repeat(write(" ")) writeln("</",call message name,">") ) Builder ul( li(p("IO")), li("Lua"), li("Javascript") )
-
创建一种使用括号的列表语法
squareBrackets := method( if(call message arguments asString containsSeq("squareBrackets") not,return call message arguments) //如果不含子数组就直接返回参数列表,参数列表本来就是一个list list := List clone call message arguments foreach(arg, aArg := if(arg asString beginsWithSeq("squareBrackets"),doMessage(arg),arg) list append(aArg)// 最后一行为返回值 ) list ) [] println [1,"kk"] println [1,["kk",10.5]] println
-
改进本节生成xml程序,使其可以处理属性:如果第一个参数时映射(用大括号语法),则为xml添加属性。例如:
book({"author":"Tate"}...)
将打印出<book author="Tate">
OperatorTable addAssignOperator(":","addAttribute") Builder := Object clone Builder addAttribute := method( call message arguments println write(call evalArgAt(0)) write("=\"",call evalArgAt(1),"\"") ) Builder curlyBrackets := method( call message arguments foreach(arg, self doMessage(arg) ) ) Builder indentSize := 4 Builder i := 0 Builder forward := method( (i * indentSize) repeat(write(" ")) write("<",call message name) i = i + 1 args :=call message arguments arg1 := args at(0) if( arg1 asString beginsWithSeq("curlyBrackets"), //arg1 println self doMessage(arg1) args remove(arg1) ) writeln(">") args foreach(arg, content := self doMessage(arg) if(content type == "Sequence", (i * indentSize) repeat(write(" ")) writeln(content) ) ) i = i -1 (i * indentSize) repeat(write(" ")) writeln("</",call message name,">") ) Builder ul( {"class" : "foo","id" : "language"}, li("Io") )
上面代码会出现异常,不知道如何解决。
<ul
Exception: Sequence does not respond to ':'
---------
Sequence : builder.io 50
Builder curlyBrackets builder.io 50
Builder ul builder.io 49
CLI doFile Z_CLI.io 140
CLI run