Go
Kotlin
rust
TypeScript
Swift

いろんな言語で「Optional(2018)年」やってみよう

元ネタ

Optional(2018)と表示された原因

Swift 2からSwift 3になった際に、DateComponents 構造体のyearやmonthプロパティがOptional 型になったため

他の言語でも同様の破壊的変更があった場合どうなるか?

Swift

破壊的変更前

typealias Year = Int
func happyNewYear(_ year: Year) {
    print("\(year)年あけましておめでとうございます。")
}

happyNewYear(2018)
出力
2018年あけましておめでとうございます。

破壊的変更後

typealias Year = Optional<Int>に変更する

typealias Year = Optional<Int>
func happyNewYear(_ year: Year) {
    print("\(year)年あけましておめでとうございます。")
}

happyNewYear(2018)
出力
Optional(2018)年あけましておめでとうございます。

view code

どうすれば「Optional(2018)」を防げたか

コンパイル時に下のような警告が出るのでそれをチェックする

warning: string interpolation produces a debug description for an optional value; did you mean to make this explicit?

OCaml

破壊的変更前

type year = int
let happy_new_year(year: year) = 
    Printf.printf "%d年あけましておめでとうございます。\n" year;;
happy_new_year(2018);;
出力
2018年あけましておめでとうございます。

破壊的変更後

type year = int optionに変更する

type year = int option
let happy_new_year(year: year) = 
    Printf.printf "%d年あけましておめでとうございます。\n" year;;
happy_new_year(Some 2018);;
破壊的変更後はエラーとなる
File "prog.ml", line 3, characters 88-92:
Error: This expression has type year = int option
       but an expression was expected of type int

view code

改修

改修後
type year = int option
let happy_new_year(year: year) = 
    let f o = match o with
      | Some year -> (Printf.sprintf "%d年あけましておめでとうございます。\n" year)
      | None -> "年は明けなかった。。。\n"
    in
    print_string(f year);;

happy_new_year(Some 2018);;
出力
2018年あけましておめでとうございます。

view code

BuckleScript

破壊的変更前

type year = int
let happy_new_year(year: year) = Js.log {j|$year年あけましておめでとうございます。|j};;
happy_new_year(2018);;
出力
2018年あけましておめでとうございます。

破壊的変更後

type year = int option
let happy_new_year(year: year) = Js.log {j|$year年あけましておめでとうございます。|j};;
happy_new_year(Some 2018);;
出力
2018年あけましておめでとうございます。

happy_new_year(None)にすると0年あけましておめでとうございます。と表示される

破壊的変更時にエラーを出すには

string_of_intを使うことで破壊的変更時にエラーとなる

type year = int
let happy_new_year(year: year) = 
    Js.log(string_of_int year ^ {j|年あけましておめでとうございます。\n|j});;
happy_new_year(2018);;
出力
2018年あけましておめでとうございます。
type year = int option
let happy_new_year(year: year) = 
    Js.log(string_of_int year ^ {j|年あけましておめでとうございます。\n|j});;
happy_new_year(Some 2018);;
破壊的変更後はエラーとなる
Error: This expression has type year = int option
       but an expression was expected of type int

PureScript

破壊的変更前

module Main where

import Prelude(($), (<>), show)
import TryPureScript

type Year = Int

happyNewYear :: Year -> String
happyNewYear x = show(x) <> "年あけましておめでとうございます。"

main =
    render $ p (text (happyNewYear 2018)) 
出力
<p>2018年あけましておめでとうございます。</p>

view code

破壊的変更後

type Year = Maybe Intに変更する

module Main where

import Prelude(($), (<>), show)
import Data.Maybe (Maybe(..))
import TryPureScript

type Year = Maybe Int

happyNewYear :: Year -> String
happyNewYear x = show(x) <> "年あけましておめでとうございます。"

main =
    render $ p (text (happyNewYear (Just 2018)))
出力
<p>(Just 2018)年あけましておめでとうございます。</p>

view code

どうすれば「(Just 2018)」を防げたか

showを使わずtoStringAsを使う実装にしていれば破壊的変更後にエラーとなり検知可能

import Data.Int (toStringAs, decimal)

happyNewYear :: Year -> String
happyNewYear x = toStringAs decimal x <> "年あけましておめでとうございます。"

view code

Elm

破壊的変更前

import Html exposing (text, div)
type alias Year = Int

happyNewYear: Year -> String
happyNewYear year = toString year ++ "年あけましておめでとうございます。"
main =
    div []
    [ text (happyNewYear 2018)
    ]
出力
<div>2018年あけましておめでとうございます。</div>

破壊的変更後

type alias Year = Maybe Intに変更する

import Html exposing (text, div)
type alias Year = Maybe Int

happyNewYear: Year -> String
happyNewYear year = toString year ++ "年あけましておめでとうございます。"
main =
    div []
    [ text (happyNewYear (Just 2018))
    ]
出力
<div>Just 2018年あけましておめでとうございます。</div>

view code

どうすれば「Just 2018」を防げたか

全言語で「Optional(2018)」を防ぐ方法!

Haxe

破壊的変更前

import haxe.ds.Option;
typedef Year = Int;

class Main {
    static function main() {
        happyNewYear(2018);
    }
    static function happyNewYear(year: Year) {
        trace(year + "年あけましておめでとうございます。");
        // これも同じ結果
        // trace('${year}年あけましておめでとうございます。');
    }
}
出力
2018年あけましておめでとうございます。

view code

破壊的変更後

typedef Year = Option<Int>;に変更する

import haxe.ds.Option;
typedef Year = Option<Int>;

