Question

Je suis souvent confronté à la situation suivante :supposons que j'ai ces trois fonctions

def firstFn: Int = ...
def secondFn(b: Int): Long = ...
def thirdFn(x: Int, y: Long, z: Long): Long = ...

et j'ai aussi calculate fonction.Ma première approche peut ressembler à ceci :

def calculate(a: Long) = thirdFn(firstFn, secondFn(firstFn), secondFn(firstFn) + a)

Il est magnifique et sans accolades - juste une expression.Mais ce n'est pas optimal, donc je me retrouve avec ce code :

def calculate(a: Long) = {
  val first = firstFn
  val second = secondFn(first)

  thirdFn(first, second, second + a)
}

Il s'agit maintenant de plusieurs expressions entourées d'accolades.Dans de tels moments, j’envie un peu Clojure.Avec laisser fonctionner Je peux définir cette fonction en une seule expression.

Mon objectif ici est donc de définir calculate fonction avec une expression.Je propose 2 solutions.

1 - Avec Scalaz Je peux le définir comme ceci (existe-t-il de meilleures façons de le faire avec Scalaz ?) :

  def calculate(a: Long) = 
    firstFn |> {first => secondFn(first) |> {second => thirdFn(first, second, second + a)}}

Ce que je n'aime pas dans cette solution, c'est qu'elle est imbriquée.Le plus vals J'ai plus cette nidification est profonde.

2 - Avec for compréhension, je peux réaliser quelque chose de similaire :

  def calculate(a: Long) = 
    for (first <- Option(firstFn); second <- Option(secondFn(first))) yield thirdFn(first, second, second + a)

D'une part, cette solution a une structure plate, tout comme let dans Clojure, mais d'un autre côté, je dois envelopper les résultats des fonctions dans Option et recevoir Option comme résultat de calculate (c'est bien que j'aie affaire à des valeurs nulles, mais je ne le fais pas...et je ne veux pas).

Existe-t-il de meilleures façons d’atteindre mon objectif ?Quelle est la manière idiomatique de gérer de telles situations (peut-être devrais-je m'en tenir à vals...mais let la façon de le faire a l'air si élégante) ?

D'un autre côté, il est connecté à Transparence référentielle.Les trois fonctions sont référentiellement transparentes (dans mon exemple firstFn calcule certaines constantes comme Pi), donc théoriquement elles peuvent être remplacées par des résultats de calcul.Je le sais, mais pas le compilateur, il ne peut donc pas optimiser ma première tentative.Et voici ma deuxième question :

Puis-je d'une manière ou d'une autre (peut-être avec une annotation) donner une indication au compilateur que ma fonction est référentiellement transparente, afin qu'elle puisse optimiser cette fonction pour moi (y mettre une sorte de mise en cache, par exemple) ?

Modifier

Merci à tous pour les excellentes réponses !Il est tout simplement impossible de sélectionner la meilleure réponse (peut-être parce qu'elles sont toutes si bonnes), donc j'accepterai la réponse avec le plus de votes positifs, je pense que c'est assez juste.

Était-ce utile?

La solution

dans le cas non récursif, soit une restructuration de lambda.

def firstFn : Int = 42
def secondFn(b : Int) : Long = 42
def thirdFn(x : Int, y : Long, z : Long) : Long = x + y + z

def let[A, B](x : A)(f : A => B) : B = f(x)

def calculate(a: Long) = let(firstFn){first => let(secondFn(first)){second => thirdFn(first, second, second + a)}}

Bien sûr, c'est toujours imbriqué.Je ne peux pas éviter ça.Mais vous avez dit que vous aimiez la forme monadique.Voici donc la monade identitaire

case class Identity[A](x : A) {
   def map[B](f : A => B) = Identity(f(x))
   def flatMap[B](f : A => Identity[B]) = f(x)
}

Et voici votre calcul monadique.Déballez le résultat en appelant .x

def calculateMonad(a : Long) = for {
   first <- Identity(firstFn)
   second <- Identity(secondFn(first))
} yield thirdFn(first, second, second + a)

Mais à ce stade, cela ressemble certainement à la version val originale.

La monade Identité existe dans Scalaz avec plus de sophistication

http://scalaz.googlecode.com/svn/continuous/latest/browse.sxr/scalaz/Identity.scala.html

Autres conseils

Restez fidèle au formulaire original :

def calculate(a: Long) = {
  val first = firstFn
  val second = secondFn(first)

  thirdFn(first, second, second + a)
}

C'est concis et clair, même pour les développeurs Java.C'est à peu près équivalent à laisser, sans limiter la portée des noms.

Voici une option que vous avez peut-être négligée.

def calculate(a: Long)(i: Int = firstFn)(j: Long = secondFn(i)) = thirdFn(i,j,j+a)

Si vous voulez réellement créer une méthode, c’est ainsi que je procéderais.

Alternativement, vous pouvez créer une méthode (on pourrait la nommer let) qui évite l'imbrication :

class Usable[A](a: A) {
  def use[B](f: A=>B) = f(a)
  def reuse[B,C](f: A=>B)(g: (A,B)=>C) = g(a,f(a))
  // Could add more
}
implicit def use_anything[A](a: A) = new Usable(a)

def calculate(a: Long) =
  firstFn.reuse(secondFn)((first, second) => thirdFn(first,second,second+a))

Mais maintenant, vous devrez peut-être nommer les mêmes choses plusieurs fois.

Si vous pensez que le premier formulaire est plus propre/plus élégant/plus lisible, alors pourquoi ne pas simplement vous y tenir ?

Tout d’abord, lisez ce récent message de commit au compilateur Scala de nul autre que Martin Odersky et prenez-le à cœur...


Peut-être que le vrai problème ici est de sauter immédiatement le pas en affirmant que ce n'est pas optimal.La JVM est très efficace pour optimiser ce genre de choses.Parfois, c'est tout simplement incroyable !

En supposant que vous rencontriez un véritable problème de performances dans une application qui a réellement besoin d'une accélération, vous devriez commencer par un rapport de profileur. prouver qu'il s'agit d'un goulot d'étranglement important, sur une JVM correctement configurée et réchauffée.

Alors, et alors seulement, devriez-vous chercher des moyens de le rendre plus rapide, ce qui pourrait finir par sacrifier la clarté du code.

Pourquoi ne pas utiliser la correspondance de modèles ici :

def calculer (a :Long) = firstFn match { case f => secondFn(f) match { case s => troisièmeFn(f,s,s + a) } }

Que diriez-vous d'utiliser le currying pour enregistrer les valeurs de retour de la fonction (les paramètres des groupes de paramètres précédents sont disponibles dans les groupes suivants).

Un peu bizarre mais assez concis et sans invocations répétées :

def calculate(a: Long)(f: Int = firstFn)(s: Long = secondFn(f)) = thirdFn(f, s, s + a)

println(calculate(1L)()())
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top