イスタンブール行きたい

たまに書きたくなります

Cerebras(CBRS)の株価下落に悲観する前に、まずはあの圧倒的な推論スピードを体感してほしい

NVIDIAの対抗馬、として2026年5月に上場したCerebras Systems(CBRS)。

しかし、上場初日の熱狂から一転、足元の株価は右肩下がりで軟調な推移が続いている。 株価掲示板やSNSを覗いてみても、「高値掴みした」「NVIDIAにはやっぱり勝てない」「期待外れだ」という悲観的なコメントが支配的だ。

実は私も、上場直後に「まずは試しに」と1株だけ打診買いしてみた。 しかし、連日の下落と掲示板の重苦しい空気に当てられ、「長期的な成長性に賭けて本当に大丈夫なのか?」と不安になり、Cerebrasの実態について改めて深く調べてみることにした。

結論から言うと、悲観して投げ売る前に、このプロダクトを体感するのがいいのではないかと考えている。

思考の待ち時間がゼロ 異次元の推論スピード

調べていく中で、Cerebrasが提供している推論環境を無料で試せるサイトがあることを知った。 一部のオープンソースのLLMを、Cerebrasのハードウェア上で動かせるチャットUIだ。

どれくらい速いんだろうと軽い気持ちでプロンプトを投げてみら……結果は驚愕だった。

エンターを押した瞬間に、画面いっぱいに文字が降ってくるのである。 ChatGPTやGeminiなど通常のGPU環境で感じる数秒のラグが全くない。 このスピードを一度体感してしまうと、他のLLMがスローモーションのように感じられてしまうほどの衝撃があった。

単純だけど容易に真似できない技術の壁

なぜ他社を置き去りにするような圧倒的なスピードが出せるのか。 それはチップの物理的な構造にある。

NVIDIAなどの一般的なAIチップは、計算コアの外側にメモリを置き、そこを細い配線で繋いでいる。 LLMの推論においてはこのメモリからデータを引っ張ってくる速度が最大のボトルネックになる。

対してCerebrasは、30cm四方の巨大なシリコンウェハ1枚を、切らずに丸ごと1つの巨大チップにして、その上にメモリと計算コアを全部載せるという力技。 計算コアとメモリが物理的にすぐ隣にあるため、データの移動速度がNVIDIAの数千倍に達する。だから一瞬で回答が出力される。 単純に聞こえるが、巨大なシリコンに必ず存在する「傷」を迂回する回路設計、熱による物理的な膨張のコントロール、そして強烈な発熱を抑え込むダイレクト水冷システムなど、これを商用レベルで実現・量産するには、狂気とも言える高い製造技術と長年のノウハウが求められるそうだ。 他社が今から簡単にまねできるものではないようである。

開発者にこの速さが見つかる日は近い

現在、Cerebrasの売上の大部分は中東資本やOpenAIなどの一部の巨大顧客に偏っており、これが投資家の懸念材料として語られている。 しかし、2026年の夏頃には、AWSのAmazon BedrockにCerebrasの推論インフラがネイティブ統合されることが発表されている。 これにより、これまで独自のAPIを叩かなければ体験できなかったこの異常なスピードが、世界中のAWSエンジニアや一般のアプリケーション開発者の手に、標準ツールとして渡る。

AIエージェントによる裏側での複雑な検索・推論ループや、リアルタイムの音声対話AI(Voice AI)など、これからのアプリケーション開発において「推論の速さ=ユーザー体験の質」に直結する。 Bedrock経由でこのポテンシャルが広く認知されれば、実需は一気に拡大するはずだ。

結論

チャートだけを眺めていると、たしかに不安になる値動きだ。 しかし、Cerebrasのインフラが叩き出す圧倒的な速さを実際に味わったことがある投資家は、実はまだごく僅かなのではないだろうか。 Xを探してもあまりそういった声は聞こえない。

