2017-10-17 32 views
2

J'ai un cas où je souhaite appliquer des modifications à un objet basé sur la présence de (quelques, disons, 5 à 10) options. Donc, en gros, si je devais le faire impérieusement, ce que je vise est:De gauche à droite arguments type inférence

var myObject = ... 
if (option.isDefined) { 
    myObject = myObject.modify(option.get) 
} 
if (option2.isDefined) { 
    myObject = myObject.someOtherModification(option2.get) 
} 

(S'il vous plaît noter: peut-être mon objet est mutable, peut-être pas, ce n'est pas le point ici.)

Je pensais que ce serait plus joli si je tentais de mettre en œuvre un moyen couramment la rédaction du présent, par exemple (code de pseudo ...):

myObject.optionally(option, _.modify(_)) 
     .optionally(option2, _.someOtherModification(_)) 

Alors j'ai commencé avec un exemple de code qui ne IntelliJ point fort comme une erreur, mais cela ne construit pas réellement.

class MyObject(content: String) { 
    /** Apply a transformation if the optional is present */ 
    def optionally[A](optional: Option[A], operation: (A, MyObject) => MyObject): MyObject = 
     optional.map(operation(_, this)).getOrElse(this) 
    /** Some possible transformation */ 
    def resized(length : Int): MyObject = new MyObject(content.substring(0, length)) 
} 
object Test { 
    val my = new MyObject("test") 
    val option = Option(2) 

    my.optionally(option, (size, value) => value.resized(size)) 
} 

Maintenant, dans mon cas, le type MyObject est d'une API externe, donc je créé une conversion implicite pour aider, donc ce qu'il ressemble vraiment à:

// Out of my control 
class MyObject(content: String) { 
    def resized(length : Int): MyObject = new MyObject(content.substring(0, length)) 
} 

// What I did : create a rich type over MyObject 
class MyRichObject(myObject: MyObject) { 
    def optionally[A](optional: Option[A], operation: (A, MyObject) => MyObject): MyObject = optional.map(operation(_, myObject)).getOrElse(myObject) 
} 
// And an implicit conversion 
object MyRichObject { 
    implicit def apply(myObject: MyObject): MyRichObject = new MyRichObject(myObject) 
} 

Et puis, je l'utilise de cette façon:

object Test { 
    val my = new MyObject("test") 
    val option = Option(2) 
    import MyRichObject._ 
    my.optionally(option, (size, value) => value.resized(size)) 
} 

et cette fois, il échoue dans IntelliJ et lors de la compilation parce que le type de Option est inconnu: Error:(8, 26) missing parameter type my.optionally(option, (size, value) => value.resized(size))

Pour le faire fonctionner, je peux:

  1. spécifier activement un type de l'argument size: my.optionally(option, (size: Int, value) => value.resized(size))
  2. Réécrire le optionally à une version cari

Aucun d'entre eux est vraiment mauvais, mais si je peux demander:

  • Y at-il une raison pour laquelle une version cari fonctionne, mais une version d'arguments multiples semble ne pas déduire le type paramétrisé,
  • Pourrait-il être écrit d'une manière qui fonctionne sans spécifier les types réels
  • et comme bonus (bien que cela puisse être basé sur l'opinion), comment l'écririez-vous (une sorte de foldLeft sur une séquence d'options me vient à l'esprit ...)?

Répondre

2

Une option pour votre considération:

// Out of my control 
class MyObject(content: String) { 
    def resized(length : Int): MyObject = new MyObject(content.substring(0, length)) 
} 

object MyObjectImplicits { 

    implicit class OptionalUpdate[A](val optional: Option[A]) extends AnyVal { 
    def update(operation: (A, MyObject) => MyObject): MyObject => MyObject = 
     (obj: MyObject) => optional.map(a => operation(a, obj)).getOrElse(obj) 
    } 

} 
object Test { 
    val my = new MyObject("test") 
    val option = Option(2) 
    import MyObjectImplicits._ 
    Seq(
    option.update((size, value) => value.resized(size)), 
    // more options... 
).foldLeft(my)(_) 
} 

pourriez tout aussi bien utiliser un comme vous avez dit la version cari de votre optionally,.

+0

J'aime l'idée d'inverser les implicits au type Optional, au lieu de MyObject (généralisation: rendre le type paramétrable convertible, éliminant ainsi toute méthode paramétrée à l'autre extrémité). Je suis d'accord avec votre conclusion: la version au curry semble au moins aussi bonne. M'a fait penser qu'il pourrait y avoir quelque chose à essayer avec les types de témoins (https://stackoverflow.com/questions/8524878/implicit-conversion-vs-type-class?rq=1) – GPI

+0

Voir aussi http: //pchiusano.blogspot. hk/2011/05/making-most-of-scalas-extrêmement-limited.html à propos de la limitation sur l'inférence de type que vous avez observée – PH88

+0

Merci d'avoir creusé cela. Ce que je ne comprends pas, c'est que dans le premier exemple d'implémentation (sans implicits), cela fonctionne réellement ... – GPI

1

Une agréable façon de penser à la nécessité d'ajouter le type il y a écrire cette façon:

object Test { 
    val my = new MyObject("test") 
    val option = Some(2) 
    my.optionally[Int](option, (size, value) => value.resized(size)) 
} 

Une autre façon, si vous ne gérerez un type depuis la création d'objets, est de déplacer le générique à la création de classe, mais attention, avec cette option, vous ne pouvez avoir qu'un seul type par exemple:

class MyObject[A](content: String) { 
    def optionally(optional: Option[A], operation: (A, MyObject[A]) => MyObject[A]): MyObject[A] = 
    optional.map(operation(_, this)).getOrElse(this) 
    def resized(length : Int): MyObject[A] = new MyObject[A](content.substring(0, length)) 
} 

object Test { 
    val my = new MyObject[Int]("test") 
    val option = Some(2) 
    my.optionally(option, (size, value) => value.resized(size)) 
} 

Comme vous pouvez le voir, maintenant tous les endroits où les génériques WAS est tak en par le type Int, parce que ce que vous vouliez en premier lieu, voici une jolie réponse dire pourquoi:

(seulement la partie que je pense applique ici :)

4) Lorsque la le type de retour déduit serait plus général que prévu, p.

Source: In Scala, why does a type annotation must follow for the function parameters ? Why does the compiler not infer the function parameter types?

+0

Merci pour la première suggestion. Cela semble évident rétrospectivement, et c'est plutôt gentil. Votre deuxième proposition est bien aussi, mais a des restrictions, plus je ne peux pas réellement modifier MyObject. Le reste de votre réponse ne semble pas s'appliquer car il n'explique pas pourquoi l'inférence cesse de fonctionner lorsque des implicites entrent en jeu ... – GPI

+0

@GPI dans ce cas La règle 4) s'applique: 4) Quand le type de retour déduit serait plus général que prévu, par exemple, Any. Je veux dire vous où retourner un type général, quand vous en vouliez un spécifique, n'êtes-vous pas d'accord? –

+0

@GPI de toute façon j'ai supprimé la partie qui n'a pas d'importance pour clarifier la réponse, j'ai toujours aimé aider. –