33

Scalaで適切に機能する構成可能なオブジェクトを作成するにはどうすればよいですか?モナドでトニーモリスのビデオを見ましたが、Readerまだ点をつなぐことができません。

オブジェクトのハードコードされたリストがありClientます:

class Client(name : String, age : Int){ /* etc */}

object Client{
  //Horrible!
  val clients  = List(Client("Bob", 20), Client("Cindy", 30))
}

Client.clientsプロパティファイルまたはデータベースから読み取る柔軟性を備えた、実行時に決定されることを望んでいます。Javaの世界では、インターフェイスを定義し、2種類のソースを実装し、DIを使用してクラス変数を割り当てます。

trait ConfigSource { 
  def clients : List[Client]
}

object ConfigFileSource extends ConfigSource {
  override def clients = buildClientsFromProperties(Properties("clients.properties"))  
  //...etc, read properties files 
}

object DatabaseSource extends ConfigSource { /* etc */ }

object Client {
  @Resource("configuration_source") 
  private var config : ConfigSource = _ //Inject it at runtime  

  val clients = config.clients 
} 

これは私にはかなりクリーンな解決策のように見えますが(多くのコードではなく、明確な意図)、それvar 飛び出します(OTOH、それは一度だけ注入されることを知っているので、私にはそれほど面倒はないようです-一度)。

この状況でモナドはどのように見えるでしょうReaderか。私が5歳のように説明すると、その利点は何ですか。

4

1 に答える 1

47

アプローチとアプローチの単純で表面的な違いから始めましょう。つまり、どこにもReaderとらわれる必要がなくなったということです。config次の漠然と巧妙なタイプの同義語を定義するとします。

type Configured[A] = ConfigSource => A

ConfigSourceこれで、ある関数、たとえばリスト内のn番目のクライアントを取得する関数が必要になった場合、その関数を「構成済み」として宣言できます。

def nthClient(n: Int): Configured[Client] = {
  config => config.clients(n)
}

つまりconfig、必要なときにいつでも、基本的に薄い空気から抜け出すことができます。依存性注入のようなにおいがしますよね?ここで、リストの1番目、2番目、3番目のクライアントの年齢が必要だとします(存在すると仮定します)。

def ages: Configured[(Int, Int, Int)] =
  for {
    a0 <- nthClient(0)
    a1 <- nthClient(1)
    a2 <- nthClient(2)
  } yield (a0.age, a1.age, a2.age)

もちろん、このためには、との適切な定義が必要mapですflatMap。ここでは詳しく説明しませんが、Scalaz(またはRúnarの素晴らしいNEScalaトーク、またはすでに見たTonyのトーク)が必要なものをすべて提供していると簡単に言います。

ここで重要な点は、ConfigSource依存関係とそのいわゆるインジェクションがほとんど隠されていることです。ここで見ることができる唯一の「ヒント」は、単純ではなくagesタイプのものです。どこでも明示的に参照する必要はありませんでした。Configured[(Int, Int, Int)](Int, Int, Int)config

余談ですが、これは私がほとんどの場合モナドについて考える方法です。モナドは効果を隠して、型シグネチャで効果を明示的に宣言しながら、コードのフローを汚染しないようにします。言い換えれば、あまり繰り返す必要はありません。関数の戻り型で「ねえ、この関数は効果Xを処理します」と言って、それ以上混乱させないでください。

この例では、もちろん、効果はいくつかの固定環境から読み取ることです。Optionおなじみのもう1つのモナディック効果には、エラー処理が含まれます。これは、メソッドの型でエラーの可能性を明示的にしながら、エラー処理ロジックを非表示にすると言えます。または、読み取りの反対のように、Writerモナドは、型システムでその存在を明示的にしながら、私たちが書いているものを隠します。

最後に、通常DIフレームワークをブートストラップする必要があるのと同じように(XMLファイルなど、通常の制御フローの外側のどこかで)、この奇妙なモナドもブートストラップする必要があります。確かに、次のようなコードへの論理的なエントリポイントがあります。

def run: Configured[Unit] = // ...

最終的には非常に単純になります。これConfigured[A]は関数の型の同義語であるため、ConfigSource => A関数をその「環境」に適用するだけです。

run(ConfigFileSource)
// or
run(DatabaseSource)

タダ!したがって、従来のJavaスタイルのDIアプローチとは対照的に、ここでは「魔法」は発生しません。唯一の魔法は、いわば、私たちの型の定義とConfiguredそれがモナドとして振る舞う方法にカプセル化されています。最も重要なことは、型システムは、どの「レルム」依存性注入が発生しているかについて正直に保つことConfigured[...]です。型のあるものはすべてDIの世界にあり、型のないものはそうではありません。これは、すべてが魔法によって管理される可能性のある古い学校のDIでは得られないため、コードのどの部分がDIフレームワークの外部(たとえば、ユニット内)で安全に再利用できるかはわかりません。テスト、または他のプロジェクト全体で)。


更新:私はより詳細に説明するブログ投稿を書きました。Reader

于 2012-06-29T03:41:15.460 に答える