5
0

More than 1 year has passed since last update.

ChatGPTにモナドを教えてもらった

Posted at

教えてもらう前

  • モナドという名前は知っている
  • Haskellの世界で有名らしい言葉(Haskellの事はよく分からない
  • 「HaskellはIO無いけどIOモナドで何とかなるよ!」というモノらしい

教えてもらった後

Option型unwrap_orunwrap_or_elseを生やして好きな振る舞いをセットにしたもの

こんな感じで
class Maybe<T> {
  private value: T | null;

  private constructor(value: T | null) {
    this.value = value;
  }

  static some<T>(value: T): Maybe<T> {
    return new Maybe(value);
  }

  static none<T>(): Maybe<T> {
    return new Maybe<T>(null);
  }

  map<U>(f: (value: T) => U): Maybe<U> {
    if (this.value === null) {
      return Maybe.none<U>();
    } else {
      return Maybe.some(f(this.value));
    }
  }
  unwrapOr<T>(orElse: T) {
    if (this.value === null) {
        return orElse;
    } else {
        return this.value;
    }
  }
  unwrapOrElse<U>(f: CallableFunction) {
    if (this.value === null) {
        return f();
    } else {
        return this.value;
    }
  }
}

const unwrap = Maybe.some(2);
const unwrapedNum = unwrap.unwrapOr(243);
console.log(unwrapedNum);

const unwrapOr = Maybe.some(null);
const unwrapOrNum = unwrapOr.unwrapOr(243);
console.log(unwrapOrNum);

const unwrapOrElse = Maybe.some(5);
const unwrapOrElseNum = unwrapOrElse.unwrapOrElse(()=>new Error("error投げたら意味ない"));
console.log(unwrapOrElseNum);

const unwrapOrElse2 = Maybe.some(null);
const unwrapOrElseNull = unwrapOrElse2.unwrapOrElse(()=>new Error("error投げたら意味ない"));
console.log(unwrapOrElseNull);

ChatGPTとのやり取り

Q. モナドとはどのような概念ですか?

モナドは、関数型プログラミングにおけるデータ構造の一種で、「可能な値」を扱うための構造を提供します。モナドは、任意の型の値に対して、その値が存在する場合と存在しない場合の両方を表現することができます。そのため、例えば、Maybeモナドを使用すると、nullやundefinedの可能性を持つ値を安全に扱うことができます。また、入れ子になったモナドを組み合わせることで、複雑な演算を行うこともできます。


Q. RustのOption型と同じですか?

モナドは、関数型プログラミングにおいて、値をラップし、その値に対して演算を実行するための概念です。RustのOption型は、Some値とNone値を持ち、値が存在する場合にはSome値、存在しない場合にはNone値を返すことで、値が存在するかどうかを表現します。

モナドは、Option型のような存在性の表現に限らず、例えば複数の値を持つMaybe型や、エラーを表現するEither型など、幅広い概念で使用されます。そして、モナドを使用することで、複雑な演算を組み合わせることができ、可読性や可管理性を向上させることができます。

なるほど、モナドという型の概念を質問したつもりだったけどどうやら何かしら値を包む事をモナドと言うらしい。
しかし複数の値を持つMaybeってどういう事だろう?EitherはRustで言うResultの事だと思って良さそうだ


Q. モナドは何のために開発されたんですか?

モナドは関数型プログラミングにおいて、値を包んで計算を行うための概念です。特に、計算の途中で失敗したり例外が発生するような場合に使用されます。RustのOption型は、値が存在するかどうかを表すために使用されますが、モナドはより一般的な概念であり、計算の途中で起こりうる複数の状態を表現するために使用されます。

なるほど?


Q. typescriptでmonadを表現するとどのようなコードになりますか?

TypeScriptでモナドを表現するには、通常は「Maybe」や「Either」といった型を使用します。これらの型は、「値があるかもしれない」または「値があるか、もしくはエラーがある」といった状態を表現するために使用されます。

以下は、TypeScriptでMaybeモナドを表現するための例です。

Code by AI
class Maybe<T> {
  private value: T | null;

  private constructor(value: T | null) {
    this.value = value;
  }

  static some<T>(value: T): Maybe<T> {
    return new Maybe(value);
  }

  static none<T>(): Maybe<T> {
    return new Maybe<T>(null);
  }

  map<U>(f: (value: T) => U): Maybe<U> {
    if (this.value === null) {
      return Maybe.none<U>();
    } else {
      return Maybe.some(f(this.value));
    }
  }
}

んんん?これじゃあ値を取り出せなく無いか?


Q. phpでmonadを表現する事は出来ますか?

PHPでモナドを表現することは可能です。モナドは、関数型プログラミングにおいて、値をラップし、その値に対して様々な演算を行うための概念です。PHPでは、クラスを使ってモナドを実装することができます。以下は、PHPでMaybeモナドを実装する例です。

Code by AI
class Maybe {
    private $value;

    public function __construct($value) {
        $this->value = $value;
    }

    public function map(callable $fn) {
        if ($this->value === null) {
            return new self(null);
        }
        return new self($fn($this->value));
    }

    public function get() {
        return $this->value;
    }
}

$maybe = new Maybe(5);
$maybe = $maybe->map(function ($val) {
    return $val * 2;
});
$maybe = $maybe->map(function ($val) {
    return $val + 1;
});

echo $maybe->get(); // 11

TypeScriptと違ってアクセサーが出現した。だけどこの方法だとnullでも取り出せちゃうじゃん?
Maybeモナドって「値があるかもしれない」に対応する為って言ってたのにこのコードじゃ「値があるかもしれない、けどとりあえず取り出してみるか」じゃん、mapメソッド使う時にちょっと安全なだけだよね?
と思ったら

この例では、Maybeクラスを定義し、そのインスタンスに対してmapメソッドを呼び出して値を変更しています。mapメソッドは、与えられた関数を適用した結果を返します。これにより、値がnullの場合にも安全に演算を行うことができます。

との事。途中経過は安全だけど最後の値を取り出す工程は運任せのモナドがMaybe?
TypeScript版だと取り出しすら出来ないけど


Q. MaybeモナドはRustのOption型と同じですか?

MaybeモナドはRustのOption型と似ていますが、異なる概念です。Maybeモナドは、値が存在するかどうかを表します。RustのOption型は、値が存在するかどうかを表します。 両方は、存在しない場合に安全にアクセスできるようにするために使用されますが、Maybeモナドは演算子を組み合わせることで、複雑な演算を行うことができます。

演算子を組み合わせるの意味が分からない


Q. typescriptでmaybeモナドを表現してください
Code by AI
class Maybe<T> {
    private value: T | null;

    constructor(value: T | null) {
        this.value = value;
    }

    public static just<T>(value: T): Maybe<T> {
        return new Maybe(value);
    }

    public static nothing<T>(): Maybe<T> {
        return new Maybe<T>(null);
    }

    public map<U>(f: (val: T) => U): Maybe<U> {
        if (this.value === null) {
            return Maybe.nothing<U>();
        } else {
            return Maybe.just(f(this.value));
        }
    }

    public flatMap<U>(f: (val: T) => Maybe<U>): Maybe<U> {
        if (this.value === null) {
            return Maybe.nothing<U>();
        } else {
            return f(this.value);
        }
    }

    public get(): T | null {
        return this.value;
    }
}

今度はgetメソッドが生えた。謎だ。
flatpMapも生えた。
somenoneのメソッド名が(多分)Haskellっぽく変わった。elmではたしかこんなメソッド名だった気がする。と思って調べてみたらelmにはMeybe型というのが存在するようだ。


Q. typescriptでhaskellのmaybeモナドを再現してください
Code by AI
type Maybe<T> = T | undefined;

function maybe<T, U>(val: Maybe<T>, func: (val: T) => U): Maybe<U> {
    if (val === undefined) {
        return undefined;
    } else {
        return func(val);
    }
}

let result = maybe(5, (x: number) => x * 2); 
console.log(result); // 10

let result2 = maybe(undefined, (x: number) => x * 2); 
console.log(result2); // undefined

いや絶対嘘だろこれ。undefined返って来とるやん


Q. 以下のクラスはどのように使えますか?
一番最初にChatGPTが書いたコードの事
Code by AI
class Maybe<T> {
  private value: T | null;

  private constructor(value: T | null) {
    this.value = value;
  }

  static some<T>(value: T): Maybe<T> {
    return new Maybe(value);
  }

  static none<T>(): Maybe<T> {
    return new Maybe<T>(null);
  }

  map<U>(f: (value: T) => U): Maybe<U> {
    if (this.value === null) {
      return Maybe.none<U>();
    } else {
      return Maybe.some(f(this.value));
    }
  }
}

このクラスはMaybeモナドを表現しています。Maybeモナドは、値が存在するかどうかが不明な場合に使用されます。

このクラスは、staticなsomeメソッドとnoneメソッドを持っており、それぞれ、値が存在するMaybeインスタンスと値が存在しないMaybeインスタンスを生成することができます。

mapメソッドは、Maybeインスタンスが持つ値に対して関数を適用し、新しいMaybeインスタンスを返します。もし、Maybeインスタンスが値を持っていなければ、noneインスタンスを返します。そうでなければ、新しいインスタンスを返します。

使用方法は以下のようになります。

Code by AI
let maybeNumber: Maybe<number> = Maybe.some(3);
let maybeString: Maybe<string> = maybeNumber.map((value) => value.toString());
console.log(maybeString); // Maybe { value: '3' }

let noneNumber: Maybe<number> = Maybe.none();
console.log(noneNumber.map((value) => value.toString())); // Maybe { value: null }

そうじゃない。
値を絶対に取り出せないこのクラスをどう活用するのかを教えて欲しいんだよ。


Q. maybeの値を取り出すには?

Maybeクラスによって表現されるmaybe値から値を取り出すには、Maybeクラスのインスタンスに対して、valueプロパティを参照することができます。このプロパティは、maybe値が保持している値を表します。

ただし、Maybeクラスは、値が存在しない可能性を表現するために、nullを保持することもできます。そのため、Maybeクラスのインスタンスに対して、valueプロパティを参照する前に、nullかどうかを確認する必要があります。

以下は、Maybeクラスのインスタンスから値を取り出す例です。

Code by AI
const maybeValue: Maybe<number> = Maybe.some(3);
if (maybeValue.value !== null) {
    console.log(maybeValue.value); // 3
} else {
    console.log("None");
}

もしくは

Code by AI
const maybeValue: Maybe<number> = Maybe.some(3);
maybeValue.map(val => console.log(val))

なるほど今時のTypeScriptはprivateフィールドを取り出せるのか。

という訳でDenoランタイムで動かしたけど普通にエラーが出ました


Q. 安全に取り出すにはどのようにしたらいいですか?Rustで例えるとOption型からmatchを用いるような例で書いてください。
Code by AI
class Maybe<T> {
  private value: T | null;

  private constructor(value: T | null) {
    this.value = value;
  }

  static some<T>(value: T): Maybe<T> {
    return new Maybe(value);
  }

  static none<T>(): Maybe<T> {
    return new Maybe<T>(null);
  }

  map<U>(f: (value: T) => U): Maybe<U> {
    if (this.value === null) {
      return Maybe.none<U>();
    } else {
      return Maybe.some(f(this.value));
    }
  }
}

const maybeNum:Maybe<number> = Maybe.some(10)
maybeNum.map( value => value + 10) // 20
const noneNum:Maybe<number> = Maybe.none<number>()
noneNum.map( value => value + 10) // None

質問に対する答えが返って来ない。
訊き方を変えてみよう。


Q. 安全にvalueを取り出すには?maybe型のvalueのみを安全に取り出して変数に入れる方法は?
Code by AI
class Maybe<T> {
  private value: T | null;

  private constructor(value: T | null) {
    this.value = value;
  }

  static some<T>(value: T): Maybe<T> {
    return new Maybe(value);
  }

  static none<T>(): Maybe<T> {
    return new Maybe<T>(null);
  }

  map<U>(f: (value: T) => U): Maybe<U> {
    if (this.value === null) {
      return Maybe.none<U>();
    } else {
      return Maybe.some(f(this.value));
    }
  }
}

const someMaybe = Maybe.some(1);
const noneMaybe = Maybe.none<number>();

// if文を使って値を取り出す
if (someMaybe.value !== null) {
  const value = someMaybe.value;
  console.log(value); // 1
}
if (noneMaybe.value === null) {
  console.log("none"); // "none"
}

// mapを使って値を取り出す
const newMaybe = someMaybe.map((value) => value + 1);
if (newMaybe.value !== null) {
  const value = newMaybe.value;
  console.log(value); // 2
}

Q. そのコードだとクラス変数がprivateなので取り出せません。安全に取り出す方法を教えてください。
Code by AI
let maybeNumber: Maybe<number> = Maybe.some(5);
let maybeString: Maybe<string> = maybeNumber.map(n => n.toString());

if (maybeString.value !== null) {
    let stringValue: string = maybeString.value;
    console.log(stringValue); // "5"
}

(これ以外にも、同様に動かないコードを回答として返された)

さらに訊き方を変えてみよう。


6. RustだとSome(v)で取り出せます。TypeScriptだとどのように取り出せますか?

TypeScriptでもRustのように、Maybeクラスに対してunwrap()のようなメソッドを追加して、値が存在する場合にのみ値を取り出すことができます。

Code by AI
class Maybe<T> {
  private value: T | null;

  private constructor(value: T | null) {
    this.value = value;
  }

  static some<T>(value: T): Maybe<T> {
    return new Maybe(value);
  }

  static none<T>(): Maybe<T> {
    return new Maybe<T>(null);
  }

  map<U>(f: (value: T) => U): Maybe<U> {
    if (this.value === null) {
      return Maybe.none<U>();
    } else {
      return Maybe.some(f(this.value));
    }
  }
  
  unwrap(): T {
    if (this.value === null) {
      throw new Error("unwrap() called on a None value");
    }
    return this.value;
  }
}

これで、Maybe型の変数から値を安全に取り出すことができます。

Code by AI
const someNumber = Maybe.some(5);
console.log(someNumber.unwrap()); //5
const noneNumber = Maybe.none<number>();
console.log(noneNumber.unwrap()); //Error: unwrap() called on a None value

そうじゃない。「存在しないかもしれない」ってだけでエラー起こしてるじゃん。(起こさせているというか

また、JavaScriptではnullやundefinedを判定しながら、値を取り出すこともできます。

Code by AI
const value = someNumber.value;
if (value !== null) {
  console.log(value); //5
}

だからprivateだから取り出せないんだって。


終わり

以上のやり取りで結論を出しました。

  • 包まれている
  • かもしれない運転をする為のもの
  • ChatGPTの説明とコード例だと辻褄合わない
  • RustのOption型とは違う
  • 値に対して安全に関数を適用するのような、何かしらの振る舞いを含む
5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0