個人的にはそこまで悲観する必要はなく、これからが楽しみと感じた。

ハイパーログログ学びメモ

概要

集合の要素数(カーディナリティ)を推定するためのデータ構造

省メモリで高速に、大規模データセットの要素数をおおよそ推定できる

基づく考え

要素をハッシュ化し、そのハッシュ値のビットパターンの連続する0の長さを記録、その長さから全体の要素数を確立的に推定する

連続して0が長いほど、たくさん要素数がある可能性が高いという考え方

データ構造

レジスタ配列

配列のサイズはmで、mが推定精度に影響を与える

一般的にはmが大きいほど精度は向上するが、メモリ使用量も増える

レジスタは、観測されたハッシュ値の連続最大値を記録するために使われる

処理の流れ

おおまかに、連続する0の最大値をレジスタ配列に記録していって、カーディナリティを求めたいタイミングでそのレジスタ配列に対して推定式を実行する

  • レジスタ登録
  • カーディナリティ推定
    • 調和平均をとるような式になっている、らしい
      • 指数的なデータの平均をとるのに適している
      • 外れ値に過度に影響されないように
    • 推定値にはバイアスが生じるので、補正係数αmで補正している、らしい

実装

Gemini, Clineに、概念理解のための簡易な実装という前提で書いてもらった

package hyperloglog

import (
    "encoding/binary"
    "fmt"
    "math"
    "math/rand"
    "time" // rand.Seed に使用
)

// HLL はハイパーログログのデータ構造です。
type HLL struct {
    // m はレジスタの数。2のべき乗である必要があります。
    m uint32
    // alphaM は補正係数。mによって決まります。
    alphaM float64
    // registers は各バケットの最大ランク(最長連続ゼロ数)を保持する配列。
    registers []uint8
    // rng はランダム数生成器
    rng *rand.Rand
}

// NewHLL は新しいハイパーログログのインスタンスを作成します。
// m はレジスタの数で、通常は 2 のべき乗です。
func NewHLL(m uint32) (*HLL, error) {
    if m == 0 || (m&(m-1)) != 0 { // m が 0 ではなく、かつ 2 のべき乗であることを確認
        return nil, fmt.Errorf("m must be a power of 2 and greater than 0, got %d", m)
    }

    alpha := getAlpha(m)          // 補正係数を取得
    registers := make([]uint8, m) // レジスタ配列を初期化

    // ランダムシードを設定 (簡略ハッシュ関数用)
    rng := rand.New(rand.NewSource(time.Now().UnixNano()))

    return &HLL{
        m:         m,
        rng:       rng,
        alphaM:    alpha,
        registers: registers,
    }, nil
}

// Add はハイパーログログに要素を追加します。
func (h *HLL) Add(data []byte) {
    // 1. データをハッシュ化
    hash := h.hashData(data)

    // 2. ハッシュ値からレジスタのインデックスを決定
    // ハッシュ値の下位 log2(m) ビットを使用
    // 例: m=16 (2^4) なら、下位4ビットがインデックス
    indexBits := uint32(math.Log2(float64(h.m)))
    index := hash & ((1 << indexBits) - 1) // (h.m - 1) と同じ

    // 3. ハッシュ値の残りの部分からランク(最長連続ゼロ数)を計算
    // インデックスに使用したビットを除いた部分から計算
    remainingHash := hash >> indexBits
    rank := getRank(remainingHash)

    // 4. レジスタを更新 (観測された最大ランクを記録)
    if rank > h.registers[index] {
        h.registers[index] = rank
    }
}

