первый подход - это рекурсивные схемы (ищите droste scala), но т.к. вам нужно смотреть минимум на два уровня это уже не самый тривиальный рефолд, сходу не вспомню какой
типа @deriveTraverse sealed trait ExprTreeF[+A] case class Num(n : Int) extends ExprTreeF[Nothing] case class Sum[A](l: A, r: A) extends ExprTreeF [A] ...
и дальше можете матчить def simplify = tree match{ case Fix(ft) => ft.map(simplify) match{ case Sum(Fix(Num(_)), Sum(Fix(Num(_)), Fix (Num(_))) => ... case other => other }