|
|
||
オブジェクト指向の“モヤッと”の正体を知りたくなったら、id:sumim:20040525:p1 がお役に立つかも…。
Smalltalk が普通に入っているのがすばらしいので、すこしだけ目立ち気味の空欄を GNU Smalltalk、Squeak、VisualWorks で項目を分けてからざっと調べて分かる範囲で埋めてみた―の巻の後半部分です。GSTer、VWer は、訂正・補足をお願いします。もちろん Squeak使いからのツッコミも歓迎です。
| libraries and modules | ||||
|---|---|---|---|---|
| > | ruby | GNU Smalltalk | Squeak | VisualWorks |
| import library | require | PackageLoader fileInPackage: 'PackageName' PackageLoader fileInPackages: #('Package1' 'Package2') |
use SqueakMap Package Loader or drag’n drop package file to desktop or (Installer file: 'path/to/package/file') install |
use Parcel Manager and Load Parcel Named... command or ParcelManag |
| library path | $: | ?? | SMSqueakMap default directory | UISettings preferenceFor: #parcelPath |
| library path environment variable | RUBYLIB | SMALLTALK_MODULES | none | none |
| library path command line option | -I | none, use gst-load script | none | -filein |
| module declaration | class Foo or module Foo | write package.xml and use gst-package script | use Monticello | Store.Registry packageNamedOrCreate: 'Foo' |
| module separator | :: | . (period) | none | . (period) |
| package management | $ gem list $ gem install rails |
PackageLoader | SMSqueakMap, Monticello | ParcelManager, StORE, Monticello |
| objects | ||||
| > | ruby | GNU Smalltlk | Squeak | VisualWorks |
| define class | class Int attr_accessor :value def initialize(i=0) @value = i end end |
Object subclass: Int [ | val | initialize [ val := 0 ] val [^val] val: i [val := i] Int class >> new [ ^super new initialize; yourself ] Int class >> new: i [ ^self new val: i; yourself ] ] |
in class browser Object subclass: #Int instanceVariableNames: 'val' classVariableNames: '' poolDictionaries: '' category: 'Category-Name' use browser’s "create inst var accessors" command Int >> initialize super initialize val := 0 Int clas >> new: i ^self new val: i; yourself |
use class browser’s New Class... dialog and set Create Methods: options for creating isnt var asscessors in class browser Int >> initialize super initialize. val := 0. ^self Int clas >> new: i ^self new initialize val: i; yourself |
| create object | i = Int.new i = Int.new(4) |
i := Int new i := Int new: 4 |
i := Int new i := Int new: 4 |
i := Int new i := Int new: 4 |
| invoke getter, setter | v = i.value i.value = v+1 |
v := i val. i val: v + 1 |
v := i val. i val: v + 1 |
v := i val. i val: v + 1 |
| instance variable accessibility | private by default; use attr_reader, attr_writer, attr_accessor to make public | private by default; create accessors to make public | private by default; create accessors to make public | private by default; create accessors to make public |
| define method | in class body: def plus(i) value + i end |
Int extend [ plus: i [^val + i] ] |
in class browser: Int >> plus: i ^val + i |
in class browser: Int >> plus: i ^val + i |
| invoke method | i.plus(7) | i plus: 7 | i plus: 7 | i plus: 7 |
| cascading | none | i val: 4; plus: 7 | i val: 4; plus: 7 | i val: 4; plus: 7 |
| destructor | val = i.value ObjectSpace.define_finalizer(i) { puts "bye, #{val}" } |
i addToBeFinalized define finalize method beforehand Int extend [ finalize [ ('bye, ', val printString) displayNl. super finalize] ] |
WeakRegistry default add: i define finalize method beforehand Int >> finalize Transcript cr; show: 'bye, ', val printString. super finalize
|
weakly := i withLastRites: [:obj | Transcript show: 'bye, ', obj val printString; cr] |
| method missing | in class body: def method_missing(name, *a) puts "no def: #{name}" + " arity: #{a.size}" end |
Int extend [ doesNotUnderstand: msg [ | sel args | sel := msg selector. args := msg arguments. ('no def: ', sel, ' args: ', args printString) displayNl] ] |
Int >> doesNotUnderstand: msg Transcript cr; show: ( 'no def: {1} args: {2}' format: {msg selector. msg arguments}) |
Int >> doesNotUnderstand: msg | sel args | sel := msg selector. args := msg arguments. Transcript cr; show: ('no def: <1s> args: <2p>' expandMacrosWith: sel with: args) |
| inheritance | class Counter < Int @@instances = 0 def initialize @@instances += 1 super end def incr self.value += 1 end def self.instances @@instances end end |
Int subclass: Counter [ incr [val := val + 1] ] Counter class extend [ Insts := 0. insts [^Insts] ] Counter extend [ initialize [ super initialize. Insts := Insts + 1 ] ] |
use class browser Int subclass: #Counter instanceVariableNames: '' classVariableNames: 'Insts' poolDictionaries: '' category: 'Category-Name' Counter class >> initialize Insts := 0 Counter class >> insts ^Insts Counter >> initialize super initialize. Insts := Insts + 1 Counter >> incr val := val + 1 don’t forget to invoke the class initializer Counter initialize |
use class browser’s New Class... dialog and create a shared variable Insts in Shared Variable tab in class browser Count class >> initialize Insts := 0 Counter class >> insts ^Insts Counter >> initialize super initialize. Insts := Insts + 1. ^self Counter >> incr val := val + 1 don’t forget to invoke the class initializer Counter initialize |
| invoke class method | Counter.instances | Counter insts | Counter insts | Counter insts |
| reflection and hooks | ||||
| > | ruby | GNU Smalltalk | Squeak | VisualWorks |
| class | a.class | a class | a class | a class |
| has method? | a.respond_to?('reverse') | a respondsTo: #reverse | a respondsTo: #reverse | a respondsTo: #reverse |
| message passing | (1..9).each { |i| a.send("phone#{i}=", nil) } | (1 to: 9) do: [:i | a perform: ('phone', i printString, ':') asSymbol with: nil] |
(1 to: 9) do: [:i | a perform: ('phone', i printString, ':') asSymbol with: nil] |
(1 to: 9) do: [:i | a perform: ('phone', i printString, ':') asSymbol with: nil] |
| eval | loop do puts eval(gets) end |
[Object evaluate: stdin nextLine, ' displayNl' ifError: [ repeat |
[Transcript cr; show: (Compiler evaluate: (FillInTheBlank request: '')) ] repeat
|
[Transcript cr; show: (Compiler evaluate: (Dialog request: '')) ] repeat |
| methods | a.methods | a class selectors | a class selectors | a class selectors |
| attributes | a.instance_variables | a class instVarNames | a class instVarNames | a class instVarNames |
| pretty print | require 'pp' h = { 'foo'=>1, 'bar'=>[2,3] } pp h |
none | none | none |
| source line number and file name | __LINE__ __FILE__ |
(Integer >> #factorial) methodSourcePos (Integer >> #factorial) methodSourceFile you can also get method source code (Counter >> #incr) methodSourceCode |
(Integer >> #factorial) fileIndex (Integer >> #factorial) filePosition you can also get method source code (Counter >> #incr) getSource |
(Integer compiledMethodAt: #factorial) fileIndex (Integer compiledMethodAt: #factorial) filePosition you can also get method source code (Integer compiledMethodAt: #factorial) getSource |
| web | ||||
| ruby | GNU Smalltalk | Squeak | VisualWorks | |
| http get | require 'net/http' h = 'www.google.com' r = Net::HTTP.start(h, 80) do |f| f.get('/') end r.body |
h := 'www.google.com:80'. r := HTTPSocket httpGetDocument: h. r contents |
||
| url encode/decode | require 'cgi' CGI::escape('hello world') CGI::unescape('hello+world') |
none | ||
| create repo and start server | $ rails foo $ cd foo $ ./script/server |
WAKom startOn: 8888. WAComponent subclass: #Foo instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Seaside-App' Foo registerAsApplication: 'foo' |
||
| web framework | Rails | Seaside | Seaside | Seaside |
| object relational mapper | ActiveRecord | DBI | GLORP | GLORP |
| templates | ERB | ?? | ?? | ?? |
| java interoperation | ||||
| ruby | GNU Smalltalk | Squeak | VisualWorks | |
| version | JRuby 1.4.0 | none | JNIPort, JavaConnect | JNIPort, JavaConnect |
| repl | jirb | none, use a Workspace | none, use a Workspace | |
| interpreter | jruby | none, use a Workspace | none, use a Workspace | |
| compiler | jrubyc | none, use a Workspace | none, use a Workspace | |
| prologue | none | none | none | |
| new | rnd = java.util.Random.new | rnd := (JVM current findClass: 'java.util.Random') new | rnd := (JVM current findClass: 'java.util.Random') new_null. | |
| method | rnd.next_float | rnd nextFloat | rnd nextFloat_null | |
| import | java_import java.util.Random rnd = Random.new |
none | none | |
| non-bundled java libraries | require 'path/to/mycode.jar' | none | none | |
| shadowing avoidance | module JavaIO include_package "java.io" end |
none | none | |
| to java array | [1,2,3].to_java(Java::int) | JavaIntArray from: #(1 2 3) jvm: JVM current | JavaIntArray from: #(1 2 3) jvm: JVM current | |
| java class subclassable? | yes | no | no | |
| java class open? | yes | no | no | |
| _______________________ | _______________________ | _______________________ | _______________________ | |
partially copied from http://hyperpolyglot.wikidot.com/scripting then modified by sumim
and also licensed under Creative Commons Attribution-ShareAlike 3.0
Smalltalk が普通に入っているのがすばらしいですね。せっかくなので、すこしだけ目立ち気味の空欄を GNU Smalltalk、Squeak、VisualWorks で項目を分けてからざっと調べて分かる範囲で埋めてみました。スペースの都合と比較のしやすさから、他の言語は Ruby だけ残してありますので、あしからず。GSTer、VWer は、訂正・補足をお願いします。もちろん Squeak使いからのツッコミも歓迎です。
| > | ruby (1995) | GNU Smalltalk (1990) | Squeak (1996; 1983, Apple Smalltalk) | VisualWorks (1993; 1980, XEROX Smalltalk-80) |
|---|---|---|---|---|
| versions used | 1.8.7; 1.9.1 | 3.2 | 4.1 | 7.7 |
| show version | $ ruby --version | Smalltalk version $ gst --version |
Smalltalk version | SystemUtils version |
| interpreter | $ ruby foo.rb | $ gst foo.st | none | $ visual your.im -filein script.st |
| repl | $ irb | $ gst | use a Workspace | use a Workspace |
| check syntax | $ ruby -c foo.rb | none | none | none |
| flags for stronger and strongest warnings | $ ruby -w foo.pl $ ruby -W2 foo.pl |
none | none | none |
| statement separator | ; or sometimes newline | . (period) or sometimes newline | . (period) | . (period) |
| block delimiters | {} or do end | [ ] | [ ] | [ ] |
| assignment | a=1 | a := 1 | a := 1 | a := 1 |
| parallel assignment | a,b,c = 1,2,3 | none | none | none |
| swap | a,b = b,a | | tmp | tmp := a. a := b. b := tmp |
| tmp | tmp := a. a := b. b := tmp |
| tmp | tmp := a. a := b. b := tmp |
| compound assignment operators: arithmetic, string, logical, bit | += -= *= /= none %= **= += &&= ||= ^= <<= >>= &= |= ^= |
none | none | none |
| increment and decrement | x not mutated: x = 1 x.succ x.pred |
none | none | none |
| variable declaration | assignment; $ prefix denotes global scope | Smalltalk at: #GLOBAL put: nil. assignment; will be local to method |
Smalltalk at: #GLOBAL put: nil. for global variable | local1 local2 | for local variable |
Smalltalk at: #GLOBAL put: nil. for global variable | local1 local2 | for local variable |
| constant declaration | warns if identifier with uppercase first character is reassigned PI = 3.14 |
none | (Smalltalk associationAt: #GLOBAL) beReadOnlyBinding
|
MyNameSpace.MyClass defineSharedVariable: #SharedVar private: false constant: true category: 'SharedVar Category' initializer: '3 + 4' |
| end-of-line comment | # comment | none | none | none |
| multiline comment | =begin comment line another line =end |
"comment line another line" |
"comment line another line" |
"comment line another line" |
| null | nil | nil | nil | nil |
| null test | v == nil v.nil? |
v = nil v == nil v isNil |
v = nil v == nil v isNil |
v = nil v == nil v isNil |
| undefined variable access | raises NameError | compile error | compile error | compile error |
| undefined test | ! defined?(v) | Smalltalk includesKey: #GLOBAL MyClass allClassVarNames includes: #ClassVar |
Smalltalk includesKey: #GLOBAL MyClass allClassVarNames includes: #ClassVar thisContext tempNames includes: #tempVar |
Smalltalk includesKey: #GLOBAL MyClass allClassVarNames includes: #ClassVar thisContext tempNames includes: #tempVar |
| arithmetic and logic | ||||
| > | ruby | GNU Smalltalk | Squeak | VisualWorks |
| true and false | true false | true false | true false | true false |
| falsehoods | false nil | false | false | false |
| logical operators | and or not also: && || ! | and: or: not always evaluate 2nd operand: & | | and: or: not always evaluate 2nd operand: & | | and: or: not always evaluate 2nd operand: & | |
| conditional expression | x > 0 ? x : -x | x > 0 ifTrue: [x] ifFalse: [0 - x] | x > 0 ifTrue: [x] ifFalse: [0 - x] x positive ifTrue: [x] ifFalse: [x negated] |
x > 0 ifTrue: [x] ifFalse: [0 - x] x positive ifTrue: [x] ifFalse: [x negated] |
| comparison operators | == != > < >= <= | = ~= > < >= <= | = ~= > < >= <= | = ~= > < >= <= |
| convert from string, to string | 7 + "12".to_i 73.9 + ".037".to_f "value: " + "8".to_s |
7 + ‘12’ asNumber 73.9 + '.037' asNumber 'value: ', 8 printString |
7 + ‘12’ asNumber 73.9 + '.037' asNumber 'value: ', 8 printString |
7 + ‘12’ asNumber 73.9 + '.037' asNumber 'value: ', 8 printString |
| arithmetic operators | + - * x.fdiv(y) / % ** | + - * / // \\ raisedTo: | + - * / // \\ raisedTo: | + - * / // \\ raisedTo: also: ** |
| integer division | a / b | a // b | a // b | a // b |
| float division | a.to_f / b or a.fdiv(b) |
a asFloat / b closure of integers under / is the rationals |
a asFloat / b closure of integers under / is the rationals |
a asFloat / b closure of integers under / is the rationals |
| arithmetic functions | include Math sqrt exp log sin cos tan asin acos atan atan2 |
sqrt exp ln sin cos tan arcSin arcCos arcTan arcTan: | sqrt exp ln sin cos tan arcSin arcCos arcTan arcTan: | sqrt exp ln sin cos tan arcSin arcCos arcTan arcTan: |
| arithmetic truncation | x.abs, x.round, x.ceil, x.floor | x abs. x rounded. x ceiling. x floor | x abs. x rounded. x ceiling. x floor | x abs. x rounded. x ceiling. x floor |
| min and max | [1,2,3].min [1,2,3].max |
none | #(1 2 3) min #(1 2 3) max |
#(1 2 3) min #(1 2 3) max |
| division by zero | integer division raises ZeroDivisionError float division returns Infinity |
raises ZeroDivide | raises ZeroDivide | raises ZeroDivide |
| integer overflow | becomes arbitrary length integer of type Bignum | becomes arbitrary length integer of type LargePositiveInteger | becomes arbitrary length integer of type LargePositiveInteger | becomes arbitrary length integer of type LargePositiveInteger |
| float overflow | Infinity | Inf | Infinity | raises OverflowSignal |
| sqrt -2 | raises Errno::EDOM | returns NaN | raises FloatingPointException | raises ImaginaryResultSignal |
| rational numbers | require 'rational' x = Rational(22,7) x.numerator x.denominator |
x := 22 / 7 x numerator x denominator |
x := 22 / 7 x numerator x denominator |
x := 22 / 7 x numerator x denominator |
| complex numbers | require 'complex' z = 1 + 1.414.im z.real z.imag |
PackageLoader fileInPackage: ‘Complex’. z := 1 + 1.414 i z real z imaginary |
z := 1 + 1.414 i z real z imaginary |
load AT MetaNumerics parcel z := 1 + 1.414 i z real z imaginary |
| random integer, uniform float, normal float | rand(100) rand none |
rand := Random new rand between: 1 with: 100 rand next next |
rand := Random new 100 atRandom rand next next |
rand := Random new (rand next * 100) asInteger rand next none |
| bit operators | << >> & | ^ ~ | bitShift: bitAnd: bitOr: bitXor: bitInvert | << >> bitAnd: bitOr: bitInvert also: bitShift: | bitShift: bitAnd: bitOr: bitXor: bitInvert |
| strings | ||||
| > | ruby | GNU Smalltalk | Squeak | VisualWroks |
| character literal | none | $A | $A | $A |
| chr and ord | 65.chr "A".ord |
65 asCharacter $A asciiValue |
65 asCharacter $A asciiValue |
65 asCharacter $A asInteger |
| string literal | 'don\'t say "no"' "don't say \"no\"" |
'don''t say "no"' | 'don''t say "no"' | 'don''t say "no"' |
| newline in literal | yes | yes (allows multiline) | yes (allows multiline) | yes (allows multiline) |
| here document | computer = 'PC' s = <<EOF here document there #{computer} EOF |
none | none | none |
| escapes | single quoted: \' \\ double quoted: \a \b \cx \e \f \n \r \s \t \uhhhh \u{hhhhh} \v \xhh \ooo |
none | none | none |
| encoding | load the I18N package UTF-8 |
UTF-8 | UTF-8 | |
| variable interpolation | count = 3 item = "ball" puts "#{count} #{item}s" |
none | none, use format: or expandMacros count := 3 item := 'ball' '{1} {2}s' format: {count. item} '<1p> <2s>s' expandMacrosWithArguments: {count. item} |
none, use expandMacros count := 3 item := 'ball' '<1p> <2s>s' expandMacrosWith: count with: item |
| length | "hello".length "hello".size |
'hello' size | 'hello' size | 'hello' size |
| character count | '(3*(7+12))'.count('(') | '(3*(7+12))' occurrencesOf: $( | '(3*(7+12))' occurrencesOf: $( | '(3*(7+12))' occurrencesOf: $( |
| index of substring | "foo bar".index("bar") | 'foo bar' indexOfSubCollection: 'bar' | 'foo bar' findString: 'bar' | 'foo bar' findString: 'bar' startingAt: 1 |
| extract substring | "foo bar"[4,3] | 'foo bar' copyFrom: 5 to: 7 | 'foo bar' copyFrom: 5 to: 7 | 'foo bar' copyFrom: 5 to: 7 |
| concatenate | "hello, " + "world" | 'hello, ', 'world' | 'hello, ', 'world' | 'hello, ', 'world' |
| split | "foo bar baz".split | 'foo bar baz' subStrings | 'foo bar baz' subStrings | 'foo bar baz' tokensBasedOn: ' ' |
| join | ['foo','bar','baz'].join(' ') | #('foo' 'bar' 'baz') join: ' ' | none, use asStringOn:delimiter: String streamContents: [:ss | #('foo' 'bar' 'baz') asStringOn: ss delimiter: ' '] |
none |
| scan | "hello, hep cat".scan(/\w+/) | 'hello, hep cat' allOccurrencesOfRegex: '\w+' | none | none |
| pack and unpack | f="a5ilfd" s=["hello",7,7,3.14,3.14].pack(f) s.unpack(f) |
none | none | none |
| sprintf | "tie: %s %d %f" % ['Spain',13,3.7] | none | none | none |
| case manipulation | "hello".upcase "HELLO".downcase "hello".capitalize |
'hello' asUppercase 'hello' asLowercase none |
'hello' asUppercase 'hello' asLowercase 'hello' capitalized |
'hello' asUppercase 'hello' asLowercase 'hello' capitalized |
| strip | " foo ".strip " foo".lstrip "foo ".rstrip |
none | ' foo ' withBlanksTrimmed ' foo' withoutLeadingBlanks 'foo ' withoutTrailingBlanks |
none |
| pad on right, on left | "hello".ljust(10) "hello".rjust(10) |
none | 'hello' forceTo: 10 paddingWith: $ 'hello' forceTo: 10 paddingStartWith: $ |
none |
| character translation | "hello".tr('a-z','n-za-m') | none | none, use translateWith: | none |
| regexp match | "1999".match(/^\d{4}$/) "foo BAR".match(/^[a-z]+/) "foo BAR".match(/[A-Z]+/) |
'1999' =~ '^\d{4}$' 'foo BAR' =~ '^[a-z]+' 'foo BAR' =~ '[A-Z]+' |
none | none |
| match, prematch, postmatch | s = "A 17 B 12" while (/\d+/.match(s)) do  discard = $‘  number = $&  s = $’  puts number end |
none, use onOccurrencesOfRegex:do: s := 'A 17 B 12'. s onOccurrencesOfRegex: '\d+' do: [:each | each match displayNl] |
none | none |
| substring matches | reg = /(\d{4})-(\d{2})-(\d{2})/ m = reg.match("2010-06-03") yr,mn,dy = m[1..3] |
reg := '(\d{4})-(\d{2})-(\d{2})'. m := '2010-06-03' =~ reg. yr := m at: 1. mn := m at: 2. dy := m at: 3 |
none | none |
| single substitution | "teh last of teh Mohicans".sub(/teh/,'the') | 'teh last of teh Mohicans' replacingRegex: 'teh' with: 'the' | none | none |
| global substitution | "teh last of teh Mohicans".gsub(/teh/,'the') | 'teh last of teh Mohicans' replacingAllRegex: 'teh' with: 'the' | none | none |
| containers | ||||
| > | ruby | GNU Smalltalk | Squeak | VisualWorks |
| array literal | nums = [1,2,3,4] | nums := #(1 2 3 4) | nums := #(1 2 3 4) | nums := #(1 2 3 4) |
| array size | nums.size or nums.length |
nums size | nums size | nums size |
| array lookup | nums[0] | nums at: 1 | nums at: 1 | nums at: 1 |
| array slice | nums[1..2] | nums copyFrom: 2 to: 3 | nums copyFrom: 2 to: 3 | nums copyFrom: 2 to: 3 |
| array iteration | [1,2,3].each { |i| puts i } | nums do: [:o | o displayNl] | nums do: [:o | Transcript cr; show: o] | nums do: [:o | Transcript show: o; cr] |
| membership | nums.include?(7) | nums includes: 7 | nums includes: 7 | nums includes: 7 |
| intersection | [1,2] & [2,3,4] | none, use select: | #(1 2) intersection: #(2 3 4) | none, use select: |
| union | [1,2] | [2,3,4] | none, use asSet | #(1 2) union: #(2 3 4) | none, use asSet |
| map | [1,2,3].map { |o| o*o } | #(1 2 3) collect: [:o | o*o] | #(1 2 3) collect: [:o | o*o] | #(1 2 3) collect: [:o | o*o] |
| filter | [1,2,3].select { |o| o > 1 } | #(1 2 3) select: [:o | o > 1] | #(1 2 3) select: [:o | o > 1] | #(1 2 3) select: [:o | o > 1] |
| reduce | [1,2,3].inject(0) { |m,o| m+o } | #(1 2 3) inject: 0 into: [:m :o| m + o] #(1 2 3) fold: [:m :o | m + o] |
#(1 2 3) inject: 0 into: [:m :o| m + o] | #(1 2 3) inject: 0 into: [:m :o| m + o] #(1 2 3) fold: [:m :o | m + o] |
| universal predicate | [1,2,3,4].all? {|i| i.even? } | #(1 2 3 4) allSatisfy: [:o | o even] | #(1 2 3 4) allSatisfy: [:o | o even] | #(1 2 3 4) allSatisfy: [:o | o even] |
| existential predicate | [1,2,3,4].any? {|i| i.even? } | #(1 2 3 4) anySatisfy: [:o | o even] | #(1 2 3 4) anySatisfy: [:o | o even] | #(1 2 3 4) anySatisfy: [:o | o even] |
| map literal | h = { 't' => 1, 'f' => 0 } | h := Dictionary new add: 't'->1; add: 'f'->0; yourself | h := {'t'->1. 'f'->0} as: Dictionary h := Dictionary new add: 't'->1; add: 'f'->0; yourself |
h := Dictionary new add: 't'->1; add: 'f'->0; yourself |
| map size | h.size or h.length |
h size | h size | h size |
| map lookup | h['t'] | h at: 't' | h at: 't' | h at: 't' |
| is map key present | h.has_key?('y') | h includesKey: 'y' | h includesKey: 'y' | h includesKey: 'y' |
| map iteration | h.each { |k,v| code} | h keysAndValuesDo: [:k :v | code] | h keysAndValuesDo: [:k :v | code] | h keysAndValuesDo: [:k :v | code] |
| keys and values of map as array | h.keys h.values |
h keys h values |
h keys h values |
h keys h values |
| out of bounds behavior | nil | raises IndexOutOfRange | raises error | raises error |
| functions | ||||
| > | ruby | GNU Smalltalk | Squeak | VisualWorks |
| function declaration | def add(a,b); a+b; end | add := [:a :b | a+b] | add := [:a :b | a+b] | add := [:a :b | a+b] |
| function invocation | add(1,2) | add value: 1 value: 2 add valueWithArguments: #(1 2) add valueWithArguments: {1. 2} |
add value: 1 value: 2 add valueWithArguments: #(1 2) add valueWithArguments: {1. 2} |
add value: 1 value: 2 add valueWithArguments: #(1 2) |
| missing argument | raises ArgumentError | raises WrongArgumentCount | raises error | raises error |
| default value | def log(x,base=10) | none | none | none |
| arbitrary number of arguments | def add(first, *rest)  if rest.empty? first  else first + add(*rest)  end end |
none | none | none |
| named parameter definition | def f(h) | none | none | none |
| named parameter invocation | f(:eps => 0.01) | none | none | none |
| pass by reference | none | none | none | |
| return value | return arg or last expression evaluated | ^ arg or last expression evaluated | ^ arg or last expression evaluated | ^ arg or last expression evaluated |
| multiple return values | none | none | none | |
| lambda declaration | f = lambda { |x| x * x } | f := [:x | x * x] | f := [:x | x * x] | f := [:x | x * x] |
| lambda invocation | f.call(2) | f value: 2 | f value: 2 | f value: 2 |
| default scope | local | local | local | local |
| nested function definition | not allowed | not allowed (nested method definition) | not allowed (nested method definition) | not allowed (nested method definition) |
| execution control | ||||
| > | ruby | GNU Smalltalk | Squeak | VisualWorks |
| if | if n == 0  puts "no hits" elsif 1 == n  puts "1 hit" else  puts "#{n} hits" end |
(n = 0 ifTrue: ['no hits'] ifFalse: [n = 1 ifTrue: ['1 hit'] ifFalse: [n printString, ' hits') displayNl |
Transcript cr; show: (n = 0 ifTrue: ['no hits'] ifFalse: [n = 1 ifTrue: ['1 hit'] ifFalse: [n printString, ' hits') or use case-switch like caseOf:otherwise: method Transcript cr; show: ( n caseOf: { [0] -> ['no hits']. [1] -> ['1 hit']} otherwise:[n printString, ' hits']) |
Transcript show: (n = 0 ifTrue: ['no hits'] ifFalse: [n = 1 ifTrue: ['1 hit'] ifFalse: [n printString, ' hits'); cr |
| while | while i < 100 i += 1 |
[i < 100] whileTrue: [i := i + 1] [i >= 100] whileFalse: [i := i + 1] [i := i + 1. i< 100] whileTrue [i := i + 1. i >= 100] whileFalse |
[i < 100] whileTrue: [i := i + 1] [i >= 100] whileFalse: [i := i + 1] [i := i + 1. i< 100] whileTrue [i := i + 1. i >= 100] whileFalse |
i < 100] whileTrue: [i := i + 1] [i >= 100] whileFalse: [i := i + 1] [i := i + 1. i< 100] whileTrue [i := i + 1. i >= 100] whileFalse |
| break/continue/redo | break, next, redo | none | none | none [:break | [i := i + 1. i > 100 ifTrue: [break value repeat ] valueWithExit |
| for | none | none; use to:by:do: method 10 to: 1 by: -1 do: [:i | i displayNl ] |
none; use to:by:do: method 10 to: 1 by: -1 do: [:i | Transcript cr; show: i ] |
none; use to:by:do: method 10 to: 1 by: -1 do: [:i | Transcript show: i; cr ] |
| range iteration | (1..10).each { |i| puts i } | (1 to: 10) do: [:i | i displayNl] | (1 to: 10) do: [:i | Transcript cr; show: i] | (1 to: 10) do: [:i | Transcript show: i; cr] |
| statement modifiers | ||||
| raise exception | raise "bad arg" | self error: 'bad arg' | self error: 'bad arg' | self error: 'bad arg' |
| catch exception | begin risky rescue puts "risky failed" end |
[risky] ifError: ['risky failed' displayNl] [risky] on: Exception do: ['risky failed' displayNl] |
[risky] ifError: [Transcript cr; show: 'risky failed'] [risky] on: Exception do: [Transcript cr; show: 'risky failed'] |
[risky] on: Exception do: [Transcript show: 'risky failed'; cr] |
| finally/ensure | begin acquire_resource risky ensure release_resource end |
[acquireResource. risky] ensure: [eleaseResource] |
[acquireResource. risky] ensure: [eleaseResource] |
[acquireResource. risky] ensure: [eleaseResource] |
| uncaught exception behavior | stderr and exit | stderr and exit | notifier | notifier |
| start thread | t = Thread.new { sleep 10 } | t := [(Delay forSeconds: 10) wait] fork | t := [10 seconds asDelay wait] fork t := [(Delay forSeconds: 10) wait] fork |
t := [10 seconds wait] fork t := [(Delay forSeconds: 10) wait] fork |
| wait on thread | t.join | none, use Semaphore, etc | none, use Semaphore, etc | none, use Semaphore, etc |
| environment and i/o | ||||
| > | ruby | GNU Smalltalk | Squeak | VisualWorks |
| external command | system('ls') | stream := FileDescriptor popen: 'ls' dir: #read. contents := stream contents. stream close. ^contents |
none, use OSProcess package | ExternalProcess new cshOne: 'ls' |
| backticks | `ls` | ?? | ?? | ?? |
| command line args | ARGV.size, ARGV[0], ARGV[1],… | Smalltalk arguments size, Smalltalk arguments at: 1 | SmalltalkImage current arguments size, SmalltalkImage current argumentAt: 1 | ?? |
| print to standard out | puts "hi world" | 'hi world' displayNl | none, use Transcript | load Standard IO Streams parcel OS.Stdout nextPutAll: 'hi world'; cr |
| standard file handles | $stdin $stdout $stderr | stdin stdout stderr | none | OS.Stdin OS.Stdout OS.Stderr |
| open file | f = File.open('/etc/hosts') or File.open('/etc/hosts') { |f| |
f := FileStream open: '/etc/hosts’. or (File path: '/etc/hosts') withReadStreamDo: [:f | | f := FileStream fileNamed: ‘/etc/hosts’ or FileStream fileNamed: ‘/etc/hosts’ do: [:f | | f := '/etc/hosts' asFilename readStream |
| open file for writing | f = File.open('/tmp/test','w') or File.open('/tmp/test','w') { |f| |
f := FileStream open: 'test.txt' mode: #write or (File path: '/etc/hosts') withWriteStreamDo: [:f | | f := FileStream fileNamed: ‘/etc/hosts’ or FileStream fileNamed: ‘/etc/hosts’ do: [:f | | f := '/etc/hosts' asFilename writeStream |
| close file | f.close | f close | f close | f close |
| read line | f.gets | f nextLine | f nextLine | f upTo: Character cr |
| iterate over a file by line | f.each do |line| | f do: [:char | | f do: [:char | | f do: [:char | |
| chomp | line.chomp! | none | none | none |
| read entire file into array or string | a = f.lines.to_a s = f.read |
s := f contents | s := f contents | s := f contents |
| write to file | f.write('hello') | f nextPutAll: ‘hello’ | f nextPutAll: ‘hello’ | f nextPutAll: ‘hello’ |
| flush file | f.flush | f flush | f flush | f flush |
| environment variable | ENV['HOME'] | Smalltalk getenv: 'HOME' | none, use OSProcess package UnixProcess env at: #HOME |
SystemUtils getEnvironmentVariable: 'HOME’ |
| exit | exit(0) | ObjectMemory quit: 0 | none, use SmalltalkIamge current snapshot: false andQuit: true |
ObjectMemory quitWithError: 0 |
| set signal handller | Signal.trap("INT", lambda { |signo| puts "exiting…"; exit }) | ?? | none | ?? |
| _______________________ | _______________________ | _______________________ | _______________________ | |
partially copied from http://hyperpolyglot.wikidot.com/scripting then modified by sumim
and also licensed under Creative Commons Attribution-ShareAlike 3.0
Smalltalkってかなり昔からあるというのは知っています。
といいつつもSmalltalkのことわかってません....
※^これがでてくるところとか...
プログラマメモ2: enumerateObjectsUsingBlockの練習
何かの参考になれば…と、同じような処理を Squeak Smalltalk に書き直してみました。あと念のため、Smalltalk ではブロックとは関係なく return の意味で ^ を使います。Objective-C のブロックに出てくる ^ とは関係ありません。
| dataClass | dataClass := Object subclass: #MyData instanceVariableNames: 'myname' classVariableNames: '' poolDictionaries: '' category: 'Category-Name'. Browser new setClass: dataClass selector: nil; createInstVarAccessors.
| array enumerator | "データをつくる" array := OrderedCollection new. 0 to: 4 do: [:i | | mydata | mydata := MyData new. mydata myname: i asString. array add: mydata]. World findATranscript: nil. "その場で定義してその場で使うパターン" "中身の確認" array do: [:obj | Transcript cr; show: '==> ', obj myname]. "BLOCKで使ってソート!!" array sort: [:obj1 :obj2 | Transcript cr; show: ('[{1}]と[{2}]を比較' format: {obj1 myname. obj2 myname}). obj2 myname < obj1 myname]. "宣言したものを後から使うパターン" enumerator := [:obj | Transcript cr; show: 'enumerator ==> ', obj myname]. array do: enumerator
==> 0 ==> 1 ==> 2 ==> 3 ==> 4 [0]と[1]を比較 [1]と[2]を比較 [3]と[4]を比較 [2]と[4]を比較 [2]と[3]を比較 enumerator ==> 4 enumerator ==> 3 enumerator ==> 2 enumerator ==> 1 enumerator ==> 0
Cog VM は、VisualWorks用の超高速 Smalltalk VM を手がけた Eliot Miranda 氏による Squeak Smalltalk 向けの新しい高性能 VM 。
Teleplace社(旧 Qwaq社)の製品である同名の仮想空間共有ソフトのベースである Croquet用に開発されたものですが、同社の厚意によりオープンソースとして公開され、Squeak のユーザーも利用可能になりました。
Win 向けのバイナリも公開されていたので、さっそく恒例のフィボナッチベンチ(39番目のフィボナッチ数 63245986 の再帰的な算出にかかる時間を計測。実装はナイーブなものにして、メモ化や遅延評価は使用しない)で人気のスクリプト言語(Ruby、Python)と戦わせてみました。結果はこんなかんじ(Core2 Duo 2.4GHz, Win Vista を使用)。
追記: Python3.1.2、Gauche0.9 の結果を追加。
追記: リクエストにおこたえして C と v8 の結果も追加。
| 処理系 | 速度[秒] |
| Ruby1.8 | 123 |
| Ruby1.9 | 20.8 |
| Python2.5 | 50.3 |
| Python3.1 | 66.2 |
| Gauche0.9 | 16.0 |
| Squeak4.1 normal VM | 15.6 (メソッド版), 63.5 (ブロック版) |
| Squeak4.1 Cog VM | 4.79 (メソッド版), 5.36 (ブロック版) |
| VisualWorks7.7 | 1.65 (メソッド版), 3.34 (ブロック版) |
| C | 1.76 |
| v8-2.1 | 1.88 |
商用で爆速の Smalltalk 処理系である VisualWorks には一歩及ばないものの、ノーマルVM で“よい勝負”どまりだった Ruby1.9 に対し、新しい Cog VM ではメソッド版(Integer>>#fib として定義。39 fib で呼び出し)のみならず、大きく水をあけられていたブロック版(Ruby風に言えば Proc版。fib value: 39 で呼び出し)でも圧勝しています。すばらしいですね。
使用したコードと出力などを以下に示します。
$ cat fib.rb def fib(n) return n if n < 2 fib(n-2) + fib(n-1) end n = ARGV[0].to_i start = Time.now puts fib(n), (Time.now - start).to_s + " sec" $ ruby -v fib.rb 39 ruby 1.8.7 (2008-08-11 patchlevel 72) [i386-cygwin] 63245986 123.01 sec $ ruby1.9 -v fib.rb 39 ruby 1.9.1p0 (2009-01-30 revision 21907) [i386-cygwin] 63245986 20.767 sec
$ cat fib.py
from sys import argv
from time import time
def fib(n):
if n < 2:
return n
else:
return fib(n-2) + fib(n-1)
n = int(argv[1])
start = time()
print(fib(n))
print(str(time() - start) + " sec")
$ python -V
Python 2.5.2
$ python fib.py 39
63245986
50.3140001297 sec
$ python3 -V
Python 3.1.2
$ python3 fib.py 39
63245986
66.2220001221 sec
| fib mTime mRes bTime bRes | Integer compile: 'fib ^(self < 2) ifTrue: [self] ifFalse: [(self - 2) fib + (self - 1) fib]'. fib := nil. fib := [:n | n < 2 ifTrue: [n] ifFalse: [(fib value: n-2) + (fib value: n-1)] ]. mTime := Time millisecondsToRun: [mRes := 39 fib]. bTime := Time millisecondsToRun: [bRes := fib value: 39]. ^Array with: mTime -> mRes with: bTime -> bRes
"Squeak VM 4.0.2 => {15573 -> 63245986. 63477 -> 63245986} "
"Cog VM => { 4793 -> 63245986. 5364 -> 63245986} "
"VisualWorks 7.7 => { 1651 -> 63245986. 3335 -> 63245986} "
$ cat fib.scm (define (fib n) (if (< n 2) n (+ (fib (- n 2)) (fib (- n 1))))) (display (time (fib (string->number (car *argv*))))) $ gosh -V fib.scm 10 Gauche scheme shell, version 0.9 [utf-8,pthreads], i686-pc-cygwin $ gosh fib.scm 39 ;(time (fib (string->number (car *argv*)))) ; real 16.020 ; user 15.054 ; sys 0.015 63245986
$ cat fib.c
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
long fib(int n){
if (n < 2) {
return n;
} else {
return fib(n-2) + fib(n-1);
}
}
int main(int argc, char *argv[]){
clock_t startTime, endTime;
long res;
startTime = clock();
res = fib(atoi(argv[1]));
endTime = clock();
printf("%ld, %f sec\n", res, (float)(endTime - startTime)/CLOCKS_PER_SEC);
return 0;
}
$ gcc -Wall -O2 -o fib fib.c
$ ./fib 39
63245986, 1.762000 sec
$ cat fib.js
function fib(n) {
if (n < 2) {
return n;
} else {
return fib(n-2) + fib(n-1);
}
}
var start = new Date();
print(fib(39));
print(new Date() - start + " msec");
$ v8-2.1/shell fib.js
63245986
1890 msec
BabyIDE は、古くは MVC(Model-View-Controller)、近年では DCI(Data-Context-Interaction)というパラダイムの提唱者として知られるトリグヴ・レインスカウ(Trygve Reenskaug)氏自らが手がける Squeak Smalltalk ベースの DCI 向けプログラミング&モデリングのためのツールで、彼の考える BabyUML(UML や他の言語の構成概念を基に作られたプログラミング規律)のための実験の場でもあります。
BabyIDE is an interactive development environment that supports the DCI paradigm with specialized browsers for each perspective. These browsers are placed in overlays within a common window so that the programmer can switch quickly between them. DCI with BabyIDE marks a new departure for object oriented programming technology.
Trygve/BabyIDE
ちなみに BabyIDE とか BabyUML に付いている Baby は、1948年にマンチェスター大で作られた世界初のノイマン型(プログラム内蔵型)コンピューターの一つである SSEM という試作機のニックネームであった「The Baby」がその由来らしいです。このプロジェクトの成果が赤ん坊がそうであるように、自分の足で立てるまでには成熟したものではない一方で伸びしろも有することを示すとと共に、BabyUML が仮想的なプログラム内蔵型コンピューターのようなものをターゲットにしていることともリンクさせているようです。
The world's first digital stored program computer was the Manchester Small Scale Experimental Machine―“The Baby”. This Baby was small, it was designed for testing the Williams-Kilburn cathode ray tube high speed storage device. It was a truly minimal computer with an operations repertoire of just 7 instructions. It executed its first program on 21st June 1948. The machine was insignificant in itself, but it marked the beginning of a new era.
The BabyUML project has created what may be the world's first integrated development environment based on a truly object oriented programming paradigm (Simula, Smalltalk, C++, Java, and others are based on the class paradigm. Even self code descibes one object at the time; there are no facilities for describing networks of collaborating objects). The result of the BabyUML project is like a new born baby. Its functionality is extremely limited. It cannot stand on its own two feet, but there is room for almost unlimited growth. My dream is that many people will adopt the Baby ideas and create their own vigorous variants.
http://folk.uio.no/trygver/2008/commonsense.pdf
I have somewhat whimsically chosen the name BabyUML. The English Baby was the world’s first stored program computer while the target for BabyUML is a virtual, stored program object computer that spans one or more hardware computers.
The BabyUML discipline of programming
後者については、ジム・コプリエン氏の「間」の概念への興味からも、アラン・ケイの“メッセージングのオブジェクト指向”において、オブジェクトが高速のネットワークで互いに接続された小さなコンピューターである、と例えられることとも無関係ではないように感じます。
Smalltalk object is a recursion on the entire possibilities of the computer. Thus its semantics are a bit like having thousands and thousands of computer all hooked together by a very fast network.
The Early History of Smalltalk
The big idea is "messaging" - that is what the kernal of Smalltalk/Squeak is all about (and it's something that was never quite completed in our Xerox PARC phase). The Japanese have a small word - ma - for "that which is in between" - perhaps the nearest English equivalent is "interstitial".
Alan Kay On Messaging
さて前置きはこのくらいにして、この BabyIDE を使い、id:digitalsoul さんによる Groovy および Scala によるローンシンジゲートの例を Squeak Smalltalk で書いてみます。なお、以下のコードで記述された仕様は Ruby札幌勉強会でのデモ向けのため、元エントリーのものに比べてかなり簡素化されているので、どうぞあしからず。
Win 向けに動作に必要なファイル一式のアーカイブが公開されています。他の OS でも、その OS 向けの Squeak VM を別途用意すれば動作するはずです。
BabyIDE というだけあって、専用のクラスブラウザが用意されています。この Squeak イメージでは、ウインドウを開く操作の際には、矩形領域をしていないといけないようになっているので注意が必要です。使いづらければ、 SystemWindow>>#openInWorld: を versions からひとつ前のバージョンに revert すれば、通常通りの動きになります。
Squeak Smalltalk でクラスの定義をしたことがあれば、説明は不要かと思います。
Object subclass: #BBFacility instanceVariableNames: 'limit loan shares' classVariableNames: '' poolDictionaries: '' category: 'BBLoanSyndicate-Data'
引き続き、BBFacility の定義を書き換えるかたちで BBLoan を定義します。
Object subclass: #BBLoan instanceVariableNames: 'shares' classVariableNames: '' poolDictionaries: '' category: 'BBLoanSyndicate-Data'
続いて、それぞれのクラスに暗黙に呼ばれる初期化メソッド(#initialize)を定義します。
initialize
shares := Dictionary new
同様に BBLoan にも同じメソッドを定義します。
これでデータの定義は完了です。なお、Share、SharePie、Company などはコードの簡素化のため省きます。
データ同様、コンテキストオブジェクトのクラスをデータの時と同様の操作で定義します。
BB1Context subclass: #BBLoanSyndicate instanceVariableNames: 'facility' classVariableNames: '' poolDictionaries: '' category: 'BBLoanSyndicate-Context'
以下では簡単のため、データおよびコンテキストのインスタンス変数に対するアクセッサーメソッドが定義されていることを前提にしているので、このタイミングでこれらを自動生成しておきます。ワークスペース(デスクトップメニュー → open... → workspace)などで、次のコードを入力後に選択し、 do it (alt + d) してください。
| browser | browser := Browser new. {BBLoanSyndicate. BBFacility. BBLoan} do: [:class | browser setClass: class selector: nil; createInstVarAccessors]
コンテキストを指定して、そこでのロールとその振る舞いを定義します。ここまでの Smalltalk の伝統的なクラスブラウザの作法でのクラス定義と違い、ロールは楕円で示され、それらを矢印でつなぐことで相互関係を図形的に記述します。
処理を簡潔に書きやすくするため、元エントリーとは関係をちょっと変えています。
各ロールにメソッドを定義します。
定義するメソッドを次に示します。入力やコピペは冒頭の ロール名 >> を省いて行なってください。accept 時に、そのロールに対する最初のメソッド定義の場合は、「Role trait for Lender is not defined. Do you want to define it so that I can compile your method?」と尋ねられるので、Yes としてください。また、以下のコードにはいくつか未定義のメソッドが含まれています。コンパイラにその旨を警告されたときは、未定義のメソッド名をそのままポップアップから選択してください。
Lender >> draw: amount
AmountPie increase: amount.
self limit: self limit - amount
Lender >> pay: amount
AmountPie decrease: amount.
self limit: self limit + amount
AmountPie >> increase: amount
PercentagePie shares associationsDo: [:assoc |
| company percentage |
company := assoc key.
percentage := assoc value / 100 asFloat.
self shares
at: company
put: (self shares at: company) + (amount * percentage)]
AmountPie >> decrease: amount | shares total | shares := self shares. total := shares sum asFloat. shares keysDo: [:company | | current | current := shares at: company. shares at: company put: current - (amount * (current / total))]
Lender
^facility
AmountPie
^facility loan
PercentagePie
^facility
BBLoanSyndicate に初期化メソッド(#buildFacility、#joinFacility:percentage:)、および、トリガーメソッド(#draw: #pay:)を定義します。
buildFacility
facility := BBFacility new.
facility loan: BBLoan new.
joinFacility: company percentage: int
facility shares at: company put: int.
facility loan shares at: company put: 0
draw: amount
self executeInContext: [(self at: #Lender) draw: amount]
pay: amount
self executeInContext: [(self at: #Lender) pay: amount]
以上で定義は完了です。実際に動作を確認してみましょう。アクセッサーの定義と同様にワークスペースなどで次のコードを入力後、選択して、do it (alt + d) すると、トランスクリプトが開いて結果が出力されます。
| syndicate | syndicate := BBLoanSyndicate new buildFacility. syndicate joinFacility: #A percentage: 50. syndicate joinFacility: #B percentage: 30. syndicate joinFacility: #C percentage: 20. syndicate facility limit: 100000. syndicate draw: 10000. syndicate pay: 5000. World findATranscript: nil. syndicate facility loan shares associations sort do: [:assoc | Transcript cr; show: assoc]
[2669]#A->2500.0 [3763]#B->1500.0 [1129]#C->1000.0
と、まあ、いちおう動きはするのですが、じつはまだ #executeInContext: をコンテキスト内のメソッドで書かなければならない理由や self at: #Role を self Role と書いてはいけない理由などモヤっとしているところも多々あるので、DCI デザインを含め、まだまだ勉強が必要なことを痛感しました。^^;
最近のコメント
最近のトラックバック