// Count は推定されたカーディナリティを返します。
func (h *HLL) Count() uint64 {
    sum := 0.0
    // 各レジスタの値の逆数(2^(-M_j))の和を計算
    for _, val := range h.registers {
        sum += math.Pow(2, -float64(val))
    }

    // 推定式を適用
    // HLL_estimate = alpha_m * m^2 / (sum of 2^(-M_j))
    estimate := h.alphaM * float64(h.m*h.m) / sum

    // 小さなカーディナリティに対する補正 (簡略版なので、非常に基本的なもの)
    // 実際の実装では、もう少し複雑な補正ロジックが入ります。
    if estimate < float64(h.m)*2.5 { // 小さな値の場合
        // ゼロのレジスタ数を数える
        zeroRegisters := 0
        for _, val := range h.registers {
            if val == 0 {
                zeroRegisters++
            }
        }
        if zeroRegisters != 0 {
            // ゼロのレジスタがある場合はポアソン推定に近づける
            estimate = float64(h.m) * math.Log(float64(h.m)/float64(zeroRegisters))
        }
    }

    return uint64(math.Round(estimate)) // 四捨五入して整数に
}

// --- ヘルパー関数 ---

// hashData は与えられたデータをハッシュ化する簡素な関数です。
// 実際の実装では、より堅牢なハッシュ関数 (例: murmur3) を使用すべきです。
func (h *HLL) hashData(data []byte) uint32 {
    if len(data) >= 4 {
        // データが十分長ければ、最初の4バイトをハッシュとして利用 (非常に単純)
        return binary.LittleEndian.Uint32(data)
    }
    // データが短い場合は、ランダムなハッシュを生成 (衝突しやすいので注意)
    // Goのmath/randはプロダクション用途では安全ではありません。
    // ここはあくまで理解のための簡単な例です。
    return h.rng.Uint32()
}

// getRank はハッシュ値の先頭の連続するゼロの数を返します。
// 例: 00101... -> 2, 10101... -> 0 (先頭の0がないため)
func getRank(hash uint32) uint8 {
    rank := uint8(0) // 0 からスタート
    // ハッシュ値が 0 の場合、全ビットが 0 なので最大のランク (32 or 64) になるが、
    // 実際にはハッシュ値はランダムなため、めったに起こらない。
    // また、HLLではランクはレジスタのビット数で上限が設定される。
    if hash == 0 {
        return 32 // 32ビットのハッシュの場合、全ビットが0ならランク32
    }
    // CLZ (Count Leading Zeros) の考え方
    for i := 0; i < 32; i++ { // 32ビットハッシュを仮定
        if (hash>>i)&1 != 0 { // 右から見て最初の1が見つかったら終了
            break
        }
        rank++
    }
    return rank
}

// getAlpha はレジスタ数 m に応じた補正係数 alpha_m を返します。
func getAlpha(m uint32) float64 {
    switch m {
    case 16:
        return 0.673
    case 32:
        return 0.697
    case 64:
        return 0.709
    default: // m >= 128
        // 論文で示される漸近式
        return 0.7213 / (1 + 1.079/float64(m))
    }
}
package main

import (
    "fmt"
    "hyperloglog/hyperloglog"
)

func main() {
    // レジスタ数 m = 16 で HLL を初期化
    m := uint32(16)
    hll, err := hyperloglog.NewHLL(m) // mは2のべき乗
    if err != nil {
        fmt.Println("HLL初期化エラー:", err)
        return
    }

    // テストデータ (実際のユニーク数は 7)
    data := []string{
        "apple",
        "banana",
        "cherry",
        "date",
        "elderberry",
        "fig",
        "grape",
        "apple",
        "banana",
        "honeydew",
        "honeydew",
        "ice cream",
    }

    fmt.Printf("ユニーク要素追加開始 (m=%d):\n", m)
    uniqueCount := make(map[string]struct{}) // 実際のユニーク数を追跡するマップ (比較用)

    for _, item := range data {
        hll.Add([]byte(item))
        uniqueCount[item] = struct{}{}
        fmt.Printf("追加: %-12s | 実際のユニーク数: %-2d | 推定カーディナリティ: %d\n", item, len(uniqueCount), hll.Count())
    }

    fmt.Println("\n--- 最終結果 ---")
    fmt.Printf("実際のユニーク要素数: %d\n", len(uniqueCount))
    fmt.Printf("HLLによる推定カーディナリティ: %d\n", hll.Count())
}

