Конструкция совпадения Haskell аналогична шаблону типа F #?
TL;DR
Если я правильно понимаю, у Haskell нет подтипов так, как это делает F #. Таким образом, я ожидаю, что у него нет шаблона тестового типа для сопоставления, например F #. Имеет ли он какие-либо аналогичные конструкции, которые можно использовать для метапрограммирования?
Пример из практики
У меня есть проект F #, в котором у меня есть развязанные модули, которые говорят через механизм обмена сообщениями. Недавно я задавался вопросом, как будет выглядеть этот код, или если бы это было возможно написать так, если бы я должен был перенести его в Haskell.
Основная идея такова. Сообщение - это тип, который наследуется от интерфейса сообщения:
type Message = interface end
Обработчик - это функция, которая принимает определенный подтип Message
и возвращает a Message
:
type Handle<'TMsg when 'TMsg :> Message> = 'TMsg -> Message
Существует Bus
с методом publish
, который распространяет сообщение на свои внутренние каналы. Существует один тип Channel<'TMsg>
для каждого типа сообщений, и они динамически добавляются при регистрации обработчика. Шина публикует все сообщения для всех каналов, и если она неправильного типа, канал просто возвращает пустую последовательность:
type Channel<'TIn when 'TIn :> Message>(...) = ...
interface Channel with
member x.publish (message : Message) =
match message with
| :? 'TIn as msg ->
Seq.map (fun handle -> handle msg) _handlers
|> Seq.filter (fun msg -> msg <> noMessage)
| _ -> Seq.empty
В конечном итоге то, что я здесь делаю, - это динамическое метапрограммирование, которое позволяет мне иметь строго типизированные сообщения, которые по-прежнему проходят через один и тот же механизм посередине. Я не такой, как Haskell, как F #, и я не могу понять, как это сделать в Haskell.
Я прав, что у Haskell нет конструкции match... with... :?... as...
? Имеет ли он подобную конструкцию или другой подход к подобному метапрограммированию, с которым вы можете решить эту проблему? Есть какой-то механизм box/unbox?
Ответы
Ответ 1
Извините, что ответил на ваш вопрос Mu, но я бы тоже не делал этого в F #.
Это Message
интерфейс, что известно как Маркер интерфейс, и хотя это спорное, я считаю, что код запах на любом языке, который поддерживает аннотацию (атрибуты, в .NET).
Интерфейс маркера ничего не делает, поэтому вы можете достичь такого же результата без него.
Я бы не создал такую систему сообщений, как на С#, потому что интерфейс Marker ничего не добавляет, кроме иллюзии, что сообщение каким-то образом "набрано".
Я бы не создал такую систему сообщений, как в F #, потому что в F # есть более эффективная, безопасная, строго типизированная альтернатива:
type UserEvent =
| UserCreated of UserCreated
| EmailVerified of EmailVerified
| EmailChanged of EmailChanged
Это четко указывает, что множество событий конечно и хорошо известно. При этом вы можете проверять время компиляции вместо проверки времени выполнения.
(см. здесь для полного примера.)
Такой Дискриминационный Союз легко перевести на Haskell:
data UserEvent = UserCreated UserCreatedData
| EmailVerified EmailVerifiedData
| EmailChanged EmailChangedData
Ответ 2
Haskell разработан таким образом, что типы могут быть полностью стерты во время выполнения. То есть, когда реализация сохраняет значение типа T
в памяти, нет необходимости помечать его какой-либо меткой, обозначающей "это тип T
".
Это дает хорошие гарантии параметричности взамен, также называемые "свободными теоремами". Например, функция, имеющая полиморфный тип
f :: a -> a
должен возвращать свой аргумент или не завершаться. То есть, мы знаем, что f=id
(если мы знаем, что это заканчивается).
Конкретно это означает, что нет способа написать что-то вроде
f :: a -> a
f x = if a == Int then x+1 else x
Если вы хотите что-то сделать, можно вручную добавить теги типа через класс Data.Typeable
:
{-# LANGUAGE ScopedTypeVariables, GADTs, TypeOperators #-}
import Data.Typeable
f :: forall a . Typeable a => a -> a
f x = case eqT :: Maybe (a :~: Int) of
Just Refl -> x+1
Nothing -> x
Обратите внимание, как изменился тип и теперь имеет ограничение. Это необходимо, поскольку эффект функции нарушает свободную теорему неограниченного полиморфного типа.
Итак, если вам действительно нужно выполнить проверки типа времени выполнения, используйте ограничение Typeable
. Обратите внимание, что это, возможно, слишком много общего. Если вы знаете, что у вас есть небольшое количество типов, возможно, вы можете использовать тип суммы вместо этого и использовать простой шаблон для конструкторов для проверки типа:
data T = TInt Int
| TChar Char
f :: T -> T
f (TInt i) = TInt (i+1)
f (TChar c) = TChar c