2017-01-10 7 views
1

Я хотел бы моделировать Mapper, который принимает в контейнере A с (T[A]), так что с функцией f: A => B мы получаем другой контейнер T[B]. У меня, после многих часов экспериментов (см комментировал код) придумали следующее решение:Scala несоответствие типов высшего kinded типа

sealed trait Mapper[ A, T[ A ], B ] { 
    //type Out <: T[B] 
    type Out[X] //= T[X] 
    def map(l: T[ A ], f: A => B): Out[B] 
    } 

    object Mappers { 

    implicit def typedMapper[ A, T[ A ] <: Iterable[ A ], B ]: Mapper[ A, T, B ] = 
     new Mapper[ A, T, B ] { 
     override type Out[X] = Iterable[X] 
     //override type Out <: Iterable[ B ] 
     //def map(l: T[ A ], f: A => B) : this.Out = { 
     def map(l: T[ A ], f: A => B) : Out[B] = { 
      println("map") 
      l.map(f) 
     } 
     } 

    implicit def IntMapper = typedMapper[Int, List, Int] 
    } 

    //def testMapper[ A, T[ A ], B ](l: T[ A ], f: A => B)(implicit mapper: Mapper[ A, T, B ]): T[B] = { 
    def testMapper[ A, T[ A ], B ](l: T[ A ], f: A => B)(implicit mapper: Mapper[ A, T, B ]) : Mapper[A, T, B]#Out[B]= { 
    println(mapper) 
    mapper.map(l, f) 
    } 

теперь я могу использовать его следующим образом:

import Mappers.IntMapper 
val l9 = testMapper(List(1, 2, 3), { x: Int => x + 1 }) 
println(l9) 

Хотя это работает я до сих пор потеря как я могу напрямую ограничить Out до T[B]. Если я это сделаю, я всегда получаю несоответствие типа. Может ли кто-нибудь указать более простой/простой способ сделать это без псевдонима типа или напрямую с помощью T[B]?

ТИА

+0

Ваш код в соответствии с опубликованным не компилируется, можете ли вы опубликовать версию, которая делает? –

+0

Вы предоставляете конкретную реализацию так называемого «Functor» в теории категорий. Существует широко используемая библиотека под названием «scalaz», а также «cat». Для хорошего резюме, посмотрите http://blog.tmorris.net/posts/functors-and-things-using-scala/index.html –

+0

'Mapper' не' Functor': вы можете ограничить возможный 'A' s и 'B', а 'Functor' должен работать для всех' A' и 'B'. Это делает это намного менее полезным в некоторых случаях, но я предполагаю, что для этого должно быть какое-то использование. – HTNW

ответ

1

Вот прокомментировал первое приближение к тому, что вы хотите. Члены типа были устранены, что выявило немного более глубокую проблему в том, что вы хотите сделать.

trait Mapper[A, T[_], B] { 
    def map(ta: T[A])(f: A => B): T[B] 
} 

// Note that Iterable[A]#map has type [B](A => B)Iterable[B]. You can't have typedMapper 
// like yours from above just yet, because T#map is not certain to return another T; 
// it only promises an Iterable. 
implicit def iterableMapper[A, B]: Mapper[A, Iterable, B] = new Mapper[A, Iterable, B] { 
    // Multiple param lists support the type inferencer 
    override def map(i: Iterable[A])(f: A => B) = i.map(f) 
} 

// Curried and arg-swapped version of Mapper 
type MapperOf[A, B] = { type l[T[_]] = Mapper[A, T, B] } 
def map[A, B, T[_]: MapperOf[A, B]#l](ta: T[A])(f: A => B): T[B] = implicitly[Mapper[A, T, B]].map(ta)(f) 

map(??? : Iterable[Any])(_.toString) // OK (at compile time, at least :P) 
map(List(1,2,3))(_*2) // NOPE! The inferencer has already decided T = List, before 
         // looking for implicits, so the resolution fails to notice that 
         // iterableMapper would work. 
map[Int, Int, Iterable](List(1,2,3))(_*2) // Works 

Это подталкивает пределы типа inferencer, поэтому вам нужны аргументы типа вручную.

Обратите внимание, что Iterable не имеет особого значения для иерархии коллекций. В основном это связано с тем, что Java имеет это. Для того, чтобы это было должным образом общим для коллекций, вам нужна вкусная CanBuildFrom темная магия.

import collection._, generic._ // Open the gates of hell 

implicit def collectionMapper[A, Coll[A] <: GenTraversableLike[A, Coll[A]], B] 
    (implicit builderFactory: CanBuildFrom[Coll[A], B, Coll[B]]): 
    Mapper[A, Coll, B] = 
    new Mapper[A, Coll, B] { 
    override def map(coll: Coll[A])(f: A => B): Coll[B] = coll.map(f) 
    } 

map(List(1))(_*2): List[Int] // Works 
map(Seq(1).view)(_*2): Seq[Int] // Works, but note how we lose the knowledge of the view 
           // Exercise for the reader: fix that. 
map(BitSet(1))(_*2): SortedSet[Int] // Works, but we lose the BitSet-ness 
            // Another exercise: fix it. 
+0

Я играл с вашим первым примером. Если я изменю «trait Mapper [A, T [_], B]', чтобы распознать «Mapper [A, T [A], B]», все еще работает так, как рекламируется. Если я изменю 'iterableMapper [A, B]', так что функция карты имеет только один список параметров, она все равно работает (версия 12.2). Теперь я не понимаю, зачем нужно «type MapperOf [A, B]». Почему это не работает? 'def map4 [A, B, T [_]] (ta: T [A], f: A => B): T [B] = неявно [Mapper1 [A, T, B]] .map (ta) (f) '(неявный не найден). TIA – user2051561

+0

Что касается решения, которое использует «CanBuildFrom», это из моей лиги. Мне нужно будет узнать, как это работает, прежде чем пытаться это сделать. – user2051561

+0

MapperOf - чистый сахар. Вы можете сбросить его и переписать после привязки к контексту как {type l [T [_]] = Mapper [A, T, B]} #l, но это уродливо. Все, что он действительно делает, - это карри-тип функции Mapper от (*, * -> *, *) -> * to (*, *) -> (* -> *) -> *. Контекст всегда привязывается к неявному аргументу для отображения (неявный $ _some_garbage_internal_name $$$: Mapper [A, T, B]).Причина, по которой вы получаете INF, - это то, что вы не знаете, что такое A, T или B. Вам нужно, чтобы пользователь предоставил это вам как неявный парам. – HTNW

Смежные вопросы