活用例

正確なカウントは必要ないものの、大量のデータからおおよそのユニーク数を高速かつ省メモリに把握したいケース

  • サイトのユニークユーザー数
  • オンライン広告のリーチ数
  • DBのカーディナリティ推定
  • ネットワークトラフィックの分析

ブルームフィルター学びメモ

ブルームフィルターを知った経緯

ここ最近読んだ本でたまたま記述があったことが続いたので、気になり調べた

ブルームフィルター概要

少ないメモリ量で、指定の要素がデータの中の存在するかおそらく存在するかどうか判定するデータ構造である。
要素そのものを格納するのではなく、ビット配列で存在有無を格納する。
ただし、偽陽性(データ中に要素が存在しないのに、存在すると)の可能性がある。偽陰性は起こりえない。
一度追加した要素を正確に削除することは難しい。

仕組み

複数の異なるハッシュ関数を使って、追加したい要素をそれぞれ異なるビット位置に対応付ける(ビットを1にする)ことでフィルタを作成する。
要素が存在するかどうかを判定する際は、同じハッシュ関数を使って要素をハッシュ化し、対応するビット位置がすべて1になっているかどうかを確認する。
もしすべて1であれば「存在する可能性がある」、一つでも0であれば「存在しない」と判定する。

偽陽性が発生する可能性があるのは、存在を確認したい要素とは別の要素が、ハッシュ関数実行の結果同じビット配列に1を入れている可能性もあるためである。 一度追加した要素を削除することが難しいのも、削除したい要素とは別の要素が同じビット配列インデックスに1を入れている可能性もあるためである。 一度1を入れたら0に戻ることはないので、偽陰性はありえない。

実装

Clineに書いてもらった、最低限のものである

// main.go
package main

import (
    "fmt"

    "bloom-filter/bloomfilter"
)

func main() {
    // Initialize the Bloom filter
    bf := bloomfilter.NewBloomFilter(100, 5)

    // Add items to the Bloom filter
    bf.Add("item1")
    bf.Add("item2")

    // Check if an item exists in the Bloom filter
    fmt.Println(bf.Check("item1")) // Should print: true
    fmt.Println(bf.Check("item2")) // Should print: true
    fmt.Println(bf.Check("item3")) // Should print: false
}
// bloomfilter.go
package bloomfilter

import (
    "crypto/sha256"
    "encoding/binary"
)

// BloomFilter represents a Bloom filter, a space-efficient probabilistic data structure.
type BloomFilter struct {
    size      int    // The size of the bit array.
    hashFuncs int    // The number of hash functions to use.
    bits      []byte // The bit array.
}

// NewBloomFilter creates a new Bloom filter with the given size and number of hash functions.
func NewBloomFilter(size int, hashFuncs int) *BloomFilter {
    bf := &BloomFilter{
        size:      size,
        hashFuncs: hashFuncs,
        bits:      make([]byte, (size+7)/8), // Calculate the byte array size needed.
    }
    return bf
}

// Add adds an item to the Bloom filter.
func (bf *BloomFilter) Add(item string) {
    for i := 0; i < bf.hashFuncs; i++ {
        index := bf.hashFunction(item, i)
        bf.setBit(index)
    }
}

// Check checks if an item is possibly in the Bloom filter.
func (bf *BloomFilter) Check(item string) bool {
    for i := 0; i < bf.hashFuncs; i++ {
        index := bf.hashFunction(item, i)
        if !bf.getBit(index) {
            return false // If any bit is 0, the item is not in the filter.
        }
    }
    return true // If all bits are 1, the item may be in the filter.
}

