Is it possible to define a function on only one variant of a type?

This scenario seems to happen to me all the time. In a main function, I do pattern matching (case ... of) to identify what variant I have to deal with, to decide which subfunction to call. This subfunction should then be applied to only that variant. But I can’t find a way to define a function only on a variant. So if I write such a subfunction, unfortunately, I have to define it on the whole type, and match again to extract the variant (despite me and the compiler already knowing which variant it is).
Is there a solution here?


Could you share a snippet to illustrate the problem?
You can pass the values of the constructors to each subfunction:
If you need to ensure the function can only receive the specific variant, you can do this way:

1 Like

You have to remove the type constructor on the sub function. So if your type is

type Foo = Bar Int String | Baz (List String) Foo

you could do

handleFoo : Foo -> Float
handleFoo foo =
    case foo of
        Bar theInt theString ->
            handleBar theInt theString

        Baz theList theFoo ->
            handleBaz theList theFoo

handleBar : Int -> String -> Float
handleBar theInt theString = ...

handleBaz : List String -> Foo -> Float
handleBaz theList theFoo = ...

(Though I’m usually happy just having a long function if it has a clear separation into cases; I would only bother with factoring out the cases like this if there was a good reason to do it. If I end up with a bunch of functions named handleBar and handleBaz, I don’t really think that’s an improvement. In particular, the update function often works like that.)

1 Like

Thanks for your replies.
Actually my variants “constructors” take records as arguments, like this:

type MyType
    = Variant1 { field1 : List String, field2 : ..., fieldN: ...}
    | Variant2 { field1 : List String, field2: .... FieldN+1: ...}

I mean, there are a lot of fields in common, and also some fields that are differents depending on the variant. It’s a bit heavy to have to repeat all the fields of the record to define the function, but I guess that’s the best solution.

In that case, you could do

type alias Variant1Record = { field1 : List String, … }
type alias Variant2Record = { field1 : List String, ... }

type MyType = Variant1 Variant1Record | Variant2 Variant2Record

handleMyType : MyType -> Int
handleMyType mt = case mt of
    Variant1 rec -> handleVariant1 rec
    Variant2 rec -> handleVariant2 rec

handleVariant1 : Variant1Record -> Int
handleVariant1 rec = …

This way, you only need to list the fields once.


Oh, and you can also look into Elm’s extensible records to maybe factor out the repeated fields.


Also, when there is a good reason for fields to be common to multiple variant, I found that it almost always makes the code clearer to extract those fields in common like so

type alias MyType =
  { common1 : ...
  , common2 : ...
  , specific : Specific

type Specific
  = Variant1 { field1 : ..., field2 : ... }
  | Variant2 { field3 : ... }

See also this post discussing how record and custom types can be modelled using math (its not hard):

Pulling out the common fields is akin to extracting a common factor from some expression. I tend to think of product-sum-product form with all common fields pulled to the outer product as what you are generally aiming for.

This is in product-sum-product form:

-- Outer product
type alias MyType =
  { common1 : ...
  , common2 : ...
  , specific : Specific

-- Sum
type Specific
  = Variant1 { field1 : ..., field2 : ... } -- Inner Product
  | Variant2 { field3 : ... } -- Inner Product
1 Like

You can also write a function that works over some subset of the fields:

myFun : { a | field3 : String, field8 : Int } -> Something

Then any custom type variant with those fields can be passed to it (removing the constructor first of course).

type Specific
  = Variant1 { field1 : ..., field2 : ..., field3: String, ..., field8: Int }
  | Variant2 { field3 : String, ..., field8: Int }
  | VariantX ...

fnSomeVariants  : Specific -> Something
fnSomeVariants s = 
    case s of 
        Variant1 fields -> myFun fields
        Variant2 fields -> myFun fields
        _ -> DefaultSomething

Nice, I didn’t know you could do that.

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.