class Main {
    static function main() {
        happyNewYear(Some(2018));
    }
    static function happyNewYear(year: Year) {
        trace(year + "年あけましておめでとうございます。");
        // これも同じ結果
        // trace('${year}年あけましておめでとうございます。');
    }
}
出力
Some(2018)年あけましておめでとうございます。

view code

どうすれば「Some(2018)」を防げたか

全言語で「Optional(2018)」を防ぐ方法!
※事前にStd.isを使って型チェックで対策できる可能性あり

Rust

破壊的変更前

type Year = u32;

fn main() {
    happy_new_year(2018);
}

fn happy_new_year(year: Year) {
    println!(
        "{}年あけましておめでとうございます。",
        year
    );
}
出力
2018年あけましておめでとうございます。

view code

破壊的変更後

type Year = Option<u32>; にする

type Year = Option<u32>;

fn main() {
    happy_new_year(Some(2018));
}

fn happy_new_year(year: Year) {
    println!(
        "{}年あけましておめでとうございます。",
        year
    );
}
破壊的変更後はエラーとなる
error[E0277]: the trait bound `std::option::Option<u32>: std::fmt::Display` is not satisfied
  --> src/main.rs:10:9
   |
10 |         year
   |         ^^^^ `std::option::Option<u32>` cannot be formatted with the default formatter; try using `:?` instead if you are using a format string
   |
   = help: the trait `std::fmt::Display` is not implemented for `std::option::Option<u32>`
   = note: required by `std::fmt::Display::fmt`

error: aborting due to previous error

改修

改修後
type Year = Option<u32>;

fn main() {
    happy_new_year(Some(2018));
}

fn happy_new_year(year: Year) {
    if let Some(year) = year {
        println!(
            "{}年あけましておめでとうございます。",
            year
        );
    } else {
        println!("年が明けることはなかった。。。")
    }
}
出力
2018年あけましておめでとうございます。

view code

Scala

破壊的変更前

object Main extends App {
    type Year = Int
    def happyNewYear(year: Year) {
        println(year + "年あけましておめでとうございます。")
        // これも同じ結果
        // println(s"${year}年あけましておめでとうございます。")
    }
    happyNewYear(2018)
}
出力
2018年あけましておめでとうございます。

view code

破壊的変更後

object Main extends App {
    type Year = Option[Int]
    def happyNewYear(year: Year) {
        println(year + "年あけましておめでとうございます。")
        // これも同じ結果
        // println(s"${year}年あけましておめでとうございます。")
    }
    happyNewYear(Some(2018))
}
出力
Some(2018)年あけましておめでとうございます。

view code

どうすれば「Some(2018)」を防げたか

f 補間子を使えばエラーとなりチェック可能

println(f"${year}%d年あけましておめでとうございます。")

Go

GoにはOptional型がない

package main

import (
    "errors"
    "fmt"
)

func happyNewYear(year uint) {
    fmt.Printf("%d年あけましておめでとうございます。\n", year)
}

func newYear2018() (uint, error) {
    return 2018, nil
}

func newYearNothing() (uint, error) {
    return -0, errors.New("年が明けることはなかった。。。")
}

func main() {
    {
        year, err := newYear2018()
        // errを使用せずに happyNewYear(year) を実行できない
        if err != nil {
            fmt.Println(err)
            return
        }
        happyNewYear(year)
    }
    {
        year, _ := newYearNothing()
        // `_` を使用することでエラーチェック回避することは可能
        happyNewYear(year)
    }
    {
        year, err := newYearNothing()
        if err != nil {
            fmt.Println(err)
            return
        }
        happyNewYear(year)
    }
}
出力
2018年あけましておめでとうございます。
0年あけましておめでとうございます。
年が明けることはなかった。。。

view code

Egison

EgisonにはOptional型がない

(define $happy-new-year
  (lambda $x
    (match (integer? x) bool
      {[,#t (S.append (show x) "年あけましておめでとうございます。")]})))

print (happy-new-year 2018)
出力
"2018年あけましておめでとうございます。"

(happy-new-year "hello")などinteger型以外を引数に取るとError: failed pattern matchとなる

Factor

FactorにはOptional型がない

: happy-new-year ( year -- )
    number>string "年あけましておめでとうございます。" append print ;
2018 happy-new-year
出力
2018年あけましておめでとうございます。

"hello" happy-new-yearなどnumber型以外を引数に取るとGeneric word >base does not define a method for the string class. Dispatching on object: "hello"となる

Kotlin

破壊的変更前

typealias Year = Int

fun happyNewYear(year: Year) {
    println("${year}年あけましておめでとうございます。")
}
fun main(args: Array<String>) {
    happyNewYear(2018)
}
出力
2018年あけましておめでとうございます。

view code

破壊的変更後

typealias Year = Int?

fun happyNewYear(year: Year) {
    println("${year}年あけましておめでとうございます。")
}
fun main(args: Array<String>) {
    happyNewYear(2018)
}
出力
2018年あけましておめでとうございます。

view code

nullが許容されたため、null年あけましておめでとうございます。が表示される可能性あり

TypeScript

破壊的変更前

type Year = number

function happyNewYear(year: Year) {
    console.log(year + "年あけましておめでとうございます。");
}

happyNewYear(2018);
出力
2018年あけましておめでとうございます。

破壊的変更後

type Year = number | null

function happyNewYear(year: Year) {
    console.log(year + "年あけましておめでとうございます。");
}

happyNewYear(2018);
出力
2018年あけましておめでとうございます。

view code

nullが許容されたため、null年あけましておめでとうございます。が表示される可能性あり

全言語で「Optional(2018)」を防ぐ方法!

  • happyNewYear関数のテストコードを書く!
  • テストコードを書かないなら、安易にメジャーアップデートしない!