// hashFunction generates a hash for the given item and seed.
func (bf *BloomFilter) hashFunction(item string, seed int) int {
    h := sha256.New()
    h.Write([]byte(item))
    h.Write([]byte{byte(seed)})
    hash := h.Sum(nil)
    return int(binary.BigEndian.Uint32(hash)) % bf.size
}

// setBit sets the bit at the given index to 1.
func (bf *BloomFilter) setBit(index int) {
    byteIndex := index / 8
    bitIndex := index % 8
    bf.bits[byteIndex] |= 1 << bitIndex
}

// getBit returns the value of the bit at the given index.
func (bf *BloomFilter) getBit(index int) bool {
    byteIndex := index / 8
    bitIndex := index % 8
    return (bf.bits[byteIndex] & (1 << bitIndex)) != 0
}

活用例

大規模データかつ、多少偽陽性を許容できる場面で利用できる。

  • 負荷の高いDBアクセスの前段にブルームフィルターで存在判定をすることで、存在しなかった場合はDBアクセスを行わない
  • 単語のスペルチェッカー
  • 安全なサイト判定のため、ブラウザがローカルでブラックリストのブルームフィルタで安全チェックを簡略

RaspberryPiでLEDライトが点かなくて詰まった

IoTに興味があり、RaspberryPi 4Bを買った。半導体不足でかなり割高だったが、まだしばらく割高な状態が続きそうで、いつまでも待っているのも勿体無いので、持っていた米国株をいくつか売って資金にし買うことにした。

あわせて、SunFounderのスターターキットも買ってみた。 SunFounderのキットは公式なドキュメントもあり、早速これに沿って勉強してみた。

最初のレッスンは、LEDを点滅させるというものだ。 参考書として「ラズパイ4対応 カラー図解 最新 Raspberry Piで学ぶ電子工作 作る、動かす、しくみがわかる! 」の本も買ってみたが、 この本でもLEDライトを点滅させるというのが最初の実践として載っていたので、LED点滅は基礎の基礎という位置づけになっていると思われる。

そんな基礎の基礎であるLED点滅だが、SunFounderのドキュメントの通りにやっているつもりだったが全く点滅してくれなかった。 ブレッドボードにワイヤや抵抗、LEDを挿す位置や順番がおかしいのかと思ったが、電流の流れを理解したうえでも問題はなさそうだった。

結論を言うと、なんてことはなく、ワイヤや抵抗器、LEDをブレッドボードに深く挿しすぎていたようで、ブレッドボード内部の線に当たっていなかったことが原因のようだった。 当初、しっかり挿そうと深めに挿していたが、それだとうまくブレッドボードの内部の線と交わっていなかったようだ。 浅めに挿すことで、LEDの点滅を確認できた。 たしかに、適度な長さにニッパでワイヤや抵抗器、LEDの線を切ると良いとは参考書に書いてあったが、線を切ってしまうという行為は初心者にはなかなか勇気が要る。 本来は、浅めに挿すよりもニッパで適度な長さに切って深く挿すのが適切なのだろう。

わかる人からすればそんなの当たり前じゃないかと言うレベルな話だと思うが、深く挿しすぎるとブレッドボードの内部の線と交わらないという情報は意外に見かけなかった。 それぐらいに当たり前のことかもしれないが、素養がないとそれもわからないのでこの記事でメモ。 基礎の基礎でうまくいかずちょっと心が折れそうになったが、とりあえずスタート地点に立つことができて一安心。

InfluxDBを触ってみるメモ

IoTにちょっと興味あって(全く未経験)、Rasberry Piを買ってみようかと思ったけど昨今の情勢もあってか値段が高騰している。 そのうち買ってみるつもりだが、安くなるのを待ちつつIoT関連の技術にちょっと触れてみたい。

IoTのプロダクトだと時系列データベースを使っているものが多いらしい。 センサーデータみたいなものをどんどん格納していくのだと思うが、時系列データベースというものが全く想像つかないので、代表格と思われるInfluxDBをちょっと触ってみる。

