はじめに
Java(1年) -> Ruby(2年)とやってきてkotlinを勉強し始めたら、完全にruby脳になっていて配列やリストの書き方すら忘れていたので自分の備忘録的にkotlinでこう記述したとき、rubyではどう書くのかを考えてみました。
思いつき次第、随時追加される予定です。
勉強中の身ですので、間違っている箇所ありましたら、優しくご教授いただけますと幸いです。
配列、リスト、ミュータブルなリスト
- 配列はkotlin.collectionsパッケージ内に定義されていないが、
forEach
,map
,filter
,find
などは使える。 - rubyの配列はサイズの変更、書き換えなど自由に行えるが、kotlin(というかJava)では使い分けが必要。
kotlin
// 配列(サイズの変更不可)
val array = arrayOf("Tokyo", "Osaka", "Aichi", "Fukuoka")
array.add("Hokkaido") //=> error: unresolved reference: add
array[4] = "Hokkaido" //=> java.lang.ArrayIndexOutOfBoundsException: 4
array.get(0) //=> "Tokyo"
array.map { it.length } //=> [5, 5, 5, 7]
array.filter { it.length > 5 } //=> [Fukuoka]
// リスト(イミュータブル)
val list = listOf("Tokyo", "Osaka", "Aichi", "Fukuoka")
list.add("Hokkaido") //=> error: unresolved reference: add
list[0] //=> "Tokyo"
list.get(0) //=> "Tokyo"
// ミュータブルなリスト
val mutableList = mutableListOf("Tokyo", "Osaka", "Aichi", "Fukuoka")
mutableList.add("Hokkaido") //=> true
mutableList[0] //=> "Tokyo"
mutableList.get(0) //=> "Tokyo"
ruby
array = ["Tokyo", "Osaka", "Aichi", "Fukuoka"]
array[4] = "Hokkaido" #=> "Hokkaido"
array[0] #=> "Tokyo"
マップ、ミュータブルなマップ
- rubyの場合はHashに相当する。
kotlin
// マップ(イミュータブル)
val fruits = mapOf("a" to "apple", "b" to "banana", "c" to "cherry")
fruits["a"] //=> apple
fruits.put("g", "grape") //=> error: unresolved reference: put
// ミュータブルなマップ
val fruits = mutableMapOf("a" to "apple", "b" to "banana", "c" to "cherry")
fruits["a"] //=> apple
fruits.put("g", "grape") //=> null
ruby
hash = { a: "apple", b: "banana", c: "cherry" }
hash[:a]
hash[:g] = "grape"
レンジ
-
..
演算子を用いる - 終了値も含まれる。pythonは含まれないので注意!
kotlin
1..10
10 in 1..10 //=> true
ruby
1..10
require 'active_support/all' # ActiveSupportのコア拡張を利用
10.in? 1..10 #=> true
python
range(1, 10)
10 in range(1, 10) #=> False
if
式
kotlin
val number = 6
val evenOrOdd = if (number % 2 == 0) "even" else "odd"
val evenOrOdd = if (number % 2 == 0) {
"even"
} else {
"odd"
}
ruby
number = 6
even_or_odd = if number % 2 == 0; 'even' else 'odd' end # 非推奨
even_or_odd = if number % 2 == 0
"even"
else
"odd"
end
even_or_odd = number % 2 ? "even" : "odd" # 短い場合は三項演算子もしばしば利用される
python
number = 6
even_or_odd = "even" if number % 2 == 0 else "odd" # pythonにおける三項演算子
if number % 2 == 0: # pythonにおけるifは文なので代入できない。
even_or_odd = "even"
else:
even_or_odd = "odd"
when
式
kotlin
val score = 75
val judge = when(score) {
in 0..49 -> "不可"
in 50..64 -> "可"
in 65..79 -> "良"
in 80..100 -> "優"
else -> "欠席"
}
ruby
score = 75
judge = case score
when 0..49 then "不可"
when 50..64 then "可"
when 65..79 then "良"
when 80..100 then "優"
else "欠席"
end
文字列
文字列テンプレート
kotlin
val name = "Kotlin"
println("Hello, ${name} World!")
ruby
name = "ruby"
puts("Hello, #{name} World!")
文字列を改行込みで複数行
kotlin
val text = """
あいうえお
かきくけお
さしすせそ
""".trimIndent()
ruby
# ruby 2.3以上で <<~ が使用できる。
text = <<~EOS
あいうえお
かきくけこ
さしすせそ
EOS
text #=> "あいうえお\nかきくけこ\nさしすせそ\n"
関数
kotlin
fun add(num1 : Int, num2 : Int) : Int {
return num1 + num2
}
fun add(num1 : Int, num2 : Int) : Int = num1 + num2 // 単一式関数
fun add(num1 : Int, num2 : Int) = num1 + num2 // 単一式関数では型推論が効き、戻り値の型が省略可
ruby
def add(num1, num2)
num1 + num2
end
デフォルト引数
kotlin
fun concat(str1 : String, str2 : String, separator : String = ",") : String {
return "${str1}${separator}${str2}"
}
ruby
def concat(str1, str2, separator = ",")
"#{str1}#{separator}#{str2}"
end
名前付き引数
kotlin
fun concat(str1 : String, str2 : String, separator : String = ",") : String {
return "${str1}${separator}${str2}"
}
concat(str2="二つめ", str1="一つめ") // 引数名を指定して呼び出せば順序を変更できる
//=> 一つめ,二つめ
ruby
# キーワード引数を用いれば同様のことが可能。
def concat(str1:, str2:, separator: ",")
"#{str1}#{separator}#{str2}"
end
concat(str2: "二つめ", str1: "一つめ")
# => "一つめ,二つめ"
可変長引数
kotlin
fun concat(separator : String, vararg strings : String) : String {
return strings.joinToString(separator)
}
concat(",", "str1", "str2", "str3")
//=> "str1,str2,str3"
ruby
def concat(separator, *args)
args.join(separator)
end
concat(",", "str1", "str2", "str3")
# => "str1,str2,str3"
関数型
- kotlinでは、関数をオブジェクト化できる。(そのオブジェクトの型は関数型となる)
- rubyではlambda(あるいはProc.new)やMethodクラスを用いる。
- 個人的にはrubyのややこしいところの一つだと思う。
- lambdaとProc.newの違いについて
- Methodクラスについて
kotlin
fun calc(operation : (Int, Int) -> Int) {
val result = operation(3, 7)
print(result)
}
val add = fun (num1 : Int, num2 : Int) = num1 + num2
calc(add) //=> 10
ruby
def calc(operation)
result = operation.call(3, 7)
puts result
end
add = ->(num1, num2) {
num1 + num2
}
calc(operation) #=> 10
# 既存の関数の場合は例えばMethodオブジェクト化する。
def add(num1, num2)
num1 + num2
end
calc(Object.new.method(:add)) #=> 10
安全呼び出し演算子
kotlin
text?.length // Safe Call Operator.
ruby
text&.length # Safe Navigation Operator. 日本語では「ぼっち演算子」とも呼ばれる。
エルビス演算子
-
a
がnull
(rubyの場合nil
)ならb
を返す
kotlin
a ?: b
ruby
a || b
型チェック
kotlin
text is String
ruby
text.is_a?(String)
クラス
kotlin
class Person(_name : String, _age : Int) {
val name : String = _name // nameプロパティ
var age : Int = _age // ageプロパティ
fun isAdult() = age >= 18
}
// コンストラクタの引数にvalやvarをつけると同時にプロパティとしても定義可能
class Person(val name : String, var age : Int) {
fun isAdult() = age >= 18
}
ruby
class Person
attr_accessor :name, :age
def initialize(name, age) # rubyではinitializeがコンストラクタに相当
self.name = name
self.age = age
end
def adult?
age >= 18
end
end
クラス定数
-
companion object
の中にconst val
で定義
kotlin
class Person() {
companion object {
const val CONST = "constant value"
}
}
class Person
CONST = "constant value".freeze
end
クラス継承
kotlin
// 継承可能なクラスはopen修飾子をつける
open class Animal(val name : String) {
fun sleep() = print("${name} sleeps")
}
class Cat(name : String) : Animal(name) { // 継承時には親クラスのコンストラクタも明示
fun mew() = print("mew!")
}
ruby
class Animal
attr_accessor :name
def initialize(name)
self.name = name
end
def sleep
puts "#{name} sleeps"
end
end
class Cat < Animal
def mew
puts "mew!"
end
end
データクラス
kotlin
data class User(val id : String, val name : String, val age : Int)
val user1 = User("id1", "Jiro", 20)
val user2 = User("id1", "Jiro", 20)
user1 == user2 //=> true
ruby
# 強いて言えばStructでしょうか。。
User = Struct.new("User", :id, :name, :age)
user1 = User.new("id1", "Jiro", 20)
user2 = User.new("id1", "Jiro", 20)
user1 == user2 #=> true
無名クラス
kotlin
fun foo() {
val textPrinter = object {
fun printText(text : String) = print(text)
}
textPrinter.printText("Hello World!")
}
foo()
//=> Hello World!
ruby
def foo
textPrinter = Class.new {
def self.printText(text)
puts text
end
}
textPrinter.printText("Hello World!")
end
foo
# => Hello World!
シングルトン
kotlin
// オブジェクト宣言でクラスを定義する
object Printer {
fun printText(text : String) = print(text)
}
Printer.printText("Hello World!")
//=> Hello World!
ruby
# Singletonモジュールをincludeする
require "singleton" # Railsの場合は含まれているはず
class Printer
include Singleton
def print_text(text)
puts text
end
end
Printer.instance.print_text("Hello World!")
# => Hello World!
Companionオブジェクト
- rubyだと特異クラスを使うと似たようなことができる。(議論の余地あり)
kotlin
data class User(val name : String)
class MyClass() {
companion object {
fun newUser(name : String) = User(name)
}
}
val user = MyClass.newUser("Jiro")
user.name //=> Jiro
ruby
User = Struct.new("User", :name)
class MyClass
def self.new_user(name)
User.new(name)
end
end
user = MyClass.new_user("Jiro")
user.name #=> "Jiro"
メソッドのオーバーライド
kotlin
// 継承可能なクラスはopen修飾子をつける
open class Animal(val name : String) {
open fun sleep() = print("${name} sleeps") // オーバーライド可能なメソッドにはopen修飾子をつける
}
class Cat(name : String) : Animal(name) { // 継承時には親クラスのコンストラクタも明示
fun mew() = print("mew!")
override fun sleep() = print("${name} had a deep sleep") // override修飾子をつける
}
ruby
class Animal
attr_accessor :name
def initialize(name)
self.name = name
end
def sleep
puts "#{name} sleeps"
end
end
class Cat < Animal
def sleep
puts "#{name} had a deep sleep"
end
def mew
puts "mew!"
end
end
インターフェイス
- rubyでは該当するものがない?
スコープ関数
let
関数、run
関数
kotlin
"text".let { it.length } // letの場合、itで受け取ることができる
"text".let { str -> str.length } // 変数を明示することも可能。
"text".run { length } // runの場合、itは不要。
"text".run { this.length } // thisで受けることができる。
//=> 4
ruby
# ruby 2.5以上
"text".yield_self { |str| str.length }
"text".yield_self(&:length)
# ruby 2.5未満
"text".tap { |str| break str.length }
apply
関数、also
関数
- オブジェクト生成と同時に初期設定を行いたい場合に使用
kotlin
class User() {
var name : String = ""
}
val user = User().apply { name = "Jiro" } // itは不要。戻り値はレシーバ。
val user = User().apply { this.name = "Jiro" } // 明示したい時はthisを用いる
val user = User().also { user -> user.name = "Jiro" } // alsoの場合、thisはラムダの内外で変わらない。変数を明示する必要がある。
user.name
//=> Jiro
ruby
class User
attr_accessor :name
end
user = User.new.tap { |u| u.name = "Jiro" }
user.name
# => Jiro
コレクションの関数
filter
関数(rubyではselect)
kotlin
data class Fruit(val name: String, val price : Int)
val fruits = listOf(Fruit("apple", 100), Fruit("banana", 150), Fruit("melon", 400))
fruits.filter { it.price < 200 }
fruits.filter { fruit -> fruit.price < 200 }
//=> [Fruit(name=apple, price=100), Fruit(name=banana, price=150)]
ruby
class Fruit
attr_accessor :name, :price
def initialize(name, price)
self.name = name
self.price = price
end
end
fruits = [Fruit.new("apple", 100), Fruit.new("banana", 150), Fruit.new("melon", 400)]
fruits.select { |fruit| fruit.price < 200 }
find
関数
kotlin
fruits.find { it.name == "melon" }
fruits.find { fruit -> fruit.name == "melon" }
//=> Fruit(name=melon, price=400)
ruby
fruits.find { |fruit| fruit.name == "melon" }
map
関数
kotlin
fruits.map { it.price * 1.08 }
fruits.map { fruit -> fruit.price * 1.08 }
//=> [108.0, 162.0, 432.0]
ruby
fruits.map { |fruit| fruit.price * 1.08 }
# => [108.0, 162.0, 432.0]
maxBy
関数
kotlin
fruits.maxBy { it.price }
//=> Fruit(name=melon, price=400)
ruby
fruits.max_by { |fruit| fruit.price }
fruits.max_by(&:price)
minBy
関数
kotlin
fruits.minBy { it.price }
//=> Fruit(name=apple, price=100)
ruby
fruits.min_by { |fruit| fruit.price }
fruits.min_by(&:price)
forEach
関数
kotlin
fruits.forEach { print("${it.name}\n") }
ruby
fruits.each { |fruit| puts fruit.name }
reduce
関数
- rubyには
reduceRight
はない。
kotlin
listOf(1, 2, 3, 4, 5).reduce { sum, n -> sum + n } //=> 15
ruby
[1, 2, 3, 4, 5].reduce { |sum, n| sum + n } #=> 15
[1, 2, 3, 4, 5].inject { |sum, n| sum + n } #=> 15
fold
関数
- 初期値を与えられる
reduce
。rubyではreduce
のまま引数を与える。
kotlin
arrayOf(1, 2, 3, 4, 5).fold(100) { sum, n -> sum + n } //=> 115
ruby
[1, 2, 3, 4, 5].reduce(100) { |sum, n| sum + n } #=> 115
[1, 2, 3, 4, 5].inject(100) { |sum, n| sum + n } #=> 115
複数の変数に代入
kotlin
val (a, b) = Pair(1, "B")
val (a, b, c) = Triple(1, "B", arrayOf("c"))
ruby
a, b = [1, "B"]
a, b, c = [1, "B", ["c"]]