https://hub.docker.com/_/influxdb のAutomated Setupの部分を参考に、とりあえずDockerでInfluxDBのセットアップ込みですぐに使える状態のものを使う。

docker run -d -p 8086:8086 --name influxdb \
      -v $PWD/data:/var/lib/influxdb2 \
      -v $PWD/config:/etc/influxdb2 \
      -e DOCKER_INFLUXDB_INIT_MODE=setup \
      -e DOCKER_INFLUXDB_INIT_USERNAME=my-user \
      -e DOCKER_INFLUXDB_INIT_PASSWORD=my-password \
      -e DOCKER_INFLUXDB_INIT_ORG=my-org \
      -e DOCKER_INFLUXDB_INIT_BUCKET=my-bucket \
      -e DOCKER_INFLUXDB_INIT_RETENTION=1w \
      -e DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=my-super-secret-auth-token \
      influxdb:latest

BUCKETというものがRDBでいうところのdatabaseっぽい。

コンテナの中に入ってみる。

docker container exec -it influxdb sh

BUCKETのリスト出力。 Docker起動時にDOCKER_INFLUXDB_INIT_BUCKETで指定したmy-bucketが確認できる。

# influx bucket list
ID          Name        Retention   Shard group duration    Organization ID     Schema Type
07c17e53661ef6bf    _monitoring 168h0m0s    24h0m0s         b1326f6776364a21    implicit
bad1a9eb45b1fcb0    _tasks      72h0m0s     24h0m0s         b1326f6776364a21    implicit
213640f9d9ad6b19    my-bucket   168h0m0s    24h0m0s         b1326f6776364a21    implicit

InfluxDBが起動していると、ブラウザで http://localhost:8086/signin にアクセスできるようになっている。 以下のようなログイン画面になっている。

Docker起動時に指定したUSERNAMEとPASSWORDを入力してログインすると、以下のような画面に

とりあえずデータを入れてみる。 CLIからも入れられるみたいだが、せっかくGUIの画面を見ているので、Exampleでも紹介されているCSVをちょっといじった以下のようなCSVをアップロードする。 (後にデータ参照のためダッシュボードを作るときにデータが少なくて面白くなかったので、同じようなデータを適当に追加した)

#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,string,string,double,string,string
#group,false,false,true,true,false,true,false,false,true,true
#default,,,,,,,,,,
,result,table,_start,_stop,_time,region,host,_value,_measurement,_field
,,0,2023-02-22T20:50:00Z,2023-02-22T20:51:00Z,2023-02-22T20:50:00Z,east,A,15.43,cpu,usage_system
,,0,2023-02-23T20:50:00Z,2023-02-23T20:51:00Z,2023-02-23T20:50:20Z,east,B,42.25,cpu,usage_system
,,0,2023-02-24T20:50:00Z,2023-02-24T20:51:00Z,2023-02-24T20:50:40Z,east,C,39.62,cpu,usage_system
,,1,2023-02-25T20:50:00Z,2023-02-25T20:51:00Z,2023-02-25T20:50:00Z,west,A,62.73,cpu,usage_system
,,1,2023-02-26T20:50:00Z,2023-02-26T20:51:00Z,2023-02-26T20:50:20Z,west,B,3.83,cpu,usage_system
,,1,2023-02-27T20:50:00Z,2023-02-27T20:51:00Z,2023-02-27T20:50:40Z,west,C,83.62,cpu,usage_system

#datetypeは文字通りデータ型を指す。 #groupIndicates the column is part of the group key.とのこと。グループキーのメンバーかどうかということだが、どういうことかよくわからない。 RDSでいうところのGROUP BYでキーになるような項目的な感じ? #defaultはデフォルト値

トップ画面から「LOAD YOUR DATA」を選ぶと、データロード方法を選ぶ画面に。 「Flux Annotated CSV」をクリックすると、ファイルアップロード画面に遷移するので上記のCSVをアップロードする。

アップロードできた

データを入れられたので、入れたデータを参照してみる。

トップページに戻り、「BUILD A DASHBOARD」からダッシュボード作成画面を開く。

「ADD CELL」より要素を追加。 GraphやGaugeなど図のタイプを選べるが、デフォルトではGraphになっている。

画面左下でBucketを選択し、GUIでmeasurement(測定データ)、その他項目を絞り込めるようになっている。 右側のSUBMITを押すと、グラフが表示される。

ちなみに、「SCRIPT EDITOR」を押すと、GUIで選んだ条件をクエリとして表すとどうなるのか確認できた。

from(bucket: "my-bucket")
  |> range(start: v.timeRangeStart, stop: v.timeRangeStop)
  |> filter(fn: (r) => r["_measurement"] == "cpu")
  |> filter(fn: (r) => r["_field"] == "usage_system")
  |> filter(fn: (r) => r["host"] == "A" or r["host"] == "B" or r["host"] == "C")
  |> filter(fn: (r) => r["region"] == "east" or r["region"] == "west")
  |> aggregateWindow(every: 1h, fn: mean, createEmpty: false)
  |> yield(name: "mean")

ごくシンプルだがInfluxDBがどのようなものか何となくのイメージはできた。 センサーデータなどを取得して異常検知などをするIoTとの相性が良いことはわかった。

【Laravel】テストでコンストラクタのパラメータをmockにする

以下のようなコンストラクタにパラメータのクラス(FunctionParamA)が存在するクラス(ClassX)があったとする。 パラメータのクラスを実行するメソッドのテストを書きたかったが、パラメータのクラスをmockにする方法がわからず調べたのでメモ。

class ClassX
{
    private $functionParamA;
    
    public function __construct(FunctionParamA $functionParamA)
    {
        $this->functionParamA = $functionParamA;
    }

    public function testMethod(int $id)
    {
        // 色々な処理
        call_user_func($this->functionParamA, $id);
        // 色々な処理
    }
}
class FunctionParamA
{
    public function __invoke(int $id)
    {
        // 色々な処理
    }
}

テストコードで以下のようにすればよかった

class ClassXTest extends BaseTestCase
{
    public function test_正常系()
    {
        // 略
    
        $mockFunctionParamA = Mockery::mock(FunctionParamA::class); // パラメータのクラスのMock作成
        $mockFunctionParamA->shouldReceive('__invoke'); // パラメータのクラスのメソッド設置
        $this->app->instance(FunctionParamA::class, $mockFunctionParamA); // パラメータのクラスのインスタンスをセット
        $this->app->make(ClassX::class); // そのうえでテスト対象のクラスを上書き

        $response = $this->postJson('/api/XXXXX', [
            'id' => 1
        ]);
        $response->assertStatus(200);
    }
}

【React】テキストからURL、改行を探してaタグ、brタグに変換

地味にちょっと苦労したのでメモ チャットUIみたいなやつで、テキスト中にURLが存在すれば色を変えたかったのでclassをつけるためaタグにしたかったのと、\nが存在すれば<br>に変換したかった

const URL_REGEXP_CONTAINS = /(https?:\/\/[\w/:%#\$&\?\(\)~\.=\+\-]+)/g
const NEW_LINE_REGEXP = /(\n)/g

const textArray = text.split(URL_REGEXP_CONTAINS).map(t => {
            return t.split(NEW_LINE_REGEXP)
}).flat()

 return textArray.map(t => {
    if (NEW_LINE_REGEXP.test(t)) {
        return React.createElement('br')
    }
    if (URL_REGEXP.test(t)) {
        return React.createElement('a', {
            href: t,
            target: "_blank",
            referrer: "noreferrer",
                style: {
                    "color": "#f32840",
                    "text-decoration": "underline",
                    "text-decoration-color": "#f32840",
                }
        }, t)
    }
    return t
})